Skip to content

Commit 8cc4b84

Browse files
Merge pull request #65 from adobe/fixMovingAverages
fix: moving averages were broken due to new normalized dimension work
2 parents 90b23c1 + c3e2294 commit 8cc4b84

File tree

2 files changed

+83
-26
lines changed

2 files changed

+83
-26
lines changed

src/specBuilder/trendline/trendlineUtils.ts

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,10 @@ export const getTrendlineMarks = (markProps: LineSpecProps): GroupMark[] => {
128128
const getTrendlineLineMark = (markProps: LineSpecProps, trendlineProps: TrendlineSpecProps): LineMark => {
129129
const { colorScheme, dimension, scaleType } = markProps;
130130
const { displayOnHover, lineType, lineWidth, metric, name, opacity } = trendlineProps;
131-
const x =
132-
scaleType === 'time'
133-
? { scale: 'xTrendline', field: `${dimension}Normalized` }
134-
: getXProductionRule(scaleType, dimension);
131+
132+
const x = trendlineUsesNormalizedDimension(trendlineProps.method, scaleType)
133+
? { scale: 'xTrendline', field: `${dimension}Normalized` }
134+
: getXProductionRule(scaleType, dimension);
135135
const color = trendlineProps.color ? { value: trendlineProps.color } : markProps.color;
136136

137137
return {
@@ -188,13 +188,8 @@ const getTrendlineHoverMarks = (lineProps: LineSpecProps, highlightRawPoint: boo
188188
export const addTrendlineData = (data: Data[], markProps: BarSpecProps | LineSpecProps) => {
189189
const { dimension } = markProps;
190190
data.push(...getTrendlineData(markProps));
191-
const trendlines = getTrendlines(markProps.children, markProps.name);
192191

193-
// only need to add the normalized dimension transform if there is a regression trendline and the dimension is time
194-
const hasRegressionTrendline = trendlines.some((trendline) => isRegressionMethod(trendline.method));
195-
const hasTimeScale = 'scaleType' in markProps && markProps.scaleType === 'time';
196-
197-
if (hasRegressionTrendline && hasTimeScale) {
192+
if (hasTrendlineWithNormailizedDimension(markProps)) {
198193
const tableData = getTableData(data);
199194
tableData.transform = addNormalizedDimensionTransform(tableData.transform ?? [], dimension);
200195
}
@@ -372,7 +367,9 @@ export const getTrendlineParamFormulaTransforms = (
372367
scaleType: ScaleType | undefined
373368
): FormulaTransform[] => {
374369
let expr = '';
375-
const trendlineDimension = scaleType === 'time' ? `${dimension}Normalized` : dimension;
370+
const trendlineDimension = trendlineUsesNormalizedDimension(method, scaleType)
371+
? `${dimension}Normalized`
372+
: dimension;
376373
if (isPolynomialMethod(method)) {
377374
const order = getPolynomialOrder(method);
378375
expr = [
@@ -400,9 +397,9 @@ export const getTrendlineParamFormulaTransforms = (
400397
};
401398

402399
export const getTrendlineScales = (props: LineSpecProps | BarSpecProps): Scale[] => {
403-
const { children, dimension, name } = props;
404-
const trendlines = getTrendlines(children, name);
405-
if (trendlines.length && 'scaleType' in props && props.scaleType === 'time') {
400+
const { dimension } = props;
401+
402+
if (hasTrendlineWithNormailizedDimension(props)) {
406403
return [
407404
{
408405
name: 'xTrendline',
@@ -421,7 +418,7 @@ export const getTrendlineScales = (props: LineSpecProps | BarSpecProps): Scale[]
421418
/**
422419
* determines if the supplied method is a polynomial method
423420
* @see https://vega.github.io/vega/docs/transforms/regression/
424-
* @param method regression method
421+
* @param method
425422
* @returns boolean
426423
*/
427424
const isPolynomialMethod = (method: TrendlineMethod): boolean =>
@@ -430,7 +427,7 @@ const isPolynomialMethod = (method: TrendlineMethod): boolean =>
430427
/**
431428
* determines if the supplied method is a regression method
432429
* @see https://vega.github.io/vega/docs/transforms/regression/
433-
* @param method regression method
430+
* @param method
434431
* @returns boolean
435432
*/
436433
const isRegressionMethod = (method: TrendlineMethod): boolean =>
@@ -439,11 +436,34 @@ const isRegressionMethod = (method: TrendlineMethod): boolean =>
439436
/**
440437
* determines if the supplied method is a windowing method
441438
* @see https://vega.github.io/vega/docs/transforms/window/
442-
* @param method regression method
439+
* @param method
443440
* @returns boolean
444441
*/
445442
const isWindowMethod = (method: TrendlineMethod): boolean => method.startsWith('movingAverage-');
446443

444+
/**
445+
* determines if the supplied method is a regression method that uses the normalized dimension
446+
* @see https://vega.github.io/vega/docs/transforms/regression/
447+
* @param method
448+
* @returns boolean
449+
*/
450+
const trendlineUsesNormalizedDimension = (method: TrendlineMethod, scaleType: ScaleType | undefined): boolean =>
451+
scaleType === 'time' && isRegressionMethod(method);
452+
453+
/**
454+
* determines if any trendlines use the normalized dimension
455+
* @param markProps
456+
* @returns boolean
457+
*/
458+
const hasTrendlineWithNormailizedDimension = (markProps: BarSpecProps | LineSpecProps): boolean => {
459+
const trendlines = getTrendlines(markProps.children, markProps.name);
460+
461+
// only need to add the normalized dimension transform if there is a regression trendline and the dimension is time
462+
const hasRegressionTrendline = trendlines.some((trendline) => isRegressionMethod(trendline.method));
463+
const hasTimeScale = 'scaleType' in markProps && markProps.scaleType === 'time';
464+
return hasRegressionTrendline && hasTimeScale;
465+
};
466+
447467
/**
448468
* gets the statistical transforms that will calculate the trendline values
449469
* @param markProps

src/stories/components/Trendline/Trendline.test.tsx

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,52 @@ describe('Trendline', () => {
2323
render(<Trendline />);
2424
});
2525

26-
test('Basic renders properly', async () => {
27-
render(<Basic {...Basic.args} />);
28-
const chart = await findChart();
29-
expect(chart).toBeInTheDocument();
30-
31-
const trendlines = await findAllMarksByGroupName(chart, 'line0Trendline0');
32-
expect(trendlines).toHaveLength(4);
33-
expect(trendlines[0]).toHaveAttribute('stroke-dasharray', '7,4');
34-
expect(trendlines[0]).toHaveAttribute('stroke-width', '1.5');
26+
describe('Basic', () => {
27+
test('Basic renders properly', async () => {
28+
render(<Basic {...Basic.args} />);
29+
const chart = await findChart();
30+
expect(chart).toBeInTheDocument();
31+
32+
const trendlines = await findAllMarksByGroupName(chart, 'line0Trendline0');
33+
expect(trendlines).toHaveLength(4);
34+
expect(trendlines[0]).toHaveAttribute('stroke-dasharray', '7,4');
35+
expect(trendlines[0]).toHaveAttribute('stroke-width', '1.5');
36+
});
37+
38+
/**
39+
* If there is an issue with the trendline, vega defaults to drawing all the x positions at 0.
40+
* This function checks that all the x positions are unique.
41+
* @param path
42+
* @returns
43+
*/
44+
const allXPathPositionsAreUnique = (path: string): boolean => {
45+
const xPositions = path.match(/([+-]?(\d*\.)?\d+),/g)?.map((match) => match.replace(',', '')) ?? [];
46+
return new Set(xPositions).size === xPositions.length;
47+
};
48+
49+
test('regression method renders correctly', async () => {
50+
render(<Basic {...Basic.args} />);
51+
const chart = await findChart();
52+
expect(chart).toBeInTheDocument();
53+
54+
const trendlines = await findAllMarksByGroupName(chart, 'line0Trendline0');
55+
expect(trendlines).toHaveLength(4);
56+
const trendlinePath = trendlines[0].getAttribute('d');
57+
expect(trendlinePath).toBeTruthy();
58+
expect(allXPathPositionsAreUnique(trendlinePath ?? '')).toBeTruthy();
59+
});
60+
61+
test('moving average method renders correctly', async () => {
62+
render(<Basic {...Basic.args} method="movingAverage-3" />);
63+
const chart = await findChart();
64+
expect(chart).toBeInTheDocument();
65+
66+
const trendlines = await findAllMarksByGroupName(chart, 'line0Trendline0');
67+
expect(trendlines).toHaveLength(4);
68+
const trendlinePath = trendlines[0].getAttribute('d');
69+
expect(trendlinePath).toBeTruthy();
70+
expect(allXPathPositionsAreUnique(trendlinePath ?? '')).toBeTruthy();
71+
});
3572
});
3673

3774
describe('DisplayOnHover', () => {

0 commit comments

Comments
 (0)