|
|
|
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; |
|
|