<!-- https://css-tricks.com/how-to-create-an-animated-chart-of-nested-squares-using-masks/ -->
html,
body {
height: 100%;
}
body {
background: #f0f0f0;
display: grid;
place-items: center;
padding: 16px;
}
svg {
display: block;
overflow: visible;
max-width: 100%;
height: auto;
}
rect {
transition: all 0.2s linear;
}
svg:hover > rect {
opacity: 0.4;
transform: translate(8px, -8px);
}
svg:hover > rect:hover {
opacity: 1;
transform: translate(0);
}
svg:hover > rect:hover ~ rect {
transform: translate(-8px, 8px);
}
type DataSetEntry = {
label: string;
value: number;
};
type DataSet = DataSetEntry[];
const remapValue = (
value: number,
fromMin: number,
fromMax: number,
toMin: number,
toMax: number
): number => {
return toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin);
};
const valueRemapper = (
fromMin: number,
fromMax: number,
toMin: number,
toMax: number
) => {
return (value: number): number => {
return remapValue(value, fromMin, fromMax, toMin, toMax);
};
};
const createSvgNSElement = (element: string): SVGElement => {
return document.createElementNS("http://www.w3.org/2000/svg", element);
};
const createRect = (
dimension: number,
x: number,
y: number,
fill: string
): SVGRectElement => {
const rect: SVGRectElement = createSvgNSElement("rect") as SVGRectElement;
rect.setAttribute("width", `${dimension}`);
rect.setAttribute("height", `${dimension}`);
rect.setAttribute("x", `${x}`);
rect.setAttribute("y", `${y}`);
rect.setAttribute("fill", fill);
return rect;
};
const maskIdForDimension = (dimension: number): string => {
const fixedDimension = dimension.toFixed();
return `maskW${fixedDimension}`;
};
const svgDimension: number = 320;
const colors: string[] = [
"#264653",
"#2A9D8F",
"#E76F51",
"#F4A261",
"#E9C46A"
];
const fallbackColor: string = "#DDDDDD";
const rawDataSet: DataSet = [
{ label: "Bad", value: 1231 },
{ label: "Beginning", value: 6321 },
{ label: "Developing", value: 10028 },
{ label: "Accomplished", value: 12123 },
{ label: "Exemplary", value: 2120 }
];
const dataSetHighestValue: number = Math.max(
...rawDataSet.map((entry: DataSetEntry) => entry.value)
);
const remapDataSetValueToSvgDimension = valueRemapper(
0,
dataSetHighestValue,
0,
svgDimension
);
const data: DataSet = rawDataSet.sort(
(a: DataSetEntry, b: DataSetEntry) => b.value - a.value
);
const svg: SVGSVGElement = createSvgNSElement("svg") as SVGSVGElement;
svg.setAttribute("viewBox", `0 0 ${svgDimension} ${svgDimension}`);
svg.setAttribute("width", `${svgDimension}`);
svg.setAttribute("height", `${svgDimension}`);
data.forEach((d: DataSetEntry, index: number) => {
const mask: SVGMaskElement = createSvgNSElement("mask") as SVGMaskElement;
const rectDimension: number = remapDataSetValueToSvgDimension(d.value);
const rect = createRect(
rectDimension,
0,
svgDimension - rectDimension,
"white"
);
mask.setAttribute("id", maskIdForDimension(rectDimension));
mask.appendChild(rect);
const smallerRectIndex = index + 1;
if (data[smallerRectIndex] !== undefined) {
const smallerRectDimension: number = remapDataSetValueToSvgDimension(
data[smallerRectIndex].value
);
const smallerRect = createRect(
smallerRectDimension,
0,
svgDimension - smallerRectDimension,
"black"
);
mask.appendChild(smallerRect);
}
svg.appendChild(mask);
});
data.forEach((d: DataSetEntry, index: number) => {
const rectDimension: number = remapDataSetValueToSvgDimension(d.value);
const rect = createRect(
rectDimension,
0,
svgDimension - rectDimension,
colors[index] ?? fallbackColor
);
rect.setAttribute("mask", `url(#${maskIdForDimension(rectDimension)})`);
svg.appendChild(rect);
});
document.body.appendChild(svg);
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.