mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 01:20:24 +03:00
Merge pull request #631 from acelaya-forks/feature/react-chartjs-4
Feature/react chartjs 4
This commit is contained in:
commit
57a2a03469
7 changed files with 38 additions and 28 deletions
|
@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
|
||||
### Changed
|
||||
* [#616](https://github.com/shlinkio/shlink-web-client/pull/616) Updated to React 18.
|
||||
* [#595](https://github.com/shlinkio/shlink-web-client/pull/595) Updated to react-chartjs-2 v4.1.0.
|
||||
* [#594](https://github.com/shlinkio/shlink-web-client/pull/594) Updated to a new coding standard.
|
||||
* [#603](https://github.com/shlinkio/shlink-web-client/pull/603) Migrated to new and maintained dependencies to parse CSV<->JSON.
|
||||
* [#619](https://github.com/shlinkio/shlink-web-client/pull/619) Introduced react testing library, to progressively replace enzyme.
|
||||
|
|
|
@ -26,13 +26,14 @@ module.exports = {
|
|||
},
|
||||
transformIgnorePatterns: [
|
||||
'<rootDir>/.stryker-tmp',
|
||||
'/node_modules\\/(?!react-leaflet)\.(js|jsx|ts|tsx)$',
|
||||
'node_modules\/(?!(\@react-leaflet|react-leaflet|leaflet|react-chartjs-2)\/)',
|
||||
'^.+\\.module\\.scss$',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^.+\\.module\\.scss$': 'identity-obj-proxy',
|
||||
// Reactstrap module resolution does not work in jest for some reason. Manually mapping it solves the problem
|
||||
'reactstrap': '<rootDir>/node_modules/reactstrap/dist/reactstrap.umd.js',
|
||||
'react-chartjs-2': '<rootDir>/node_modules/react-chartjs-2/dist/index.js',
|
||||
},
|
||||
moduleFileExtensions: ['js', 'ts', 'tsx', 'json'],
|
||||
};
|
||||
|
|
16
package-lock.json
generated
16
package-lock.json
generated
|
@ -27,7 +27,7 @@
|
|||
"qs": "^6.9.6",
|
||||
"ramda": "^0.27.2",
|
||||
"react": "^18.1.0",
|
||||
"react-chartjs-2": "^3.3.0",
|
||||
"react-chartjs-2": "^4.1.0",
|
||||
"react-colorful": "^5.5.1",
|
||||
"react-copy-to-clipboard": "^5.0.4",
|
||||
"react-datepicker": "^4.7.0",
|
||||
|
@ -19110,12 +19110,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-chartjs-2": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-3.3.0.tgz",
|
||||
"integrity": "sha512-4Mt0SR2aiUbWi/4762odRBYSnbNKSs4HWc0o3IW43py5bMfmfpeZU95w6mbvtuLZH/M3GsPJMU8DvDc+5U9blQ==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.1.0.tgz",
|
||||
"integrity": "sha512-AsUihxEp8Jm1oBhbEovE+w50m9PVNhz1sfwEIT4hZduRC0m14gHWHd0cUaxkFDb8HNkdMIGzsNlmVqKiOpU74g==",
|
||||
"peerDependencies": {
|
||||
"chart.js": "^3.5.0",
|
||||
"react": "^16.8.0 || ^17.0.0"
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-colorful": {
|
||||
|
@ -41617,9 +41617,9 @@
|
|||
}
|
||||
},
|
||||
"react-chartjs-2": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-3.3.0.tgz",
|
||||
"integrity": "sha512-4Mt0SR2aiUbWi/4762odRBYSnbNKSs4HWc0o3IW43py5bMfmfpeZU95w6mbvtuLZH/M3GsPJMU8DvDc+5U9blQ==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.1.0.tgz",
|
||||
"integrity": "sha512-AsUihxEp8Jm1oBhbEovE+w50m9PVNhz1sfwEIT4hZduRC0m14gHWHd0cUaxkFDb8HNkdMIGzsNlmVqKiOpU74g==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-colorful": {
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"qs": "^6.9.6",
|
||||
"ramda": "^0.27.2",
|
||||
"react": "^18.1.0",
|
||||
"react-chartjs-2": "^3.3.0",
|
||||
"react-chartjs-2": "^4.1.0",
|
||||
"react-colorful": "^5.5.1",
|
||||
"react-copy-to-clipboard": "^5.0.4",
|
||||
"react-datepicker": "^4.7.0",
|
||||
|
|
|
@ -6,6 +6,7 @@ import { container } from './container';
|
|||
import { store } from './container/store';
|
||||
import { fixLeafletIcons } from './utils/helpers/leaflet';
|
||||
import { register as registerServiceWorker } from './serviceWorkerRegistration';
|
||||
import 'chart.js/auto'; // TODO Import specific ones to reduce bundle size https://react-chartjs-2.js.org/docs/migration-to-v4/#tree-shaking
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import './index.scss';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { FC } from 'react';
|
||||
import { ChartData, ChartDataset, ChartOptions } from 'chart.js';
|
||||
import { FC, MutableRefObject, useRef } from 'react';
|
||||
import { ChartData, ChartDataset, ChartOptions, InteractionItem } from 'chart.js';
|
||||
import { keys, values } from 'ramda';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import { Bar, getElementAtEvent } from 'react-chartjs-2';
|
||||
import { fillTheGaps } from '../../utils/helpers/visits';
|
||||
import { pointerOnHover, renderChartLabel } from '../../utils/helpers/charts';
|
||||
import { prettify } from '../../utils/helpers/numbers';
|
||||
|
@ -57,8 +57,7 @@ const generateChartData = (
|
|||
datasets: generateChartDatasets(data, highlightedData, highlightedLabel),
|
||||
});
|
||||
|
||||
type ClickedCharts = [{ index: number }] | [];
|
||||
const chartElementAtEvent = (labels: string[], onClick?: (label: string) => void) => ([chart]: ClickedCharts) => {
|
||||
const chartElementAtEvent = (labels: string[], [chart]: InteractionItem[], onClick?: (label: string) => void) => {
|
||||
if (!onClick || !chart) {
|
||||
return;
|
||||
}
|
||||
|
@ -80,6 +79,8 @@ export const HorizontalBarChart: FC<HorizontalBarChartProps> = (
|
|||
}, { ...stats }),
|
||||
);
|
||||
const highlightedData = fillTheGaps(highlightedStats ?? {}, labels);
|
||||
const refWithStats = useRef(null);
|
||||
const refWithoutStats = useRef(null);
|
||||
|
||||
const options: ChartOptions = {
|
||||
plugins: {
|
||||
|
@ -110,13 +111,14 @@ export const HorizontalBarChart: FC<HorizontalBarChartProps> = (
|
|||
const height = determineHeight(labels);
|
||||
|
||||
// Provide a key based on the height, to force re-render every time the dataset changes (example, due to pagination)
|
||||
const renderChartComponent = (customKey: string) => (
|
||||
const renderChartComponent = (customKey: string, theRef: MutableRefObject<any>) => (
|
||||
<Bar
|
||||
ref={theRef}
|
||||
key={`${height}_${customKey}`}
|
||||
data={chartData as any}
|
||||
options={options as any}
|
||||
height={height}
|
||||
getElementAtEvent={chartElementAtEvent(labels, onClick) as any}
|
||||
onClick={(e) => chartElementAtEvent(labels, getElementAtEvent(theRef.current, e), onClick)}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -124,8 +126,8 @@ export const HorizontalBarChart: FC<HorizontalBarChartProps> = (
|
|||
<>
|
||||
{/* It's VERY IMPORTANT to render two different components here, as one has 1 dataset and the other has 2 */}
|
||||
{/* Using the same component causes a crash when switching from 1 to 2 datasets, and then back to 1 dataset */}
|
||||
{highlightedStats !== undefined && renderChartComponent('with_stats')}
|
||||
{highlightedStats === undefined && renderChartComponent('without_stats')}
|
||||
{highlightedStats !== undefined && renderChartComponent('with_stats', refWithStats)}
|
||||
{highlightedStats === undefined && renderChartComponent('without_stats', refWithoutStats)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useMemo } from 'react';
|
||||
import { useState, useMemo, MutableRefObject, useRef } from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
|
@ -8,7 +8,7 @@ import {
|
|||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from 'reactstrap';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import { getElementAtEvent, Line } from 'react-chartjs-2';
|
||||
import { always, cond, countBy, reverse } from 'ramda';
|
||||
import {
|
||||
add,
|
||||
|
@ -21,7 +21,7 @@ import {
|
|||
startOfISOWeek,
|
||||
endOfISOWeek,
|
||||
} from 'date-fns';
|
||||
import { ChartData, ChartDataset, ChartOptions } from 'chart.js';
|
||||
import { ChartData, ChartDataset, ChartOptions, InteractionItem } from 'chart.js';
|
||||
import { NormalizedVisit, Stats } from '../types';
|
||||
import { fillTheGaps } from '../../utils/helpers/visits';
|
||||
import { useToggle } from '../../utils/helpers/hooks';
|
||||
|
@ -148,8 +148,9 @@ let selectedLabel: string | null = null;
|
|||
const chartElementAtEvent = (
|
||||
labels: string[],
|
||||
datasetsByPoint: Record<string, NormalizedVisit[]>,
|
||||
[chart]: InteractionItem[],
|
||||
setSelectedVisits?: (visits: NormalizedVisit[]) => void,
|
||||
) => ([chart]: [{ index: number }]) => {
|
||||
) => {
|
||||
if (!setSelectedVisits || !chart) {
|
||||
return;
|
||||
}
|
||||
|
@ -160,7 +161,7 @@ const chartElementAtEvent = (
|
|||
setSelectedVisits([]);
|
||||
selectedLabel = null;
|
||||
} else {
|
||||
setSelectedVisits(labels[index] ? datasetsByPoint[labels[index]] : []);
|
||||
setSelectedVisits(labels[index] && datasetsByPoint[labels[index]] ? datasetsByPoint[labels[index]] : []);
|
||||
selectedLabel = labels[index] ?? null;
|
||||
}
|
||||
};
|
||||
|
@ -172,6 +173,8 @@ const LineChartCard = (
|
|||
visits.length > 0 ? determineInitialStep(visits[visits.length - 1].date) : 'monthly',
|
||||
);
|
||||
const [skipNoVisits, toggleSkipNoVisits] = useToggle(true);
|
||||
const refWithHighlightedVisits = useRef(null);
|
||||
const refWithoutHighlightedVisits = useRef(null);
|
||||
|
||||
const datasetsByPoint = useMemo(() => visitsToDatasetGroups(step, visits), [step, visits]);
|
||||
const groupedVisitsWithGaps = useMemo(() => groupVisitsByStep(step, reverse(visits)), [step, visits]);
|
||||
|
@ -220,11 +223,13 @@ const LineChartCard = (
|
|||
},
|
||||
onHover: pointerOnHover,
|
||||
};
|
||||
const renderLineChart = () => (
|
||||
const renderLineChart = (theRef: MutableRefObject<any>) => (
|
||||
<Line
|
||||
ref={theRef}
|
||||
data={generateChartData() as any}
|
||||
options={options as any}
|
||||
getElementAtEvent={chartElementAtEvent(labels, datasetsByPoint, setSelectedVisits) as any}
|
||||
onClick={(e) =>
|
||||
chartElementAtEvent(labels, datasetsByPoint, getElementAtEvent(theRef.current, e), setSelectedVisits)}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -255,8 +260,8 @@ const LineChartCard = (
|
|||
<CardBody className="line-chart-card__body">
|
||||
{/* It's VERY IMPORTANT to render two different components here, as one has 1 dataset and the other has 2 */}
|
||||
{/* Using the same component causes a crash when switching from 1 to 2 datasets, and then back to 1 dataset */}
|
||||
{highlightedVisits.length > 0 && renderLineChart()}
|
||||
{highlightedVisits.length === 0 && renderLineChart()}
|
||||
{highlightedVisits.length > 0 && renderLineChart(refWithHighlightedVisits)}
|
||||
{highlightedVisits.length === 0 && renderLineChart(refWithoutHighlightedVisits)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue