Visual apperance

This commit is contained in:
Adrian Rumpold
2025-07-09 11:51:32 +02:00
parent 5dc583e387
commit 86688519f5
11 changed files with 310 additions and 147 deletions

30
src/components/Chart.css Normal file
View File

@@ -0,0 +1,30 @@
.dot {
stroke: #fff;
stroke-width: 1;
}
.selected {
fill: rgb(223 110 38);
}
.axis-label {
font-size: 12px;
fill: #666;
}
.axis text {
font-size: 11px;
fill: #666;
}
.axis path,
.axis line {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
.grid line {
stroke: #e0e0e0;
stroke-dasharray: 2, 2;
}

19
src/components/Legend.css Normal file
View File

@@ -0,0 +1,19 @@
.legend {
.box {
display: inline-block;
width: 32px;
height: 32px;
border-radius: 4px;
margin-right: 8px;
vertical-align: middle;
}
ul {
list-style: none;
margin: 16px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 16px;
}
}

28
src/components/Legend.tsx Normal file
View File

@@ -0,0 +1,28 @@
import { colorScheme } from "../config";
import "./Legend.css";
const labels = {
0: "Keine Erfahrung",
1: "Grundkenntnisse",
2: "Geübte Anwendung",
3: "Sichere Praxisanwendung",
4: "Fachwissen und Erfahrung",
};
export default function Legend() {
return (
<div className="legend">
<ul>
{Object.entries(labels).map(([level, label]) => (
<li key={level}>
<span
className="box"
style={{ backgroundColor: `${colorScheme[level]}` }}
></span>
<span className="label">{label}</span>
</li>
))}
</ul>
</div>
);
}

22
src/components/QRCode.css Normal file
View File

@@ -0,0 +1,22 @@
.qr-code-container {
background-color: rgb(223 110 37);
flex-basis: 20%;
flex-shrink: 0;
padding: 16px;
border-radius: 16px;
filter: drop-shadow(0 8px 8px hsl(202.5deg 20% 76.5%));
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
img {
background-color: #fff;
border-radius: 16px;
padding: 16px;
width: 50%;
}
}

16
src/components/QRCode.tsx Normal file
View File

@@ -0,0 +1,16 @@
import "./QRCode.css";
export default function QRCode() {
return (
<div className="qr-code-container">
<h1>Scan me!</h1>
<p>
Scanne den Code und zeige, welche KI-Skills du mitbringst &mdash; ganz
egal, auf welchem Level du bist.
</p>
<div className="qr-code bracket-frame">
<img src="https://upload.wikimedia.org/wikipedia/commons/4/41/QR_Code_Example.svg" />
</div>
</div>
);
}

View File

@@ -2,6 +2,8 @@ import * as d3 from "d3";
import { useEffect, useRef } from "react";
import { colorScheme, config } from "../config";
import "./Chart.css";
interface QuestionGroupChartProps {
question: string;
groupData: { response: string }[];
@@ -9,6 +11,31 @@ interface QuestionGroupChartProps {
xScale: d3.ScaleBand<string>;
}
function makeDot(
shape: "rect" | "circle",
g: d3.Selection<SVGGElement, unknown, null, undefined>,
dotX: number,
dotY: number
) {
if (shape === "circle") {
return g
.append("circle")
.attr("class", "dot")
.attr("cx", dotX)
.attr("cy", dotY)
.attr("r", config.dotRadius);
} else if (shape === "rect") {
return g
.append("rect")
.attr("class", "dot")
.attr("x", dotX - config.dotRadius)
.attr("y", dotY - config.dotRadius)
.attr("width", config.dotRadius * 2)
.attr("height", config.dotRadius * 2);
}
throw new Error(`Unsupported shape: ${shape}`);
}
export function QuestionGroupChart({
question,
groupData,
@@ -23,27 +50,14 @@ export function QuestionGroupChart({
// Group responses by category (within this group only)
const responseGroups = d3.group(groupData, (d) => d.response);
const maxCount =
d3.max(Array.from(responseGroups.values(), (values) => values.length)) ||
0;
const maxRows = Math.ceil(maxCount / config.columnsPerGroup);
const chartHeight =
maxRows * (config.dotRadius * 2 + config.dotSpacing) +
config.margin.bottom;
const chartWidth =
xScale.range()[1] + config.margin.left + config.margin.right;
const chartHeight = 200;
const chartWidth = xScale.range()[1];
const svg = d3
.select(svgRef.current)
.attr("width", chartWidth)
.attr("height", chartHeight);
const g = svg
.append("g")
.attr(
"transform",
`translate(${config.margin.left},${config.margin.top})`
);
const g = svg.append("g");
// Dots
responses.forEach((response) => {
@@ -58,36 +72,25 @@ export function QuestionGroupChart({
(col - (config.columnsPerGroup - 1) / 2) *
(config.dotRadius * 2 + config.dotSpacing);
const dotY =
chartHeight -
config.margin.top -
config.margin.bottom -
(row + 1) * (config.dotRadius * 2 + config.dotSpacing);
chartHeight - (row + 1) * (config.dotRadius * 2 + config.dotSpacing);
g.append("circle")
.attr("class", "dot")
.attr("cx", dotX)
.attr("cy", dotY)
.attr("r", config.dotRadius)
.attr("fill", colorScheme[response] || "#666");
makeDot("rect", g, dotX, dotY).attr(
"fill",
colorScheme[response] || "#666"
);
});
});
}, [groupData, responses, xScale, question]);
/*
// Calculate height for container
const responseGroups = d3.group(groupData, (d) => d.response);
const maxCount =
d3.max(Array.from(responseGroups.values(), (values) => values.length)) || 0;
const maxRows = Math.ceil(maxCount / config.columnsPerGroup);
const chartHeight =
maxRows * (config.dotRadius * 2 + config.dotSpacing) +
config.margin.top +
config.margin.bottom;
*/
return (
<div className="question-group">
<div className="question-title">{question}</div>
<svg ref={svgRef}></svg>
<div className="question-title">{question}</div>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo.
</p>
</div>
);
}