From 888b48347d05a0592bb8f065dc0d5249bf9b7683 Mon Sep 17 00:00:00 2001 From: Trdat Mkrtchyan Date: Wed, 27 Mar 2024 13:18:37 +0400 Subject: [PATCH] fix: fixed integral calculation (#211) --- demo/examples/tooltip-with-aggregates.html | 26 +++++++++---------- src/YagrCore/defaults.ts | 1 + src/YagrCore/mixins/create-options.ts | 5 ++-- src/YagrCore/utils/axes.ts | 4 +-- src/plugins/aggregates/aggregates.ts | 3 +-- src/plugins/aggregates/utils.ts | 6 +++-- src/plugins/weekends/weekends.ts | 3 ++- tests/core/plugins.test.ts | 2 +- tests/plugins/aggregates.test.ts | 30 +++++++++++----------- 9 files changed, 42 insertions(+), 38 deletions(-) diff --git a/demo/examples/tooltip-with-aggregates.html b/demo/examples/tooltip-with-aggregates.html index 7051236..d81125b 100644 --- a/demo/examples/tooltip-with-aggregates.html +++ b/demo/examples/tooltip-with-aggregates.html @@ -44,7 +44,7 @@

Tooltip Plugin

}, timeline: new Array(100).fill().map((_, i) => i * 1000), series: [ - {data: new Array(100).fill().map((_, i) => Math.random() * 6), color: 'red'}, + {data: new Array(100).fill().map((_, i) => i > 20 && i < 30 ? Math.random() * 6 : null), color: 'red'}, {data: new Array(100).fill().map((_, i) => Math.random() * 6), color: 'green'}, ], chart: { @@ -112,23 +112,23 @@

Tooltip Plugin

return ` ${id} - ${ref.min.toFixed(1) ?? '-'} - ${ref.max.toFixed(1) ?? '-'} - ${ref.avg.toFixed(1) ?? '-'} - ${ref.sum.toFixed(1) ?? '-'} - ${ref.count.toFixed(1) ?? '-'} - ${ref.integral.toFixed(1) ?? '-'} + ${ref.min?.toFixed(1) ?? '-'} + ${ref.max?.toFixed(1) ?? '-'} + ${ref.avg?.toFixed(1) ?? '-'} + ${ref.sum?.toFixed(1) ?? '-'} + ${ref.count?.toFixed(1) ?? '-'} + ${ref.integral?.toFixed(1) ?? '-'} ${ref.last?.toFixed(1) ?? '-'} `; })} Total - ${refs.y.total.min.toFixed(1)} - ${refs.y.total.max.toFixed(1)} - ${refs.y.total.avg.toFixed(1)} - ${refs.y.total.sum.toFixed(1)} - ${refs.y.total.count.toFixed(1)} - ${refs.y.total.integral.toFixed(1)} + ${refs.y.total.min?.toFixed(1)} + ${refs.y.total.max?.toFixed(1)} + ${refs.y.total.avg?.toFixed(1)} + ${refs.y.total.sum?.toFixed(1)} + ${refs.y.total.count?.toFixed(1)} + ${refs.y.total.integral?.toFixed(1)} ${refs.y.total.last ?? '-'} `; diff --git a/src/YagrCore/defaults.ts b/src/YagrCore/defaults.ts index 597ed8d..7cf67f4 100644 --- a/src/YagrCore/defaults.ts +++ b/src/YagrCore/defaults.ts @@ -183,3 +183,4 @@ export default class ThemedDefaults { export const TOOLTIP_Y_OFFSET = 24; export const TOOLTIP_X_OFFSET = 24; export const TOOLTIP_DEFAULT_MAX_LINES = 10; +export const TIME_MULTIPLIER = 1; \ No newline at end of file diff --git a/src/YagrCore/mixins/create-options.ts b/src/YagrCore/mixins/create-options.ts index 66bac5b..03b9646 100644 --- a/src/YagrCore/mixins/create-options.ts +++ b/src/YagrCore/mixins/create-options.ts @@ -13,6 +13,7 @@ import { DEFAULT_X_SCALE, DEFAULT_X_SERIE_NAME, MIN_SELECTION_WIDTH, + TIME_MULTIPLIER, } from '../defaults'; import {configureSeries} from '../utils/series'; import markersPlugin from '../plugins/markers'; @@ -72,7 +73,7 @@ export class CreateUplotOptionsMixin { this._uHooks.setSelect = (u: uPlot) => { const {left, width} = u.select; const [_from, _to] = [u.posToVal(left, DEFAULT_X_SCALE), u.posToVal(left + width, DEFAULT_X_SCALE)]; - const {timeMultiplier = 1} = this.config.chart || {}; + const {timeMultiplier = TIME_MULTIPLIER} = this.config.chart || {}; this.execHooks('onSelect', { from: Math.ceil(_from / timeMultiplier), @@ -136,7 +137,7 @@ export class CreateUplotOptionsMixin { count: config.timeline.length, } as Series, ], - ms: chart.timeMultiplier || 1, + ms: chart.timeMultiplier || TIME_MULTIPLIER, hooks: config.hooks || {}, }; diff --git a/src/YagrCore/utils/axes.ts b/src/YagrCore/utils/axes.ts index 9006e39..674f209 100644 --- a/src/YagrCore/utils/axes.ts +++ b/src/YagrCore/utils/axes.ts @@ -89,7 +89,7 @@ function getTimeFormatterByRange(range: number, ticksCount: number) { } export const getTimeFormatter = (config: YagrConfig) => { - const msm = config.chart.timeMultiplier || 1; + const msm = config.chart.timeMultiplier || defaults.TIME_MULTIPLIER; return (_: unknown, ticks: number[]) => { const range = ticks[ticks.length - 1] - ticks[0]; const rangeMs = range / msm; @@ -145,7 +145,7 @@ function getAxis(axisConfig: AxisOptions, yagr: Yagr): Axis { ticks: axisConfig.ticks ? {...theme.X_AXIS_TICKS, ...axisConfig.ticks} : theme.X_AXIS_TICKS, scale: defaults.DEFAULT_X_SCALE, space: axisConfig.space || (() => defaults.X_AXIS_SPACE), - incrs: axisConfig.incrs || (() => defaults.X_AXIS_INCRS.map((i) => i * (config.chart.timeMultiplier || 1))), + incrs: axisConfig.incrs || (() => defaults.X_AXIS_INCRS.map((i) => i * (config.chart.timeMultiplier || defaults.TIME_MULTIPLIER))), side: 2, stroke: axisConfig.stroke || (() => theme.AXIS_STROKE), }); diff --git a/src/plugins/aggregates/aggregates.ts b/src/plugins/aggregates/aggregates.ts index 119e3b7..23844b9 100644 --- a/src/plugins/aggregates/aggregates.ts +++ b/src/plugins/aggregates/aggregates.ts @@ -85,7 +85,7 @@ const DataRef = (opst: AggregatesPluginOptions) => { const seriesIdx = yagr.state.y2uIdx[seriesId]; const timestamps = yagr.uplot.data[0].slice(fromIdx, toIdx + 1) as number[]; const values = yagr.uplot.series[seriesIdx].$c; - const integral = integrate(timestamps, values, yagr.config.chart?.timeMultiplier); + const integral = integrate(timestamps, values.slice(fromIdx, toIdx + 1)); const sum = safeSum(values, fromIdx, toIdx); const min = safeMin(values, fromIdx, toIdx); const max = safeMax(values, fromIdx, toIdx); @@ -136,7 +136,6 @@ const DataRef = (opst: AggregatesPluginOptions) => { const rowIntegral = integrate( u.data[0] as number[], $c as DataSeriesExtended, - yagr.config.chart?.timeMultiplier, ); aggrs[scale].integral = integral === null ? rowIntegral : integral + rowIntegral; }); diff --git a/src/plugins/aggregates/utils.ts b/src/plugins/aggregates/utils.ts index 096665d..14ac8c5 100644 --- a/src/plugins/aggregates/utils.ts +++ b/src/plugins/aggregates/utils.ts @@ -1,14 +1,16 @@ import {DataSeriesExtended} from '../../types'; -export function integrate(timestamps: number[], values: DataSeriesExtended, timeMultiplier = 0.001) { +export function integrate(timestamps: number[], values: DataSeriesExtended) { if (timestamps.length < 2) { return 0; } + let t0 = timestamps[0]; let x0 = Number(values[0]); let t1: number; let x1: number; let integral = 0; + for (let i = 1; i < timestamps.length; i++) { x1 = Number(values[i]); t1 = timestamps[i]; @@ -19,7 +21,7 @@ export function integrate(timestamps: number[], values: DataSeriesExtended, time if (!Number.isNaN(x1) && !Number.isNaN(x0)) { const dt = t1 - t0; const dx = x1 - x0; - const area = (x0 + dx / 2) * dt * timeMultiplier; // convert milliseconds to seconds + const area = (x0 + dx / 2) * dt; integral += area; } diff --git a/src/plugins/weekends/weekends.ts b/src/plugins/weekends/weekends.ts index 4b4f487..14ef426 100644 --- a/src/plugins/weekends/weekends.ts +++ b/src/plugins/weekends/weekends.ts @@ -1,5 +1,6 @@ import type {YagrPlugin} from '../../types'; import type Yagr from '../../index'; +import {TIME_MULTIPLIER} from '../../YagrCore/defaults'; export interface WeekendsPluginOptions { color?: string; @@ -38,7 +39,7 @@ export default function WeekendsPlugin({ if (predicate) { isWeekend = predicate(ts); } else { - const date = new Date(ts / (yagr.config.chart?.timeMultiplier || 1)); + const date = new Date(ts / (yagr.config.chart?.timeMultiplier || TIME_MULTIPLIER)); const dayOfWeek = date.getDay(); isWeekend = dayOfWeek === 6 || dayOfWeek === 0; } diff --git a/tests/core/plugins.test.ts b/tests/core/plugins.test.ts index 50e7069..d23f12f 100644 --- a/tests/core/plugins.test.ts +++ b/tests/core/plugins.test.ts @@ -35,7 +35,7 @@ describe('yagr plugins', () => { min: 1, sum: 6, last: null, - integral: 0.004, + integral: 4, }, }); }); diff --git a/tests/plugins/aggregates.test.ts b/tests/plugins/aggregates.test.ts index 330040d..d70264a 100644 --- a/tests/plugins/aggregates.test.ts +++ b/tests/plugins/aggregates.test.ts @@ -18,8 +18,8 @@ describe('Aggregates plugin', () => { it('should calc refs for series perScale', async () => { await new Promise((resolve) => setTimeout(resolve, 100)); expect(y.plugins.aggr?.get()).toEqual({ - y: {min: 1, max: 4, count: 8, last: null, avg: 2.75, sum: 22, integral: 0.0165}, - r: {min: 5, max: 8, count: 4, last: null, avg: 6.5, sum: 26, integral: 0.0195}, + y: {min: 1, max: 4, count: 8, last: null, avg: 2.75, sum: 22, integral: 16.5}, + r: {min: 5, max: 8, count: 4, last: null, avg: 6.5, sum: 26, integral: 19.5}, }); }); @@ -32,7 +32,7 @@ describe('Aggregates plugin', () => { avg: 1.5, last: 2, sum: 3, - integral: 0.0015, + integral: 1.5, }); }); @@ -47,13 +47,13 @@ describe('Aggregates plugin', () => { count: 2, avg: 1.5, sum: 3, - integral: 0.0015, + integral: 1.5, last: 2, }, two: { avg: 3, count: 2, - integral: 0.003, + integral: 3, last: 3, max: 3, min: 3, @@ -63,7 +63,7 @@ describe('Aggregates plugin', () => { total: { avg: 2.25, count: 4, - integral: 0.0045000000000000005, + integral: 4.5, last: null, max: 3, min: 1, @@ -75,7 +75,7 @@ describe('Aggregates plugin', () => { three: { avg: 5.5, count: 2, - integral: 0.0055, + integral: 5.5, last: 6, max: 6, min: 5, @@ -85,7 +85,7 @@ describe('Aggregates plugin', () => { total: { avg: 5.5, count: 2, - integral: 0.0055, + integral: 5.5, last: null, max: 6, min: 5, @@ -114,7 +114,7 @@ describe('Aggregates plugin', () => { it('should calc refs for series perScale', async () => { await new Promise((resolve) => setTimeout(resolve, 100)); expect(y.plugins.aggr?.get()).toEqual({ - y: {min: 1, max: 4, count: 8, last: null, avg: 2.75, sum: 22, integral: 0.0165}, + y: {min: 1, max: 4, count: 8, last: null, avg: 2.75, sum: 22, integral: 16.5}, }); }); @@ -127,7 +127,7 @@ describe('Aggregates plugin', () => { avg: 1.5, last: 2, sum: 3, - integral: 0.0015, + integral: 1.5, }); expect(y.plugins.aggr?.calc(0, 1, 'two')).toEqual({ min: 3, @@ -136,7 +136,7 @@ describe('Aggregates plugin', () => { avg: 3, last: 3, sum: 6, - integral: 0.003, + integral: 3, }); }); }); @@ -160,7 +160,7 @@ describe('Aggregates plugin', () => { it('should calc refs for series perScale', async () => { await new Promise((resolve) => setTimeout(resolve, 100)); expect(y.plugins.aggr?.get()).toEqual({ - y: {min: 1, max: 4, count: 4, last: null, avg: 2.75, sum: 11, integral: 0.006999999999999999}, + y: {min: 1, max: 4, count: 4, last: null, avg: 2.75, sum: 11, integral: 7}, }); }); @@ -173,7 +173,7 @@ describe('Aggregates plugin', () => { avg: 1, last: 1, sum: 1, - integral: 0.0005, + integral: 0.5, }); expect(y.plugins.aggr?.calc(0, 1, 'two')).toEqual({ min: null, @@ -211,7 +211,7 @@ describe('Aggregates plugin', () => { it('should calc refs for series perScale', async () => { await new Promise((resolve) => setTimeout(resolve, 100)); expect(y.plugins.aggr?.get()).toEqual({ - y: {min: 1, max: 4, count: 5, last: null, avg: 2.6, sum: 13, integral: 0.005}, + y: {min: 1, max: 4, count: 5, last: null, avg: 2.6, sum: 13, integral: 5}, }); }); @@ -233,7 +233,7 @@ describe('Aggregates plugin', () => { avg: 3, last: 3, sum: 3, - integral: 0, + integral: 1.5, }); }); });