nolual's picture
Upload 55 files
0c20ea8
/* eslint-disable @typescript-eslint/no-use-before-define */
import React, { useRef, useState, useMemo } from "react";
import { scaleLinear } from "@visx/scale";
import { Brush } from "@visx/brush";
import { Bounds } from "@visx/brush/lib/types";
import BaseBrush, {
BaseBrushState,
UpdateBrush,
} from "@visx/brush/lib/BaseBrush";
import { PatternLines } from "@visx/pattern";
import { Group } from "@visx/group";
import { LinearGradient } from "@visx/gradient";
import { extent } from "@visx/vendor/d3-array";
import { BrushHandleRenderProps } from "@visx/brush/lib/BrushHandle";
import AreaChart from "./Area";
const brushMargin = { top: 0, bottom: 15, left: 50, right: 20 };
const chartSeparation = 30;
const PATTERN_ID = "brush_pattern";
const GRADIENT_ID = "brush_gradient";
export const accentColor = "#f6acc8";
export const background = "#584153";
export const background2 = "#af8baf";
const getWeek = (d: any) => d.week;
const getIndice = (d: any) => d.indice_base_100;
export interface week_distribution {
indice_base_100: number;
week: number;
}
export type BrushProps = {
data: week_distribution[];
width: number;
height: number;
margin?: { top: number; right: number; bottom: number; left: number };
compact?: boolean;
};
function BrushChart({
data,
compact = false,
width,
height,
margin = {
top: 20,
left: 50,
bottom: 20,
right: 20,
},
}: BrushProps) {
const brushRef = useRef<BaseBrush | null>(null);
const [filteredData, setFilteredData] = useState(data);
const onBrushChange = (domain: Bounds | null) => {
if (!domain) return;
const { x0, x1, y0, y1 } = domain;
const dataCopy = data.filter((d) => {
const x = getWeek(d);
const y = getIndice(d);
return x > x0 && x < x1 && y > y0 && y < y1;
});
setFilteredData(dataCopy);
};
const innerHeight = height - margin.top - margin.bottom;
const topChartBottomMargin = compact
? chartSeparation / 2
: chartSeparation + 10;
const topChartHeight = 0.8 * innerHeight - topChartBottomMargin;
const bottomChartHeight = innerHeight - topChartHeight - chartSeparation;
const xMax = Math.max(width - margin.left - margin.right, 0);
const yMax = Math.max(topChartHeight, 0);
const xBrushMax = Math.max(width - brushMargin.left - brushMargin.right, 0);
const yBrushMax = Math.max(
bottomChartHeight - brushMargin.top - brushMargin.bottom,
0
);
const weekScale = useMemo(
() =>
scaleLinear<number>({
range: [0, xMax],
domain: extent(data, getWeek) as [number, number],
}),
[xMax, data]
);
const brushWeekScale = useMemo(
() =>
scaleLinear<number>({
range: [0, xBrushMax],
domain: extent(data, getWeek) as [number, number],
}),
[xBrushMax, data]
);
const indiceScale = useMemo(
() =>
scaleLinear<number>({
range: [topChartHeight, 0],
domain: [70, 105],
}),
[topChartHeight, data]
);
const initialBrushPosition = useMemo(
() => ({
start: { x: brushWeekScale(data[0].week) },
end: { x: brushWeekScale(data[10].week) },
}),
[brushWeekScale]
);
const handleClearClick = () => {
if (brushRef?.current) {
setFilteredData(data);
brushRef.current.reset();
}
};
const handleResetClick = () => {
if (brushRef?.current) {
const updater: UpdateBrush = (prevBrush) => {
const newExtent = brushRef.current!.getExtent(
initialBrushPosition.start,
initialBrushPosition.end
);
const newState: BaseBrushState = {
...prevBrush,
start: { y: newExtent.y0, x: newExtent.x0 },
end: { y: newExtent.y1, x: newExtent.x1 },
extent: newExtent,
};
return newState;
};
brushRef.current.updateBrush(updater);
}
};
return (
<div>
<svg
width={width}
height={height}
style={{
borderBottomLeftRadius: "14px",
borderBottomRightRadius: "14px",
boxShadow: "0 0 8px rgba(0, 0, 0, 0.2)",
}}
>
<LinearGradient
id={GRADIENT_ID}
from={background}
to={background2}
rotate={45}
/>
<rect
x={0}
y={0}
width={width}
height={height}
fill={`url(#${GRADIENT_ID})`}
/>
<AreaChart
hideBottomAxis={compact}
data={filteredData}
width={width}
margin={{ ...margin, bottom: topChartBottomMargin }}
yMax={yMax}
xScale={weekScale}
yScale={indiceScale}
gradientColor={background2}
/>
<AreaChart
hideBottomAxis
hideLeftAxis
data={data}
width={width}
yMax={yBrushMax}
xScale={brushWeekScale}
yScale={indiceScale}
margin={brushMargin}
top={topChartHeight + topChartBottomMargin + margin.top}
gradientColor={background2}
>
<PatternLines
id={PATTERN_ID}
height={8}
width={8}
stroke={accentColor}
strokeWidth={1}
orientation={["diagonal"]}
/>
<Brush
xScale={brushWeekScale}
yScale={indiceScale}
width={xBrushMax}
height={yBrushMax}
margin={brushMargin}
handleSize={8}
innerRef={brushRef}
resizeTriggerAreas={["left", "right"]}
brushDirection="horizontal"
initialBrushPosition={initialBrushPosition}
onChange={onBrushChange}
onClick={() => setFilteredData(data)}
selectedBoxStyle={{
fill: `url(#${PATTERN_ID})`,
stroke: "white",
}}
useWindowMoveEvents
renderBrushHandle={(props) => <BrushHandle {...props} />}
/>
</AreaChart>
</svg>
</div>
);
}
function BrushHandle({ x, height, isBrushActive }: BrushHandleRenderProps) {
const pathWidth = 8;
const pathHeight = 15;
if (!isBrushActive) {
return null;
}
return (
<Group left={x + pathWidth / 2} top={(height - pathHeight) / 2}>
<path
fill="#f2f2f2"
d="M -4.5 0.5 L 3.5 0.5 L 3.5 15.5 L -4.5 15.5 L -4.5 0.5 M -1.5 4 L -1.5 12 M 0.5 4 L 0.5 12"
stroke="#999999"
strokeWidth="1"
style={{ cursor: "ew-resize" }}
/>
</Group>
);
}
export default BrushChart;