Skip to content

Commit 77a3e04

Browse files
committed
[FIX] chart: fix misalignment with trend line axis
Task Description When inserting a trend line on a line chart, depending on the data we sometimes have a misplaced trend line axis giving an alignment issue between the trend line and the original plot. For example, if you have these data : A1 : A, B1 : 1 A2 : 2, B2 : 2 A3 : 3, B3 : 3 A4 : 4, B4 : 4 A5 : 5, B5 : 5 A6 : 6, B6 : 6 A7 : 7, B7 : 7 A8 : 8, B8 : 8 When you try to insert a linear trend line, the line won't be aligned to the original line chart. This PR aims to fix this by changing the labels and axis of the trend line. Related Task closes #5422 Task: 4251695 X-original-commit: 0b78315 Signed-off-by: Rémi Rahir (rar) <[email protected]> Signed-off-by: Hendrickx Anthony (anhe) <[email protected]>
1 parent 37b8e04 commit 77a3e04

File tree

6 files changed

+97
-136
lines changed

6 files changed

+97
-136
lines changed

src/functions/helper_statistical.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Point } from "chart.js";
12
import { DEFAULT_WINDOW_SIZE } from "../constants";
23
import { isNumber, parseDateTime, range } from "../helpers";
34
import { _t } from "../translation";
@@ -288,19 +289,20 @@ export function predictLinearValues(
288289

289290
export function getMovingAverageValues(
290291
dataset: number[],
292+
labels: number[],
291293
windowSize = DEFAULT_WINDOW_SIZE
292-
): (number | null)[] {
293-
const values: (number | null)[] = [];
294+
): Point[] {
295+
const values: Point[] = [];
294296
// Fill the starting values with null until we have a full window
295297
for (let i = 0; i < windowSize - 1; i++) {
296-
values.push(null);
298+
values.push({ x: labels[i], y: NaN });
297299
}
298300
for (let i = 0; i <= dataset.length - windowSize; i++) {
299301
let sum = 0;
300302
for (let j = i; j < i + windowSize; j++) {
301303
sum += dataset[j];
302304
}
303-
values.push(sum / windowSize);
305+
values.push({ x: labels[i + windowSize - 1], y: sum / windowSize });
304306
}
305307
return values;
306308
}

src/helpers/figures/charts/runtime/chart_data_extractor.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Point } from "chart.js";
12
import { ChartTerms } from "../../../../components/translations_terms";
23
import {
34
evaluatePolynomial,
@@ -60,7 +61,7 @@ export function getBarChartData(
6061
? { x: leftAxisFormat || rightAxisFormat }
6162
: { y: leftAxisFormat, y1: rightAxisFormat };
6263

63-
const trendDataSetsValues: ((number | null)[] | undefined)[] = [];
64+
const trendDataSetsValues: (Point[] | undefined)[] = [];
6465
for (const index in dataSetsValues) {
6566
const { data } = dataSetsValues[index];
6667

@@ -139,7 +140,7 @@ export function getLineChartData(
139140
const labelsFormat = getChartLabelFormat(getters, labelRange);
140141
const axisFormats = { y: leftAxisFormat, y1: rightAxisFormat, x: labelsFormat };
141142

142-
const trendDataSetsValues: ((number | null)[] | undefined)[] = [];
143+
const trendDataSetsValues: (Point[] | undefined)[] = [];
143144
for (const index in dataSetsValues) {
144145
let { data } = dataSetsValues[index];
145146
if (definition.cumulative) {
@@ -349,20 +350,20 @@ export function getTrendDatasetForLineChart(
349350
}
350351
const numberOfStep = 5 * trendLabels.length;
351352
const step = (xmax - xmin) / numberOfStep;
352-
const newLabels = range(xmin, xmax + step / 2, step);
353-
const newValues = interpolateData(config, filteredValues, filteredLabels, newLabels);
354-
if (!newValues.length) {
353+
const trendNewLabels = range(xmin, xmax + step / 2, step);
354+
const trendValues = interpolateData(config, filteredValues, filteredLabels, trendNewLabels);
355+
if (!trendValues.length) {
355356
return;
356357
}
357-
return newValues;
358+
return trendValues;
358359
}
359360

360361
function interpolateData(
361362
config: TrendConfiguration,
362363
values: number[],
363364
labels: number[],
364365
newLabels: number[]
365-
): (number | null)[] {
366+
): Point[] {
366367
if (values.length < 2 || labels.length < 2 || newLabels.length === 0) {
367368
return [];
368369
}
@@ -376,13 +377,21 @@ function interpolateData(
376377
case "polynomial": {
377378
const order = config.order;
378379
if (!order) {
379-
return Array.from({ length: newLabels.length }, () => NaN);
380+
return newLabels.map((x) => ({ x, y: NaN }));
380381
}
381382
if (order === 1) {
382-
return predictLinearValues([values], [normalizedLabels], [normalizedNewLabels], true)[0];
383+
return predictLinearValues(
384+
[values],
385+
[normalizedLabels],
386+
[normalizedNewLabels],
387+
true
388+
)[0].map((y, i) => ({ x: newLabels[i], y }));
383389
}
384390
const coeffs = polynomialRegression(values, normalizedLabels, order, true).flat();
385-
return normalizedNewLabels.map((v) => evaluatePolynomial(coeffs, v, order));
391+
return normalizedNewLabels.map((x, i) => ({
392+
x: newLabels[i],
393+
y: evaluatePolynomial(coeffs, x, order),
394+
}));
386395
}
387396
case "exponential": {
388397
const positiveLogValues: number[] = [];
@@ -394,28 +403,28 @@ function interpolateData(
394403
}
395404
}
396405
if (!filteredLabels.length) {
397-
return Array.from({ length: newLabels.length }, () => NaN);
406+
return newLabels.map((x) => ({ x, y: NaN }));
398407
}
399408
return expM(
400409
predictLinearValues([positiveLogValues], [filteredLabels], [normalizedNewLabels], true)
401-
)[0];
410+
)[0].map((y, i) => ({ x: newLabels[i], y }));
402411
}
403412
case "logarithmic": {
404413
return predictLinearValues(
405414
[values],
406415
logM([normalizedLabels]),
407416
logM([normalizedNewLabels]),
408417
true
409-
)[0];
418+
)[0].map((y, i) => ({ x: newLabels[i], y }));
410419
}
411420
case "trailingMovingAverage": {
412-
return getMovingAverageValues(values, config.window);
421+
return getMovingAverageValues(values, labels, config.window);
413422
}
414423
default:
415-
return Array.from({ length: newLabels.length }, () => NaN);
424+
return newLabels.map((x) => ({ x, y: NaN }));
416425
}
417426
} catch (e) {
418-
return Array.from({ length: newLabels.length }, () => NaN);
427+
return newLabels.map((x) => ({ x, y: NaN }));
419428
}
420429
}
421430

src/helpers/figures/charts/runtime/chartjs_dataset.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChartDataset } from "chart.js";
1+
import { ChartDataset, Point } from "chart.js";
22
import {
33
BACKGROUND_CHART_COLOR,
44
CHART_WATERFALL_NEGATIVE_COLOR,
@@ -328,7 +328,7 @@ export function getGeoChartDatasets(
328328
function getTrendingLineDataSet(
329329
dataset: ChartDataset<"line" | "bar">,
330330
config: TrendConfiguration,
331-
data: (number | null)[]
331+
data: Point[]
332332
): ChartDataset<"line"> {
333333
const defaultBorderColor = colorToRGBA(dataset.backgroundColor as Color);
334334
defaultBorderColor.a = 1;

src/helpers/figures/charts/runtime/chartjs_scales.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,18 @@ export function getLineChartScales(
107107
/* We add a second x axis here to draw the trend lines, with the labels length being
108108
* set so that the second axis points match the classical x axis
109109
*/
110-
const maxLength = Math.max(...trendDatasets.map((trendDataset) => trendDataset?.length || 0));
111110
scales[TREND_LINE_XAXIS_ID] = {
112111
...(scales.x as any),
113-
type: "category",
114-
labels: range(0, maxLength).map((x) => x.toString()),
115-
offset: false,
116112
display: false,
117113
};
114+
if (axisType === "category") {
115+
/* We add a second x axis here to draw the trend lines, with the labels length being
116+
* set so that the second axis points match the classical x axis
117+
*/
118+
const maxLength = Math.max(...trendDatasets.map((trendDataset) => trendDataset?.length || 0));
119+
scales[TREND_LINE_XAXIS_ID]!["labels"] = range(0, maxLength).map((x) => x.toString());
120+
scales[TREND_LINE_XAXIS_ID]!["offset"] = false;
121+
}
118122
}
119123

120124
return scales;

src/types/chart/chart.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Point } from "chart.js";
12
import { Align, Color, Format, Locale, Range } from "../../types";
23
import { XlsxHexColor } from "../xlsx";
34
import { BarChartDefinition, BarChartRuntime } from "./bar_chart";
@@ -169,7 +170,7 @@ export interface ChartRuntimeGenerationArgs {
169170
axisFormats: ChartAxisFormats;
170171
labels: string[];
171172
locale: Locale;
172-
trendDataSetsValues?: ((number | null)[] | undefined)[];
173+
trendDataSetsValues?: (Point[] | undefined)[];
173174
axisType?: AxisType;
174175
}
175176

0 commit comments

Comments
 (0)