diff --git a/package-lock.json b/package-lock.json index 9e82ac232..1818f79a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "assert": "^2.1.0", "buffer": "^6.0.3", "chart.js": "^4.3.0", + "chartjs-plugin-zoom": "^2.2.0", "crypto-browserify": "^3.12.0", "dayjs": "^1.11.7", "eslint-plugin-jest-formatting": "^3.1.0", @@ -4583,6 +4584,12 @@ "@types/node": "*" } }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", + "license": "MIT" + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -6417,6 +6424,19 @@ "pnpm": ">=7" } }, + "node_modules/chartjs-plugin-zoom": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-2.2.0.tgz", + "integrity": "sha512-in6kcdiTlP6npIVLMd4zXZ08PDUXC52gZ4FAy5oyjk1zX3gKarXMAof7B9eFiisf9WOC3bh2saHg+J5WtLXZeA==", + "license": "MIT", + "dependencies": { + "@types/hammerjs": "^2.0.45", + "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", @@ -9157,6 +9177,15 @@ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, + "node_modules/hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", + "license": "MIT", + "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", @@ -20933,6 +20962,11 @@ "@types/node": "*" } }, + "@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==" + }, "@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -22372,6 +22406,15 @@ "@kurkle/color": "^0.3.0" } }, + "chartjs-plugin-zoom": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-2.2.0.tgz", + "integrity": "sha512-in6kcdiTlP6npIVLMd4zXZ08PDUXC52gZ4FAy5oyjk1zX3gKarXMAof7B9eFiisf9WOC3bh2saHg+J5WtLXZeA==", + "requires": { + "@types/hammerjs": "^2.0.45", + "hammerjs": "^2.0.8" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -24413,6 +24456,11 @@ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==" + }, "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 a8b00d6e1..46c373692 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "assert": "^2.1.0", "buffer": "^6.0.3", "chart.js": "^4.3.0", + "chartjs-plugin-zoom": "^2.2.0", "crypto-browserify": "^3.12.0", "dayjs": "^1.11.7", "eslint-plugin-jest-formatting": "^3.1.0", diff --git a/src/components/CompareResults/CommonGraph.tsx b/src/components/CompareResults/CommonGraph.tsx index 71d8c28f5..abe0fd0ed 100644 --- a/src/components/CompareResults/CommonGraph.tsx +++ b/src/components/CompareResults/CommonGraph.tsx @@ -1,59 +1,112 @@ -import { Chart as ChartJS, LineElement, LinearScale } from 'chart.js'; +import { + Chart as ChartJS, + LineElement, + LinearScale, + ScriptableContext, +} from 'chart.js'; import 'chart.js/auto'; +import ZoomPlugin from 'chartjs-plugin-zoom'; import * as kde from 'fast-kde'; import { Line } from 'react-chartjs-2'; -import { style } from 'typestyle'; -import { Spacing } from '../../styles'; -import { MeasurementUnit } from '../../types/types'; - -ChartJS.register(LinearScale, LineElement); - -const styles = { - container: style({ - display: 'flex', - marginBottom: Spacing.Medium, - width: '100%', - }), -}; +ChartJS.register(LinearScale, LineElement, ZoomPlugin); function CommonGraph({ - baseRevisionRuns, - newRevisionRuns, + baseValues, + newValues, min, max, + unit, }: CommonGraphProps) { - const scaleUnit = - baseRevisionRuns.measurementUnit || newRevisionRuns.measurementUnit; + ///////////////// START SHOW VALUES //////////////////////// + const baseValuesData = baseValues.map((v) => { + return { x: v, y: 'Base' }; + }); + const newValuesData = newValues.map((v) => { + return { x: v, y: 'New' }; + }); + + const allValuesData = [...baseValuesData, ...newValuesData]; + + //////////////////// START FAST KDE //////////////////////// + // Arbitrary value that seems to work OK. + // In the future we'll want to compute a better value, see + // https://bugzilla.mozilla.org/show_bug.cgi?id=1901248 for some ideas. + const bandwidth = (max - min) / 15; + const baseRunsDensity = Array.from( + kde.density1d(baseValues, { + bandwidth, + extent: [min, max], + }), + ); + const newRunsDensity = Array.from( + kde.density1d(newValues, { + bandwidth, + extent: [min, max], + }), + ); + //////////////////// END FAST KDE //////////////////////// const options = { + responsive: true, + maintainAspectRatio: false, plugins: { legend: { - align: 'start' as const, - position: 'bottom' as const, + display: false, }, title: { align: 'start' as const, display: true, text: 'Runs Density Distribution', }, + zoom: { + zoom: { + mode: 'x', + drag: { + enabled: true, + borderWidth: 1, + backgroundColor: 'rgba(225,225,225,0.5)', + }, + wheel: { + enabled: true, + speed: 0.2, + }, + pinch: { + enabled: true, + }, + }, + pan: { + enabled: true, + mode: 'x', + modifierKey: 'ctrl', + }, + limits: { + x: { min: 'original', max: 'original', minRange: bandwidth }, + }, + }, }, - responsive: true, scales: { x: { + type: 'linear' as const, + suggestedMin: min, + suggestedMax: max, grid: { display: false, offset: false, }, - type: 'linear' as const, title: { align: 'end' as const, display: true, - text: scaleUnit || '', + text: `${unit} →`, }, }, - y: { + yKde: { + type: 'linear', // Linear scale + stack: 'y', // yKde and yValues are part of the same stack + stackWeight: 3, // Higher stack weight means it takes more space + weight: 2, // Higher weight means it's on top in the stack beginAtZero: true, + grace: '3%', // Make sure there's some more space at the top grid: { drawBorder: false, display: false, @@ -63,10 +116,16 @@ function CommonGraph({ beginAtZero: true, display: true, }, - title: { - align: 'end' as const, - display: true, - text: 'Run Density' as const, + }, + yValues: { + type: 'category', + stack: 'y', + stackWeight: 1, // Lower stack weight means it takes less space + weight: 1, // Lower weight means it's lower in the stack + labels: ['Base', 'New'], + offset: true, // This gives some space around the graph + ticks: { + autoSkip: false, }, }, }, @@ -88,74 +147,46 @@ function CommonGraph({ }, }; - //////////////////// START FAST KDE //////////////////////// - // Arbitrary value that seems to work OK. - // In the future we'll want to compute a better value, see - // https://bugzilla.mozilla.org/show_bug.cgi?id=1901248 for some ideas. - if (max === min) { - min = max - 15; - max = max + 15; - } - const bandwidth = (max - min) / 15; - const baseRunsDensity = Array.from( - kde.density1d(baseRevisionRuns.values, { - bandwidth, - extent: [min - bandwidth, max + bandwidth], - }), - ); - const newRunsDensity = Array.from( - kde.density1d(newRevisionRuns.values, { - bandwidth, - extent: [min - bandwidth, max + bandwidth], - }), - ); - - //////////////////// END FAST KDE //////////////////////// - const data = { datasets: [ { + yAxisID: 'yKde', label: 'Base', data: baseRunsDensity, fill: false, borderColor: 'rgba(144, 89, 255, 1)', }, { + yAxisID: 'yKde', label: 'New', data: newRunsDensity, fill: false, borderColor: 'rgba(0, 135, 135, 1)', }, + { + yAxisID: 'yValues', + type: 'bubble', + pointStyle: 'triangle', + pointRadius: 7, + data: allValuesData, + backgroundColor: (context: ScriptableContext<'bubble'>) => + (context.raw as { y: 'Base' | 'New' }).y === 'Base' + ? 'rgba(144, 89, 255, 0.6)' + : 'rgba(0, 135, 135, 0.6)', + }, ], }; - return ( -