computeSeries = ({
data,
width,
height,
align,
spacing,
xPadding,
xName = "x"
}) => {
const xAccessor = (d, xName) => d[xName];
const slices = [];
let maxSum;
let maxValues;
data.forEach((serie) => {
serie.data.forEach((datum) => {
let slice = slices.find((s) => s.id === datum.x);
if (!slice) {
slice = {
id: xAccessor(datum, xName),
total: 0,
values: [],
x: 0
};
slices.push(slice);
}
const total = slice.total + datum.y;
slice.total = total;
slice.values.push({
serieId: serie.id,
value: datum.y,
position: 0,
height: 0,
beforeHeight: 0
});
if (maxSum === undefined || total > maxSum) {
maxSum = total;
}
if (maxValues === undefined || slice.values.length > maxValues) {
maxValues = slice.values.length;
}
});
});
const xScale = d3
.scalePoint()
.domain(slices.map((slice) => slice.id))
.range([0, width]);
const heightScale = d3
.scaleLinear()
.domain([0, maxSum])
.range([0, height - maxValues * spacing]);
slices.forEach((slice) => {
slice.x = xScale(slice.id);
const sliceHeight =
heightScale(slice.total) + slice.values.length * spacing;
let offset = 0;
if (align === "middle") {
offset = (height - sliceHeight) / 2;
} else if (align === "end") {
offset = height - sliceHeight;
}
slice.values
.sort((a, b) => b.value - a.value)
.forEach((value, _, all) => {
const previousValues = all.filter((v) => v.value < value.value);
const equalValues = previousValues.filter(
(v) => v.value === value.value
);
const equalValuesHeightSum = equalValues.reduce(
(t, v) => t + v.height,
0
);
const beforeValue =
previousValues.reduce((t, v) => t + v.value, 0) -
equalValuesHeightSum;
value.height = heightScale(value.value);
value.beforeHeight =
heightScale(beforeValue) +
offset +
spacing * (previousValues.length - equalValues.length + 0.5);
});
});
const areaPointPadding = xScale.step() * Math.min(xPadding * 0.5, 0.5);
const series = data.map((serie) => {
const computedSerie = {
id: serie.id,
data: serie,
points: [],
areaPoints: []
};
serie.data.forEach((datum, i) => {
const slice = slices.find((s) => s.id === datum.x);
const position = slice.values.find((v) => v.serieId === serie.id);
const x = slice.x;
const { beforeHeight, height } = position;
const y = beforeHeight + height / 2;
const y0 = beforeHeight;
const y1 = beforeHeight + height;
computedSerie.points.push({
x,
y,
height,
data: { ...datum }
});
if (i > 0) {
computedSerie.areaPoints.push({ x: x - areaPointPadding, y0, y1 });
}
computedSerie.areaPoints.push({ x, y0, y1 });
if (i < serie.data.length - 1) {
computedSerie.areaPoints.push({ x: x + areaPointPadding, y0, y1 });
}
});
return computedSerie;
});
return {
series,
xScale,
heightScale
};
}