Axis - Parallel Brush
Live View
Loading...
Live Editor
const data = [ { name: "Adrien", strength: 5, intelligence: 30, speed: 500, luck: 3, }, { name: "Brice", strength: 1, intelligence: 13, speed: 550, luck: 2, }, { name: "Casey", strength: 4, intelligence: 15, speed: 80, luck: 1, }, { name: "Drew", strength: 3, intelligence: 25, speed: 600, luck: 5, }, { name: "Erin", strength: 9, intelligence: 50, speed: 350, luck: 4, }, { name: "Francis", strength: 2, intelligence: 40, speed: 200, luck: 2, }, ]; const attributes = [ "strength", "intelligence", "speed", "luck", ]; const height = 500; const width = 700; const padding = { top: 100, left: 50, right: 50, bottom: 50, }; function getMaximumValues() { // Find the maximum value for each axis. This will be used to normalize data and re-scale axis ticks return attributes.map((attribute) => { return data.reduce( (memo, datum) => { return datum[attribute] > memo ? datum[attribute] : memo; }, -Infinity, ); }); } function normalizeData(maximumValues) { // construct normalized datasets by dividing the value for each attribute by the maximum value return data?.map((datum) => ({ name: datum.name, data: attributes.map( (attribute, i) => ({ x: attribute, y: datum[attribute] / maximumValues[i], }), ), })); } function App() { const maximumValues = getMaximumValues(); const datasets = normalizeData( maximumValues, ); const [state, setState] = React.useState({ maximumValues, datasets, filters: {}, activeDatasets: [], isFiltered: false, }); function addNewFilters( domain, props, ) { const filters = state.filters || {}; const extent = domain && Math.abs(domain[1] - domain[0]); const minVal = 1 / Number.MAX_SAFE_INTEGER; filters[props.name] = extent <= minVal ? undefined : domain; return filters; } function getActiveDatasets(filters) { // Return the names from all datasets that have values within all filters const isActive = (dataset) => { return _.keys(filters).reduce( (memo, name) => { if ( !memo || !Array.isArray( filters[name], ) ) { return memo; } const point = _.find( dataset.data, (d) => d.x === name, ); return ( point && Math.max( ...filters[name], ) >= point.y && Math.min( ...filters[name], ) <= point.y ); }, true, ); }; return state.datasets .map((dataset) => { return isActive( dataset, filters, ) ? dataset.name : null; }) .filter(Boolean); } function onDomainChange( domain, props, ) { const filters = addNewFilters( domain, props, ); const isFiltered = !_.isEmpty( _.values(filters).filter(Boolean), ); const activeDatasets = isFiltered ? getActiveDatasets(filters) : state.datasets; setState((state) => ({ ...state, activeDatasets, filters, isFiltered, })); } function isActive(dataset) { // Determine whether a given dataset is active return !state.isFiltered ? true : _.includes( state.activeDatasets, dataset.name, ); } function getAxisOffset(index) { const step = (width - padding.left - padding.right) / (attributes.length - 1); return step * index + padding.left; } return ( <VictoryChart domain={{ y: [0, 1.1] }} height={height} width={width} padding={padding} theme={VictoryTheme.clean} > <VictoryAxis style={{ tickLabels: { fontSize: 20 }, axis: { stroke: "none" }, }} tickLabelComponent={ <VictoryLabel y={padding.top - 40} /> } /> {state.datasets.map((dataset) => ( <VictoryLine key={dataset.name} name={dataset.name} data={dataset.data} groupComponent={<g />} style={{ data: { opacity: isActive(dataset) ? 1 : 0.2, }, }} /> ))} {attributes.map( (attribute, index) => ( <VictoryAxis dependentAxis key={index} axisComponent={ <VictoryBrushLine name={attribute} width={20} onBrushDomainChange={ onDomainChange } /> } offsetX={getAxisOffset( index, )} style={{ tickLabels: { fontSize: 15, padding: 15, pointerEvents: "none", }, }} tickValues={[ 0.2, 0.4, 0.6, 0.8, 1, ]} tickFormat={(tick) => Math.round( tick * state.maximumValues[ index ], ) } /> ), )} </VictoryChart> ); } render(<App />);