cl-react-graph
Version:
React D3 Charts
192 lines (178 loc) • 4.36 kB
text/typescript
import { ScaleLinear } from "d3-scale";
import { SpringConfig } from "@react-spring/web";
import { EChartDirection } from "../../BarChart";
import { BarChartDataSet } from "../../Histogram";
import { ColorScheme, getFill } from "../../utils/colorScheme";
import { ExtendedGroupItem } from "./Bars";
type HistogramSpringProps = {
bins: [number, number][];
values: BarChartDataSet[];
height: number;
width: number;
dataSets: ExtendedGroupItem[];
numericScale: ScaleLinear<any, any>;
continuousScale: ScaleLinear<any, any>;
colorScheme: ColorScheme;
hoverColorScheme?: ColorScheme;
config: SpringConfig;
direction: EChartDirection;
radius?: number;
};
/**
* Build the from / to spring animation properties to animate the bars.
*/
export const buildHistogramSprings = (props: HistogramSpringProps) => {
const {
bins,
direction,
config,
height,
dataSets,
numericScale,
continuousScale,
colorScheme,
hoverColorScheme,
radius = 4,
} = props;
const s = dataSets.map((item, index) => {
const bandPosition = continuousScale(bins[index][0]);
// Ensure that if we have bins that don't start at 0 our bars will still have the correct width
const startValue = bins[0][0];
const binWidth = bins[index][1] - bins[index][0];
const itemWidth = continuousScale(binWidth + startValue);
const itemHeight = numericScale(item.value);
const fill = getFill(colorScheme[item.datasetIndex]);
const hoverFill = getFill(
hoverColorScheme?.[item.datasetIndex] ?? colorScheme[item.datasetIndex]
);
if (direction === EChartDirection.HORIZONTAL) {
return horizontalSpring({
fill,
hoverFill,
bandPosition,
height,
itemWidth,
config,
itemHeight,
radius,
});
}
return verticalSpring({
fill,
hoverFill,
bandPosition,
height,
itemWidth,
config,
itemHeight,
radius,
});
});
return s;
};
type FnProps = {
fill: string;
hoverFill: string;
bandPosition: number;
height: number;
itemWidth: number;
itemHeight: number;
config: SpringConfig;
radius: number;
};
const horizontalSpring = ({
fill,
hoverFill,
bandPosition,
height,
itemWidth,
itemHeight,
config,
radius,
}: FnProps) => {
const from = {
x: 0,
y: height - itemWidth - bandPosition,
};
const to = {
x: 0,
y: height - itemWidth - bandPosition,
};
let r = radius * 2 < itemHeight ? radius : itemHeight / 2;
const topRightCurve = `a${r} ${r} 0 0 1 ${r} ${r}`;
const bottomRightCurve = `a${r} ${r} 0 0 1 -${r} ${r}`;
const vertical = itemWidth;
const horizontal = itemHeight;
return {
from: {
width: 0,
d: `m${from.x} ${from.y} h${0} ${topRightCurve} v${
vertical - 2 * r
} ${bottomRightCurve} h-${0} v-${vertical - 2 * r}`,
fill,
hoverFill,
x: 0,
y: height - itemWidth - bandPosition,
height: itemWidth,
},
to: {
d: `m${to.x} ${to.y} h${horizontal - r} ${topRightCurve} v${
vertical - 2 * r
} ${bottomRightCurve} h-${horizontal - r} v-${vertical - 2 * r}`,
width: itemHeight,
fill,
hoverFill,
x: 0,
y: height - itemWidth - bandPosition,
height: itemWidth,
},
config,
};
};
const verticalSpring = ({
fill,
hoverFill,
bandPosition,
height,
itemWidth,
itemHeight,
config,
radius,
}: FnProps) => {
const r = radius * 2 < itemWidth ? radius : itemWidth / 2;
const topLeftCurve = `a${r},${r} 0 0 1 ${r},-${r}`;
const topRightCurve = `a${r} ${r} 0 0 1 ${r} ${r}`;
const from = {
x: bandPosition,
y: height,
};
const to = {
x: bandPosition,
y: height,
};
return {
from: {
height: 0,
d: `m${from.x} ${from.y} v0 ${topLeftCurve} h${
itemWidth - 2 * r
} ${topRightCurve} v0 h-${itemHeight} z`,
fill,
hoverFill,
x: bandPosition,
y: height,
width: itemWidth,
},
to: {
height: itemHeight,
d: `m${to.x} ${to.y} v-${itemHeight} ${topLeftCurve} h${
itemWidth - 2 * r
} ${topRightCurve} v${itemHeight} h-${itemWidth} z`,
fill,
hoverFill,
x: bandPosition,
y: height - itemHeight,
width: itemWidth,
},
config,
};
};