diff --git a/package-lock.json b/package-lock.json index c0c39b1..48a8eb2 100755 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,6 @@ "dependencies": { "@microsoft/signalr": "^6.0.5", "antd": "^4.20.7", - "chart.js": "^3.8.0", - "chartjs-adapter-moment": "^1.0.0", - "chartjs-plugin-datalabels": "^2.0.0", - "chartjs-plugin-zoom": "^1.2.1", "d3": "^7.4.4", "moment": "^2.29.3", "pigeon-maps": "^0.21.0", @@ -4532,39 +4528,6 @@ "node": ">=10" } }, - "node_modules/chart.js": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.0.tgz", - "integrity": "sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg==" - }, - "node_modules/chartjs-adapter-moment": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.0.tgz", - "integrity": "sha512-PqlerEvQcc5hZLQ/NQWgBxgVQ4TRdvkW3c/t+SUEQSj78ia3hgLkf2VZ2yGJtltNbEEFyYGm+cA6XXevodYvWA==", - "peerDependencies": { - "chart.js": "^3.0.0", - "moment": "^2.10.2" - } - }, - "node_modules/chartjs-plugin-datalabels": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.0.0.tgz", - "integrity": "sha512-WBsWihphzM0Y8fmQVm89+iy99mmgejmj5/jcsYqwxSioLRL/zqJ4Scv/eXq5ZqvG3TpojlGzZLeaOaSvDm7fwA==", - "peerDependencies": { - "chart.js": "^3.0.0" - } - }, - "node_modules/chartjs-plugin-zoom": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-1.2.1.tgz", - "integrity": "sha512-2zbWvw2pljrtMLMXkKw1uxYzAne5PtjJiOZftcut4Lo3Ee8qUt95RpMKDWrZ+pBZxZKQKOD/etdU4pN2jxZUmg==", - "dependencies": { - "hammerjs": "^2.0.8" - }, - "peerDependencies": { - "chart.js": "^3.2.0" - } - }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -6458,14 +6421,6 @@ "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", "dev": true }, - "node_modules/hammerjs": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", - "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -15549,31 +15504,6 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true }, - "chart.js": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.0.tgz", - "integrity": "sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg==" - }, - "chartjs-adapter-moment": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.0.tgz", - "integrity": "sha512-PqlerEvQcc5hZLQ/NQWgBxgVQ4TRdvkW3c/t+SUEQSj78ia3hgLkf2VZ2yGJtltNbEEFyYGm+cA6XXevodYvWA==", - "requires": {} - }, - "chartjs-plugin-datalabels": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.0.0.tgz", - "integrity": "sha512-WBsWihphzM0Y8fmQVm89+iy99mmgejmj5/jcsYqwxSioLRL/zqJ4Scv/eXq5ZqvG3TpojlGzZLeaOaSvDm7fwA==", - "requires": {} - }, - "chartjs-plugin-zoom": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-1.2.1.tgz", - "integrity": "sha512-2zbWvw2pljrtMLMXkKw1uxYzAne5PtjJiOZftcut4Lo3Ee8qUt95RpMKDWrZ+pBZxZKQKOD/etdU4pN2jxZUmg==", - "requires": { - "hammerjs": "^2.0.8" - } - }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -16996,11 +16926,6 @@ "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", "dev": true }, - "hammerjs": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", - "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" - }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", diff --git a/package.json b/package.json index 4273022..39758dd 100755 --- a/package.json +++ b/package.json @@ -5,10 +5,6 @@ "dependencies": { "@microsoft/signalr": "^6.0.5", "antd": "^4.20.7", - "chart.js": "^3.8.0", - "chartjs-adapter-moment": "^1.0.0", - "chartjs-plugin-datalabels": "^2.0.0", - "chartjs-plugin-zoom": "^1.2.1", "d3": "^7.4.4", "moment": "^2.29.3", "pigeon-maps": "^0.21.0", diff --git a/src/components/charts/ChartTimeBase.tsx b/src/components/charts/ChartTimeBase.tsx deleted file mode 100755 index 5960a3d..0000000 --- a/src/components/charts/ChartTimeBase.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import { memo, useEffect, useRef, useState } from 'react' -import { - Chart, - TimeScale, - LinearScale, - Legend, - LineController, - PointElement, - LineElement, - ChartData, - ChartOptions, - ChartType, - ChartDataset, - Tooltip -} from 'chart.js' -import 'chartjs-adapter-moment' -import ChartDataLabels from 'chartjs-plugin-datalabels' -import zoomPlugin from 'chartjs-plugin-zoom' - -Chart.register( - TimeScale, - LinearScale, - LineController, - LineElement, - PointElement, - Legend, - ChartDataLabels, - zoomPlugin, - Tooltip, -) - -const defaultOptions: ChartOptions = { - responsive: true, - aspectRatio: 0.45, - animation: false, - events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], - scales: { - y: { - type: 'time', - reverse: true, - time: { - stepSize: 20, - displayFormats: { - millisecond: 'HH:mm:ss.SSS', - second: 'HH:mm:ss', - minute: 'HH:mm:ss', - hour: 'DD HH:mm:ss', - day: 'MM.DD HH:mm', - week: 'yy.MM.DD HH:mm', - month: 'yyyy.MM.DD', - quarter: 'yyyy.MM.DD', - year: 'yyyy.MM', - }, - }, - grid: { - drawTicks: false, - }, - ticks: { - z: 1, - display : false, - textStrokeColor : '#ffff', - textStrokeWidth : 2, - color:'#000', - } - }, - - x: { - type: 'linear', - position: 'top' - } - }, - parsing: false, - elements: { - point: { - radius: 0, - hoverRadius: 5, - }, - }, - plugins: { - legend: { - display: false, - }, - datalabels: { - display: false, - }, - zoom: { - zoom: { - wheel: { - enabled: true, - modifierKey: 'alt', - }, - pinch: { - enabled: true - }, - mode: 'x', - } - }, - tooltip: { - enabled: true, - callbacks: { - label: (tooltipItem: any) => tooltipItem.yLabel - } - }, - } -} - -export type ChartTimeDataPoint = { - x: number - label: number - y: Date -} - -export type ChartTimeDataset = ChartDataset -export type ChartTimeData = ChartData - -export type ChartTimeDataParams = { - data: ChartTimeData - yStart?: Date - yInterval?: number - displayLabels?: boolean -} - -export type ChartTimeBaseProps = { - dataParams: ChartTimeDataParams - options?: ChartOptions -} - -export type TimeParams = { - unit: string - stepSize: number -} - -const linesPerInterval = 32 - -const intervals = { - millisecond: 0.001, - second: 1, - minute: 60, - hour: 60 * 60, - day: 60 * 60 * 24, - week: 60 * 60 * 24 * 7, - month: 60 * 60 * 24 * 30, - quarter: 60 * 60 * 24 * 91, - year: 60 * 60 * 24 * 365.25 -} - -type IntervalType = keyof typeof intervals - -export const timeUnitByInterval = (intervalSec: number): IntervalType => { - if(intervalSec <= intervals.minute) - return 'millisecond' - - if(intervalSec <= 32 * intervals.minute) - return 'second' - - if(intervalSec <= 32 * intervals.hour) - return 'minute' - - if(intervalSec <= 32 * intervals.day / 2) - return 'hour' - - if(intervalSec <= 32 * intervals.day) - return 'day' - - if(intervalSec <= 32 * intervals.week) - return 'week' - - if(intervalSec <= 32 * intervals.year / 12) - return 'month' - - if(intervalSec <= 32 * intervals.year / 3) - return 'quarter' - - return 'year' -} - -export const timeParamsByInterval = (intervalSec: number): TimeParams => { - const unit = timeUnitByInterval(intervalSec) - const stepSize = Math.max(1, Math.round(intervalSec / intervals[unit] / linesPerInterval)) - return { unit, stepSize } -} - -export const ChartTimeBase = memo(({ options, dataParams }) => { - const chartRef = useRef(null) - const [chart, setChart] = useState() - - useEffect(() => { - const chartOptions: ChartOptions = {} - Object.assign(chartOptions, defaultOptions, options) - - const newChart = new Chart(chartRef.current ?? '', { - type: 'line', - plugins: [ChartDataLabels], - options: chartOptions, - data: { datasets: [] } - }) - - setChart(newChart) - return () => newChart?.destroy() - }, [options]) - - useEffect(() => { - if (!chart) return - chart.data = dataParams.data - if(dataParams.yStart){ - const interval = Number(dataParams.yInterval ?? 600_000) - const { unit, stepSize } = timeParamsByInterval(Number(interval / 1000)) - - if(chart.options.scales?.y){ - chart.options.scales.y.max = +dataParams.yStart + interval - chart.options.scales.y.min = +dataParams.yStart - chart.options.scales.y.ticks.display = dataParams.displayLabels ?? true - chart.options.scales.y.time.unit = unit - chart.options.scales.y.time.stepSize = stepSize - } - } - - chart.update(0) - }, [chart, dataParams]) - - return() -}) diff --git a/src/components/charts/Column.tsx b/src/components/charts/Column.tsx deleted file mode 100755 index 9945d06..0000000 --- a/src/components/charts/Column.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { ChartOptions, Scriptable, ScriptableContext } from 'chart.js' -import React, { useState, useEffect } from 'react' - -import { makeDateSorter } from '@components/Table' -import { - ChartTimeBase, - ChartTimeData, - ChartTimeDataset, - ChartTimeDataPoint, - ChartTimeDataParams -} from './ChartTimeBase' - -export type ColumnLineConfig = { - label?: string - units?: string - xAccessorName: string - yAccessorName: string - color?: string - showLine?: boolean - isShape?: boolean - xConstValue?: number | string - dash?: Array - borderColor?: string - backgroundColor?: string - borderWidth?: Scriptable> - showDatalabels?: boolean - fill?: string -} -export type ColumnPostParsing = (data: ChartTimeDataParams) => void -export type ColumnData = { [accessors: string]: any } -export type ColumnAdditionalData = (point: ColumnData, cfg: ColumnLineConfig) => object -export type ColumnProps = { - postParsing?: ColumnPostParsing - additionalPointData?: ColumnAdditionalData - interval?: number - yDisplay?: boolean - yStart?: Date - lineGroup: ColumnLineConfig[] - data: ColumnData[] -} - - -const chartPluginsOptions: ChartOptions = { - plugins: { - datalabels: { - backgroundColor: 'transparent', - borderRadius: 4, - color: '#000B', - display: context => !!context.dataset.label?.endsWith(' ') && 'auto', - formatter: value => `${value.y.toLocaleTimeString()} ${value.label.toPrecision(4)}`, - padding: 6, - align: 'left', - anchor: 'center', - clip: true - }, - legend: { display: false }, - tooltip: { enabled: true } - } -} - -const GetRandomColor = () => '#' + Math.floor(Math.random() * (16**6 - 1)).toString(16) - -export const GetOrCreateDatasetByLineConfig = (data: ChartTimeData, lineConfig: ColumnLineConfig): ChartTimeDataset => { - let dataset = data?.datasets.find(d => d.label === lineConfig.label) - if (!dataset) { - const color = lineConfig.borderColor - ?? lineConfig.backgroundColor - ?? lineConfig.color - ?? GetRandomColor() - - dataset = { - label: lineConfig.label?.trimEnd() + (lineConfig.showDatalabels ? ' ' : ''), - data: [], - backgroundColor: lineConfig.backgroundColor ?? color, - borderColor: lineConfig.borderColor ?? color, - borderWidth: lineConfig.borderWidth ?? 1, - borderDash: lineConfig.dash ?? [], - showLine: lineConfig.showLine ?? !lineConfig.isShape, - fill: lineConfig.fill ?? (lineConfig.isShape ? 'shape' : 'none'), - } - - data.datasets.push(dataset) - } - return dataset -} - -export const Column: React.NamedExoticComponent = React.memo(({ lineGroup, data, postParsing, additionalPointData, interval, yDisplay, yStart }) => { - const [dataParams, setDataParams] = useState({data: {datasets:[]}, yStart, }) - - useEffect(()=>{ - if((lineGroup.length === 0) || (data.length === 0)) return - - setDataParams((preDataParams) => { - lineGroup.forEach(lineCfg => { - const dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg) - let points: ChartTimeDataPoint[] = data.map(dataItem => ({ - x: lineCfg.xConstValue ?? dataItem[lineCfg.xAccessorName], - label: dataItem[lineCfg.xAccessorName], - y: new Date(dataItem[lineCfg.yAccessorName]), - ...additionalPointData?.(dataItem, lineCfg) - })) - - points = points.filter(point => (point.x ?? null) !== null && (point.y ?? null) !== null) - - if(points?.length > 2) - points.sort(makeDateSorter('y')) - - dataset.data = points - }) - - preDataParams.yStart = yStart - preDataParams.yInterval = interval - preDataParams.displayLabels = yDisplay - - postParsing?.(preDataParams) - return {...preDataParams} - }) - - }, [data, lineGroup, interval, yDisplay, yStart, postParsing, additionalPointData]) - - return -})