Bar - Linked Brushing
Live View
Loading...
Live Editor
const barData = [ { name: "SEA", range: [ new Date(2013, 1, 1), new Date(2019, 1, 1), ], }, { name: "HKG", range: [ new Date(2015, 1, 1), new Date(2015, 5, 1), ], }, { name: "LHR", range: [ new Date(2016, 5, 1), new Date(2019, 1, 1), ], }, { name: "DEN", range: [ new Date(2018, 8, 1), new Date(2019, 1, 1), ], }, ]; const pointData = [ { name: "SEA", date: new Date(2012, 9, 1), }, { name: "HKG", date: new Date(2014, 3, 1), }, { name: "LHR", date: new Date(2015, 6, 1), }, { name: "DEN", date: new Date(2018, 3, 1), }, ]; const containerStyle = { display: "flex", flexDirection: "row", flexWrap: "wrap", alignItems: "center", justifyContent: "center", paddingBottom: 50 }; const domain = { y: [ new Date(2012, 1, 1), new Date(2019, 1, 1), ], x: [0.5, 4.5], }; const sharedProps = { width: 800, domain, }; class DraggablePoint extends React.Component { static defaultEvents = [ { target: "data", eventHandlers: { onMouseOver: ( evt, targetProps, ) => { return [ { mutation: () => Object.assign( targetProps, { active: true }, ), }, ]; }, onMouseDown: ( evt, targetProps, ) => { return [ { mutation: () => Object.assign( targetProps, { dragging: true }, ), }, ]; }, onMouseMove: ( evt, targetProps, ) => { const { onPointChange, datum, scale, } = targetProps; if (targetProps.dragging) { const { x } = Selection.getSVGEventCoordinates( evt, ); const point = scale.y.invert(x); const name = datum.name; onPointChange({ name, date: point, }); return [ { mutation: () => Object.assign( targetProps, { x }, ), }, ]; } return null; }, onMouseUp: ( evt, targetProps, ) => { return [ { mutation: () => Object.assign( targetProps, { dragging: false, active: false, }, ), }, ]; }, onMouseLeave: ( evt, targetProps, ) => { return [ { mutation: () => Object.assign( targetProps, { dragging: false, active: false, }, ), }, ]; }, }, }, ]; render() { return <Point {...this.props} />; } } function App() { const [zoomDomain, setZoomDomain] = React.useState({}); const [bars, setBars] = React.useState(barData); const [points, setPoints] = React.useState(pointData); function handleZoom(domain) { setZoomDomain(domain); } function onDomainChange( domain, props, ) { const { name } = props; const newBars = bars.map((bar) => bar.name === name ? { name, range: domain } : bar, ); setBars(newBars); } function onPointChange(point) { const newPoints = points.map((p) => p.name === point.name ? point : p, ); setPoints(newPoints); } return ( <div style={containerStyle}> <VictoryChart theme={VictoryTheme.clean} horizontal {...sharedProps} containerComponent={ <VictoryZoomContainer responsive={false} allowPan={false} zoomDomain={zoomDomain} zoomDimension="y" onZoomDomainChange={ handleZoom } clipContainerComponent={ <VictoryClipContainer clipPadding={{ top: 15, bottom: 15, }} /> } /> } > <VictoryAxis style={{ axis: { stroke: "none" }, }} /> {bars.map((bar, index) => ( <VictoryAxis dependentAxis key={index} axisComponent={ <VictoryBrushLine name={bar.name} width={20} allowDraw={false} brushDomain={bar.range} onBrushDomainChange={ onDomainChange } brushStyle={{ fill: VictoryTheme .clean.palette ?.colors?.cyan, opacity: ({ active, }) => active ? 1 : 0.5, }} /> } style={{ axis: { stroke: "none" }, }} axisValue={bar.name} tickFormat={() => ""} /> ))} <VictoryScatter data={points} dataComponent={ <DraggablePoint onPointChange={ onPointChange } /> } style={{ data: { fill: VictoryTheme.clean .palette?.colors?.cyan, opacity: ({ active }) => active ? 1 : 0.5, cursor: "move", }, }} x="name" y="date" size={10} /> </VictoryChart> <VictoryChart theme={VictoryTheme.clean} horizontal {...sharedProps} padding={{ top: 30, left: 50, right: 30, bottom: 0, }} scale={{ y: "time" }} height={120} containerComponent={ <VictoryBrushContainer responsive={false} brushDomain={zoomDomain} brushDimension="y" onBrushDomainChange={ handleZoom } /> } > <VictoryAxis style={{ axis: { stroke: "none" }, }} /> <VictoryAxis dependentAxis orientation="top" style={{ axis: { stroke: "none" }, tickLabels: { fontSize: 20, }, }} tickCount={3} tickFormat={(t) => t.getFullYear() } /> <VictoryScatter data={points} size={5} style={{ data: { fill: VictoryTheme.clean .palette?.colors?.cyan, }, }} x="name" y="date" /> <VictoryBar style={{ data: { fill: VictoryTheme.clean .palette?.colors?.cyan, }, }} data={bars} x="name" y={(d) => d.range[0]} y0={(d) => d.range[1]} /> </VictoryChart> </div> ); } render(<App />);