Commit 0323c99e authored by Aron Fiechter's avatar Aron Fiechter
Browse files

Highlight bars in year range chart according to hovered site years

parent 941b7afb
Pipeline #901 canceled with stage
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setRange, setYears, selectYears } from './yearRangeSlice';
import { setRange } from './yearRangeSlice';
import ReactEcharts from 'echarts-for-react';
import { selectNodes, selectResultsById } from '../navbar/querySlice';
import { selectNodes, selectQuery, selectResultsById } from '../navbar/querySlice';
import { useDebouncedCallback } from 'use-debounce';
import config from '../../config';
import logger from '../../utils/logger';
import Container from 'react-bootstrap/Container';
import { selectHoverSite } from '../voronoi/voronoiSlice';
export default function YearRange() {
const dispatch = useDispatch();
......@@ -16,15 +17,23 @@ export default function YearRange() {
const [zoomed, setZoomed] = useState(false);
// Data from redux state
const query = useSelector(selectQuery);
const nodes = useSelector(selectNodes);
const resultsById = useSelector(selectResultsById);
const years = useSelector(selectYears);
const hoverSiteId = useSelector(selectHoverSite);
const chartRef = useRef(null);
// Reset zoom when data changes completely
useEffect(() => {
logger.debug('YearRange: resetting chart zoom');
const chart = chartRef.current.getEchartsInstance();
chart.dispatchAction({ type: 'dataZoom', start: 0, end: 100 });
}, [query]);
// Create data for chart and store it in redux
useEffect(() => {
logger.debug('YearRange: creating year counts for chart');
logger.debug('YearRange: creating data for chart');
const yearCounts = (nodes || [])
.filter((n) => n.leaf)
.map((n) => resultsById[n.name])
......@@ -36,12 +45,67 @@ export default function YearRange() {
return counts;
}, {});
const yearList = Object.keys(yearCounts).map((y) => ({ year: y, count: yearCounts[y] }));
const x = yearList.map((y) => parseInt(y.year, 10));
const y = yearList.map((y) => y.count);
// Years of node that is being hovered on Voronoi diagram
const voronoiHoverNode = resultsById[hoverSiteId] || null;
const getAllChildren = (nodeId) => {
if (nodeId === null) return [];
const children = nodes.filter((node) => node.parent === nodeId);
return [...children, ...children.flatMap((c) => getAllChildren(c.name))];
};
const voronoiHoverNodeYears = new Set([
...(voronoiHoverNode?.years || []),
...getAllChildren(hoverSiteId)
.map((c) => resultsById[c.name])
.flatMap((n) => n?.years || []),
]);
// Highlight bars in the bar chart if there is no node being hovered, or if the year corresponding
// to the bar is contained in the years of the note that is being hovered.
const highlight = (yearString) => {
return !hoverSiteId || voronoiHoverNodeYears.has(parseInt(yearString, 10));
};
// Create xAxis (categorical years) and yAxis (year counts, with highlight depending on hovered
// voronoi node)
const xAxis = yearList.map((y) => parseInt(y.year, 10));
const yAxis = yearList.map((y) => ({
value: y.count,
itemStyle: { color: highlight(y.year) ? 'rgba(40, 55, 131, 1)' : 'rgba(40, 55, 131, 0.1)' },
}));
// Set data in chart
const chart = chartRef.current.getEchartsInstance();
chart.dispatchAction({ type: 'dataZoom', start: 0, end: 100 });
debouncedDispatch(setYears({ x, y }));
}, [debouncedDispatch, resultsById, nodes]);
chart.setOption({
xAxis: {
type: 'category',
name: 'year',
data: xAxis,
silent: false,
splitLine: {
show: false,
},
splitArea: {
show: false,
},
},
yAxis: {
type: 'value',
name: 'count',
show: false,
splitArea: {
show: false,
},
},
series: [
{
type: 'bar',
data: yAxis,
large: true,
},
],
});
}, [debouncedDispatch, resultsById, nodes, hoverSiteId]);
// Handler for dataZoom event on chart
const dataZoomHandler = useCallback(
......@@ -55,7 +119,7 @@ export default function YearRange() {
[debouncedDispatch],
);
// Handler for mouseover event, sets the year range to the hovered year
// Handler for mouseover event, sets the year range to the hovered year (if not zoomed on a year)
const mouseOverHandler = useCallback(
(e) => {
if (!zoomed) {
......@@ -65,7 +129,8 @@ export default function YearRange() {
},
[debouncedDispatch, zoomed],
);
// Handler for mouseout event, resets the year range
// Handler for mouseout event, resets the year range (if not zoomed on a year)
const mouseOutHandler = useCallback(
(chart) => (e) => {
if (!zoomed) {
......@@ -77,6 +142,9 @@ export default function YearRange() {
},
[debouncedDispatch, zoomed],
);
// Handler for click event, zooms on a single year or zooms out to the complete year range,
// and sets the "zoomed" flag accordingly
const clickHandler = useCallback(
(chart) => (e) => {
logger.debug('Click on chart');
......@@ -93,7 +161,7 @@ export default function YearRange() {
[zoomed],
);
// Setup chart range selection handler
// Setup chart event handlers
useEffect(() => {
logger.debug('YearRange: registering chart handlers');
const chart = chartRef.current.getEchartsInstance();
......@@ -141,7 +209,7 @@ export default function YearRange() {
xAxis: {
type: 'category',
name: 'year',
data: years.x,
data: [],
silent: false,
splitLine: {
show: false,
......@@ -161,7 +229,7 @@ export default function YearRange() {
series: [
{
type: 'bar',
data: years.y,
data: [],
large: true,
},
],
......
......@@ -8,7 +8,6 @@ export const yearRangeSlice = createSlice({
start: config.YEARS.MIN,
end: config.YEARS.MAX,
},
years: [],
},
reducers: {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
......@@ -24,15 +23,11 @@ export const yearRangeSlice = createSlice({
end: config.YEARS.MAX,
};
},
setYears: (state, action) => {
state.years = action.payload;
},
},
});
export const { setRange, setYears, resetRange } = yearRangeSlice.actions;
export const { setRange, resetRange } = yearRangeSlice.actions;
export const selectYears = (state) => state.yearRange.years;
export const selectRange = (state) => state.yearRange.range;
export default yearRangeSlice.reducer;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment