@neo4j-ndl/react-charts
Version:
React implementation of charts from Neo4j Design System
149 lines • 6.6 kB
JavaScript
;
/**
*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.useLegendSelection = useLegendSelection;
exports.useLegendHoverHighlight = useLegendHoverHighlight;
const echarts_1 = require("echarts");
const react_1 = require("react");
const legend_utils_1 = require("../utils/legend-utils");
const threshold_1 = require("../utils/threshold");
const use_chart_refs_1 = require("./use-chart-refs");
const LEGEND_HOVER_HIGHLIGHT_DELAY_MS = 80;
const LEGEND_SELECTION_EVENT_TYPES = [
'legendselectchanged',
'legendselectall',
'legendselected',
'legendunselected',
];
const getAllSeriesSelected = (series) => Object.fromEntries(series.map((s) => { var _a; return [(_a = s.name) !== null && _a !== void 0 ? _a : '', true]; }));
const hasSelectionForCurrentSeries = (selection, series) => {
const selectedNames = Object.keys(selection);
return (selectedNames.length === series.length &&
series.every((s) => { var _a; return Object.prototype.hasOwnProperty.call(selection, (_a = s.name) !== null && _a !== void 0 ? _a : ''); }));
};
const filterThresholdLineSelection = (selected) => Object.fromEntries(Object.entries(selected).filter(([key]) => { var _a; return !((_a = (0, threshold_1.isThresholdLine)(key)) !== null && _a !== void 0 ? _a : false); }));
const clearTimeoutRef = (timeoutRef) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
const createLegendSelectionEventHandler = (chart, eventType, setSelectedSeries) => (params) => {
var _a;
if (typeof params !== 'object' ||
params === null ||
!('selected' in params) ||
params.selected === null) {
return;
}
if (eventType === 'legendselectchanged') {
(0, legend_utils_1.resetAllSeriesHighlight)(chart);
}
setSelectedSeries(filterThresholdLineSelection((_a = params.selected) !== null && _a !== void 0 ? _a : {}));
};
/**
* Owns React legend selection and keeps it synchronized with ECharts.
*
* Current series changes preserve existing selection by name, new series default
* to visible, removed series are dropped, and ECharts legend events update the
* React state used to render legend items.
*/
function useLegendSelection({ series }) {
const { chartEchartRef, legendSelectedRef } = (0, use_chart_refs_1.useChartRefsContext)();
const initialSelected = (0, react_1.useMemo)(() => getAllSeriesSelected(series), [series]);
const [selectedSeries, setSelectedSeries] = (0, react_1.useState)(initialSelected);
const renderedSelectedSeries = hasSelectionForCurrentSeries(selectedSeries, series)
? selectedSeries
: initialSelected;
(0, react_1.useEffect)(() => {
var _a, _b, _c;
const prev = (_a = legendSelectedRef.current) !== null && _a !== void 0 ? _a : {};
const next = {};
for (const s of series) {
const name = (_b = s.name) !== null && _b !== void 0 ? _b : '';
next[name] = (_c = prev[name]) !== null && _c !== void 0 ? _c : true;
}
setSelectedSeries(next);
}, [legendSelectedRef, series]);
(0, react_1.useEffect)(() => {
legendSelectedRef.current = selectedSeries;
}, [legendSelectedRef, selectedSeries]);
(0, react_1.useEffect)(() => {
if (chartEchartRef.current === null) {
return;
}
const chart = (0, echarts_1.getInstanceByDom)(chartEchartRef.current);
if (!chart) {
return;
}
const eventHandlers = LEGEND_SELECTION_EVENT_TYPES.map((eventType) => {
const handler = createLegendSelectionEventHandler(chart, eventType, setSelectedSeries);
chart.on(eventType, handler);
return { eventType, handler };
});
return () => {
eventHandlers.forEach(({ eventType, handler }) => {
chart.off(eventType, handler);
});
};
}, [chartEchartRef]);
return { renderedSelectedSeries };
}
/**
* Creates a delayed ECharts highlighter for legend item hover state.
*
* Legend hover moves often happen as a quick leave/enter pair between adjacent
* items. Keeping separate highlight and downplay timers lets the next hover
* cancel the previous opposite action, preventing the chart from flickering
* between emphasized and reset states. Deselected legend items are ignored so
* hidden series do not get emphasized through hover.
*/
function useLegendHoverHighlight({ selectedSeries, series, }) {
const { chartEchartRef } = (0, use_chart_refs_1.useChartRefsContext)();
const highlightTimeOut = (0, react_1.useRef)(null);
const downplayTimeOut = (0, react_1.useRef)(null);
(0, react_1.useEffect)(() => {
return () => {
clearTimeoutRef(highlightTimeOut);
clearTimeoutRef(downplayTimeOut);
};
}, []);
const toggleHighlight = (0, react_1.useCallback)((seriesToUpdate, shouldHighlight) => {
const isDeselected = seriesToUpdate.name === undefined
? false
: !selectedSeries[seriesToUpdate.name];
if (isDeselected) {
return;
}
const timeoutToClear = shouldHighlight
? downplayTimeOut
: highlightTimeOut;
const timeoutToSet = shouldHighlight ? highlightTimeOut : downplayTimeOut;
if (timeoutToClear.current) {
clearTimeout(timeoutToClear.current);
}
timeoutToSet.current = setTimeout(() => {
(0, legend_utils_1.highlightOrDownplaySeries)(chartEchartRef, series, selectedSeries, [seriesToUpdate], shouldHighlight ? 'highlight' : 'downplay');
}, LEGEND_HOVER_HIGHLIGHT_DELAY_MS);
}, [chartEchartRef, selectedSeries, series]);
return { toggleHighlight };
}
//# sourceMappingURL=use-legend-interactions.js.map