diff --git a/common/components/charts/SingleDayLineChart.tsx b/common/components/charts/SingleDayLineChart.tsx index 34eb2966..e371cb4b 100644 --- a/common/components/charts/SingleDayLineChart.tsx +++ b/common/components/charts/SingleDayLineChart.tsx @@ -4,6 +4,7 @@ import 'chartjs-adapter-date-fns'; import { enUS } from 'date-fns/locale'; import React, { useMemo, useRef } from 'react'; import ChartjsPluginWatermark from 'chartjs-plugin-watermark'; +import ChartTrendline from 'chartjs-plugin-trendline'; import type { DataPoint } from '../../types/dataPoints'; import { CHART_COLORS, COLORS } from '../../constants/colors'; import { useAlertStore } from '../../../modules/tripexplorer/AlertStore'; @@ -100,13 +101,39 @@ export const SingleDayLineChart: React.FC = ({ const multiplier = units === 'Minutes' ? 1 / 60 : 1; const benchmarkDataFormatted = benchmarkData - .map((datapoint) => (datapoint ? (datapoint * multiplier).toFixed(2) : null)) + .map((datapoint) => (datapoint ? parseFloat((datapoint * multiplier).toFixed(2)) : null)) .filter((datapoint) => datapoint !== null); const convertedData = data.map((datapoint) => - ((datapoint[metricField] as number) * multiplier).toFixed(2) + parseFloat(((datapoint[metricField] as number) * multiplier).toFixed(2)) ); + function calculateTrendSlope(data) { + if (!data || data.length < 2) { + return 0; // Not enough data points to calculate a trend + } + + let sumX = 0; + let sumY = 0; + let sumXY = 0; + let sumX2 = 0; + const n = data.length; + + for (let i = 0; i < n; i++) { + const x = i + 1; // Assuming x values are sequential + const y = data[i]; + + sumX += x; + sumY += y; + sumXY += x * y; + sumX2 += x * x; + } + + const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); + + return Math.ceil(slope * 100) / 100; + } + return ( @@ -138,6 +165,12 @@ export const SingleDayLineChart: React.FC = ({ pointRadius: 3, pointHitRadius: 10, data: convertedData, + trendlineLinear: { + colorMin: 'grey', + colorMax: 'grey', + lineStyle: 'dashed', + width: 1, + }, }, { label: `Benchmark MBTA`, @@ -246,6 +279,7 @@ export const SingleDayLineChart: React.FC = ({ }, }, ChartjsPluginWatermark, + ChartTrendline, ]} /> diff --git a/package-lock.json b/package-lock.json index a1d3abec..78a695b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "chartjs-color": "^2.4.1", "chartjs-plugin-annotation": "^3.1.0", "chartjs-plugin-datalabels": "^2.2.0", + "chartjs-plugin-trendline": "^2.1.6", "chartjs-plugin-watermark": "^2.0.2", "classnames": "^2.5.1", "color": "^4.2.3", @@ -66,6 +67,7 @@ "@storybook/testing-library": "^0.2.2", "@tailwindcss/forms": "^0.5.10", "@types/bezier-js": "^4.1.3", + "@types/chartjs-plugin-trendline": "^1.0.4", "@types/color": "^4.2.0", "@types/lodash": "^4.17.14", "@types/node": "^20.12.7", @@ -4747,6 +4749,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/chartjs-plugin-trendline": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/chartjs-plugin-trendline/-/chartjs-plugin-trendline-1.0.4.tgz", + "integrity": "sha512-s94at2TCSMfqYoYejp4nzX+H/exAs90aQ8I55sERqQWlVaV4eoMajVhajL6b6uwd9Z9ycJngtAfqEQF2JB6fkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chart.js": "^3.7.1" + } + }, + "node_modules/@types/chartjs-plugin-trendline/node_modules/chart.js": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", + "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/color": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/color/-/color-4.2.0.tgz", @@ -7090,6 +7109,12 @@ "chart.js": ">=3.0.0" } }, + "node_modules/chartjs-plugin-trendline": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chartjs-plugin-trendline/-/chartjs-plugin-trendline-2.1.6.tgz", + "integrity": "sha512-73lpSv87RcIeu0so4ndEE48Xf08Q4scz079tSgYdfeGuIce4JKcSE64oluhz/j6NpBr50Z7PPwolGJ0cLtknRw==", + "license": "MIT" + }, "node_modules/chartjs-plugin-watermark": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/chartjs-plugin-watermark/-/chartjs-plugin-watermark-2.0.2.tgz", diff --git a/package.json b/package.json index e1ef48df..81b43f5a 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "chartjs-color": "^2.4.1", "chartjs-plugin-annotation": "^3.1.0", "chartjs-plugin-datalabels": "^2.2.0", + "chartjs-plugin-trendline": "^2.1.6", "chartjs-plugin-watermark": "^2.0.2", "classnames": "^2.5.1", "color": "^4.2.3", @@ -95,6 +96,7 @@ "@storybook/testing-library": "^0.2.2", "@tailwindcss/forms": "^0.5.10", "@types/bezier-js": "^4.1.3", + "@types/chartjs-plugin-trendline": "^1.0.4", "@types/color": "^4.2.0", "@types/lodash": "^4.17.14", "@types/node": "^20.12.7", @@ -133,4 +135,4 @@ "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0" }, "proxy": "http://localhost:5000" -} \ No newline at end of file +} diff --git a/pages/_app.tsx b/pages/_app.tsx index c4a243b9..f3395465 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -22,6 +22,7 @@ import { config } from '@fortawesome/fontawesome-svg-core'; import '@fortawesome/fontawesome-svg-core/styles.css'; import { GCScript } from 'next-goatcounter'; import ChartDataLabels from 'chartjs-plugin-datalabels'; +import ChartTrendline from 'chartjs-plugin-trendline'; import '../styles/dashboard.css'; import '../styles/globals.css'; @@ -45,6 +46,7 @@ ChartJS.register( LineElement, Annotation, ChartDataLabels, + ChartTrendline, Filler, Title, Tooltip,