diff --git a/.gitignore b/.gitignore
index 53a06febdd..36759fa78d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ test/failure-screenshots/**/*.png
reports/
tmp/
+.temp/
dist/
coverage/
.out/
diff --git a/.playground/index.html b/.playground/index.html
index 3973665311..9ce5896129 100644
--- a/.playground/index.html
+++ b/.playground/index.html
@@ -33,6 +33,9 @@
height: 300px;
margin: 20px;
}
+ label {
+ display: block;
+ }
diff --git a/.playground/playground.tsx b/.playground/playground.tsx
index 3a170dc677..8a8dd21c01 100644
--- a/.playground/playground.tsx
+++ b/.playground/playground.tsx
@@ -17,43 +17,235 @@
* under the License. */
import React from 'react';
-import { Chart, Partition, Settings, PartitionLayout, XYChartElementEvent, PartitionElementEvent } from '../src';
+import {
+ Chart,
+ ScaleType,
+ Settings,
+ RectAnnotation,
+ LineAnnotation,
+ TooltipType,
+ BarSeries,
+ LineSeries,
+ Axis,
+ Position,
+} from '../src';
-export class Playground extends React.Component {
- onElementClick = (elements: (XYChartElementEvent | PartitionElementEvent)[]) => {
- // eslint-disable-next-line no-console
- console.log(elements);
+// const data = [
+// { x: 0, min: 0, max: 1 },
+// { x: 10, min: 0, max: 2 },
+// // { x: 2, min: 0, max: 3 },
+// ];
+
+const data = new Array(11).fill(0).map((d, i) => {
+ return {
+ x: i,
+ max: Math.random() * 10,
+ };
+});
+interface State {
+ showRectAnnotation: boolean;
+ showLineXAnnotation: boolean;
+ showLineYAnnotation: boolean;
+ totalBars: number;
+ useLinearBar: boolean;
+ useOrdinalBar: boolean;
+ useHistogramBar: boolean;
+ totalLines: number;
+ useLinearLine: boolean;
+ useOrdinalLine: boolean;
+}
+export class Playground extends React.Component<{}, State> {
+ state: State = {
+ showRectAnnotation: false,
+ showLineXAnnotation: false,
+ showLineYAnnotation: false,
+ totalBars: 1,
+ totalLines: 1,
+ useLinearBar: true,
+ useOrdinalBar: false,
+ useHistogramBar: false,
+ useLinearLine: false,
+ useOrdinalLine: false,
+ };
+ handleInputChange = (stateParam: keyof State) => (event: React.ChangeEvent) => {
+ const updatedValue = stateParam === 'totalBars' || stateParam === 'totalLines' ? Number(event.target.value) : 1;
+ this.setState((prevState: State) => {
+ return {
+ ...prevState,
+ [stateParam]: stateParam === 'totalBars' || stateParam === 'totalLines' ? updatedValue : !prevState[stateParam],
+ };
+ });
};
render() {
+ const keys: Array = [
+ 'showRectAnnotation',
+ 'showLineXAnnotation',
+ 'showLineYAnnotation',
+ 'useLinearBar',
+ 'useOrdinalBar',
+ 'useHistogramBar',
+ 'useLinearLine',
+ 'useOrdinalLine',
+ 'totalBars',
+ 'totalLines',
+ ];
return (
-
-
-
- {
- return d.v;
- }}
- data={[
- { g1: 'a', g2: 'a', v: 1 },
- { g1: 'a', g2: 'b', v: 1 },
- { g1: 'b', g2: 'a', v: 1 },
- { g1: 'b', g2: 'b', v: 1 },
- ]}
- layers={[
- {
- groupByRollup: (datum: { g1: string }) => datum.g1,
- },
- {
- groupByRollup: (datum: { g2: string }) => datum.g2,
- },
- ]}
- />
-
-
+ <>
+
+
+
+
+
+
+
+
+ {this.state.showRectAnnotation && (
+
+ )}
+ {this.state.showLineYAnnotation && (
+ }
+ />
+ )}
+ {this.state.showLineXAnnotation && (
+ }
+ />
+ )}
+ {this.state.useLinearBar &&
+ new Array(this.state.totalBars)
+ .fill(0)
+ .map((d, i) => (
+
+ ))}
+
+ {this.state.useOrdinalBar &&
+ new Array(this.state.totalBars)
+ .fill(0)
+ .map((d, i) => (
+
+ ))}
+
+ {this.state.useHistogramBar &&
+ new Array(this.state.totalBars)
+ .fill(0)
+ .map((d, i) => (
+
+ ))}
+
+ {this.state.useOrdinalLine &&
+ new Array(this.state.totalLines)
+ .fill(0)
+ .map((d, i) => (
+
+ ))}
+
+ {this.state.useLinearLine &&
+ new Array(this.state.totalLines)
+ .fill(0)
+ .map((d, i) => (
+
+ ))}
+
+
+ >
);
}
}
diff --git a/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx b/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx
index cbfd81d4b9..17b813236a 100644
--- a/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx
+++ b/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx
@@ -76,7 +76,7 @@ describe('annotation marker', () => {
xScale,
Position.Left,
0,
- false,
+ 0,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -131,7 +131,7 @@ describe('annotation marker', () => {
xScale,
Position.Left,
0,
- false,
+ 0,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -185,7 +185,7 @@ describe('annotation marker', () => {
xScale,
Position.Bottom,
0,
- false,
+ 0,
);
const expectedDimensions: AnnotationLineProps[] = [
{
diff --git a/src/chart_types/xy_chart/annotations/annotation_utils.test.ts b/src/chart_types/xy_chart/annotations/annotation_utils.test.ts
index 4d885075ca..dda358f9c9 100644
--- a/src/chart_types/xy_chart/annotations/annotation_utils.test.ts
+++ b/src/chart_types/xy_chart/annotations/annotation_utils.test.ts
@@ -116,7 +116,7 @@ describe('annotation utils', () => {
axesSpecs.push(verticalAxisSpec);
- test('should compute annotation dimensions', () => {
+ test('should compute rect annotation in x ordinal scale', () => {
const chartRotation: Rotation = 0;
const yScales: Map = new Map();
yScales.set(groupId, continuousScale);
@@ -171,15 +171,16 @@ describe('annotation utils', () => {
start: { x1: 0, y1: 20 },
end: { x2: 10, y2: 20 },
},
+ marker: undefined,
details: { detailsText: 'foo', headerText: '2' },
},
]);
- expectedDimensions.set(rectAnnotationId, [{ rect: { x: 0, y: 30, width: 25, height: 20 } }]);
+ expectedDimensions.set(rectAnnotationId, [{ rect: { x: 0, y: 30, width: 50, height: 20 } }]);
expect(dimensions).toEqual(expectedDimensions);
});
- test('should not compute annotation dimensions if a corresponding axis does not exist', () => {
+ test('should compute annotation dimensions also with missing axis', () => {
const chartRotation: Rotation = 0;
const yScales: Map = new Map();
yScales.set(groupId, continuousScale);
@@ -211,8 +212,7 @@ describe('annotation utils', () => {
1,
false,
);
- const expectedDimensions = new Map();
- expect(dimensions).toEqual(expectedDimensions);
+ expect(dimensions.size).toEqual(1);
});
test('should compute line annotation dimensions for yDomain on a yScale (chartRotation 0, left axis)', () => {
@@ -242,7 +242,6 @@ describe('annotation utils', () => {
xScale,
Position.Left,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -288,7 +287,6 @@ describe('annotation utils', () => {
xScale,
Position.Right,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -334,7 +332,6 @@ describe('annotation utils', () => {
xScale,
Position.Left,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -379,7 +376,6 @@ describe('annotation utils', () => {
xScale,
Position.Left,
0,
- false,
);
expect(dimensions).toEqual(null);
});
@@ -409,7 +405,6 @@ describe('annotation utils', () => {
xScale,
Position.Left,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -453,7 +448,6 @@ describe('annotation utils', () => {
xScale,
Position.Top,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -498,7 +492,6 @@ describe('annotation utils', () => {
xScale,
Position.Bottom,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -543,7 +536,7 @@ describe('annotation utils', () => {
xScale,
Position.Bottom,
0,
- true,
+ 1,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -589,7 +582,6 @@ describe('annotation utils', () => {
xScale,
Position.Left,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -635,7 +627,6 @@ describe('annotation utils', () => {
xScale,
Position.Left,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -681,7 +672,6 @@ describe('annotation utils', () => {
xScale,
Position.Left,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -727,7 +717,6 @@ describe('annotation utils', () => {
xScale,
Position.Top,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -772,7 +761,6 @@ describe('annotation utils', () => {
xScale,
Position.Bottom,
0,
- false,
);
const expectedDimensions: AnnotationLineProps[] = [
{
@@ -792,7 +780,7 @@ describe('annotation utils', () => {
expect(dimensions).toEqual(expectedDimensions);
});
- test('should not compute annotation line values for values outside of domain or AnnotationSpec.hideLines', () => {
+ test('should not compute annotation line values for invalid data values or AnnotationSpec.hideLines', () => {
const chartRotation: Rotation = 0;
const yScales: Map = new Map();
yScales.set(groupId, continuousScale);
@@ -819,7 +807,6 @@ describe('annotation utils', () => {
xScale,
Position.Right,
0,
- false,
);
expect(emptyXDimensions).toEqual([]);
@@ -843,7 +830,6 @@ describe('annotation utils', () => {
continuousScale,
Position.Right,
0,
- false,
);
expect(invalidStringXDimensions).toEqual([]);
@@ -859,7 +845,7 @@ describe('annotation utils', () => {
style: DEFAULT_ANNOTATION_LINE_STYLE,
};
- const emptyOutOfBoundsXDimensions = computeLineAnnotationDimensions(
+ const cappedToMinX = computeLineAnnotationDimensions(
outOfBoundsXLineAnnotation,
chartDimensions,
chartRotation,
@@ -867,10 +853,8 @@ describe('annotation utils', () => {
continuousScale,
Position.Right,
0,
- false,
);
-
- expect(emptyOutOfBoundsXDimensions).toEqual([]);
+ expect(cappedToMinX).toHaveLength(1);
const invalidYLineAnnotation: AnnotationSpec = {
chartType: ChartTypes.XYAxis,
@@ -891,7 +875,6 @@ describe('annotation utils', () => {
xScale,
Position.Right,
0,
- false,
);
expect(emptyYDimensions).toEqual([]);
@@ -907,7 +890,7 @@ describe('annotation utils', () => {
style: DEFAULT_ANNOTATION_LINE_STYLE,
};
- const emptyOutOfBoundsYDimensions = computeLineAnnotationDimensions(
+ const cappedToMinY = computeLineAnnotationDimensions(
outOfBoundsYLineAnnotation,
chartDimensions,
chartRotation,
@@ -915,10 +898,9 @@ describe('annotation utils', () => {
xScale,
Position.Right,
0,
- false,
);
- expect(emptyOutOfBoundsYDimensions).toEqual([]);
+ expect(cappedToMinY).toHaveLength(1);
const invalidStringYLineAnnotation: AnnotationSpec = {
chartType: ChartTypes.XYAxis,
@@ -939,7 +921,6 @@ describe('annotation utils', () => {
continuousScale,
Position.Right,
0,
- false,
);
expect(invalidStringYDimensions).toEqual([]);
@@ -964,7 +945,6 @@ describe('annotation utils', () => {
continuousScale,
Position.Right,
0,
- false,
);
expect(hiddenAnnotationDimensions).toEqual(null);
@@ -1379,7 +1359,7 @@ describe('annotation utils', () => {
dataValues: [{ coordinates: { x0: 1, x1: 2, y0: 3, y1: 5 } }],
};
- const noYScale = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0);
+ const noYScale = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0, 1);
expect(noYScale).toBe(null);
});
@@ -1401,9 +1381,9 @@ describe('annotation utils', () => {
],
};
- const skippedInvalid = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0);
+ const skippedInvalid = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0, 1);
- expect(skippedInvalid).toEqual([]);
+ expect(skippedInvalid).toHaveLength(1);
});
test('should compute rectangle dimensions shifted for histogram mode', () => {
const yScales: Map = new Map();
@@ -1411,7 +1391,7 @@ describe('annotation utils', () => {
const xScale: Scale = new ScaleContinuous(
{ type: ScaleType.Linear, domain: continuousData, range: [minRange, maxRange] },
- { bandwidth: 1, minInterval: 1 },
+ { bandwidth: 72, minInterval: 1 },
);
const annotationRectangle: RectAnnotationSpec = {
@@ -1428,7 +1408,7 @@ describe('annotation utils', () => {
],
};
- const dimensions = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, true, 0);
+ const dimensions = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, true, 0, 1);
const [dims1, dims2, dims3, dims4] = dimensions;
expect(dims1.rect.x).toBe(10);
@@ -1438,18 +1418,18 @@ describe('annotation utils', () => {
expect(dims2.rect.x).toBe(0);
expect(dims2.rect.y).toBe(0);
- expect(dims2.rect.width).toBe(10);
+ expect(dims2.rect.width).toBe(20);
expect(dims2.rect.height).toBe(100);
expect(dims3.rect.x).toBe(0);
- expect(dims3.rect.y).toBe(0);
+ expect(dims3.rect.y).toBe(10);
expect(dims3.rect.width).toBeCloseTo(110);
- expect(dims3.rect.height).toBe(10);
+ expect(dims3.rect.height).toBe(90);
expect(dims4.rect.x).toBe(0);
- expect(dims4.rect.y).toBe(10);
+ expect(dims4.rect.y).toBe(0);
expect(dims4.rect.width).toBeCloseTo(110);
- expect(dims4.rect.height).toBe(90);
+ expect(dims4.rect.height).toBe(10);
});
test('should compute rectangle dimensions when only a single coordinate defined', () => {
const yScales: Map = new Map();
@@ -1471,13 +1451,13 @@ describe('annotation utils', () => {
],
};
- const dimensions = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0);
+ const dimensions = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0, 0);
const expectedDimensions = [
- { rect: { x: 10, y: 0, width: 90, height: 100 } },
- { rect: { x: 0, y: 0, width: 10, height: 100 } },
- { rect: { x: 0, y: 0, width: 100, height: 10 } },
- { rect: { x: 0, y: 10, width: 100, height: 90 } },
+ { rect: { x: 10, y: 0, width: 90, height: 100 }, details: undefined },
+ { rect: { x: 0, y: 0, width: 10, height: 100 }, details: undefined },
+ { rect: { x: 0, y: 10, width: 100, height: 90 }, details: undefined },
+ { rect: { x: 0, y: 0, width: 100, height: 10 }, details: undefined },
];
expect(dimensions).toEqual(expectedDimensions);
@@ -1497,7 +1477,7 @@ describe('annotation utils', () => {
dataValues: [{ coordinates: { x0: 1, x1: 2, y0: 3, y1: 5 } }],
};
- const unrotated = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0);
+ const unrotated = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0, 0);
expect(unrotated).toEqual([{ rect: { x: 10, y: 30, width: 10, height: 20 } }]);
});
@@ -1507,6 +1487,7 @@ describe('annotation utils', () => {
const xScale: Scale = ordinalScale;
+ // will render a rectangle that inclused both a and b
const annotationRectangle: RectAnnotationSpec = {
chartType: ChartTypes.XYAxis,
specType: SpecTypes.Annotation,
@@ -1516,26 +1497,28 @@ describe('annotation utils', () => {
dataValues: [{ coordinates: { x0: 'a', x1: 'b', y0: 0, y1: 2 } }],
};
- const unrotated = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0);
+ const unrotated = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0, 1);
- expect(unrotated).toEqual([{ rect: { x: 0, y: 0, width: 25, height: 20 } }]);
+ expect(unrotated).toEqual([{ rect: { x: 0, y: 0, width: 50, height: 20 } }]);
});
test('should validate scaled dataValues', () => {
// not aligned with tick
- expect(scaleAndValidateDatum('', ordinalScale, false)).toBe(null);
- expect(scaleAndValidateDatum('a', continuousScale, false)).toBe(null);
- expect(scaleAndValidateDatum(-10, continuousScale, false)).toBe(null);
- expect(scaleAndValidateDatum(20, continuousScale, false)).toBe(null);
+ expect(scaleAndValidateDatum('', ordinalScale, 0)).toBe(null);
+ expect(scaleAndValidateDatum('a', continuousScale, 0)).toBe(null);
+
+ // valid value limited to min/max
+ expect(scaleAndValidateDatum(-10, continuousScale, 0)).toBe(0);
+ expect(scaleAndValidateDatum(20, continuousScale, 0)).toBe(100);
// allow values within domainEnd + minInterval when not alignWithTick
- expect(scaleAndValidateDatum(10.25, continuousScale, false)).toBeCloseTo(102.5);
- expect(scaleAndValidateDatum(10.25, continuousScale, true)).toBe(null);
+ expect(scaleAndValidateDatum(10.25, continuousScale, 1)).toBeCloseTo(102.5);
+ expect(scaleAndValidateDatum(10.25, continuousScale, 0)).toBe(100);
- expect(scaleAndValidateDatum('a', ordinalScale, false)).toBe(0);
- expect(scaleAndValidateDatum(0, continuousScale, false)).toBe(0);
+ expect(scaleAndValidateDatum('a', ordinalScale, 0)).toBe(0);
+ expect(scaleAndValidateDatum(0, continuousScale, 0)).toBe(0);
// aligned with tick
- expect(scaleAndValidateDatum(1.25, continuousScale, true)).toBe(12.5);
+ expect(scaleAndValidateDatum(1.25, continuousScale, 0)).toBe(12.5);
});
test('should determine if a point is within a rectangle annotation', () => {
const cursorPosition = { x: 3, y: 4 };
diff --git a/src/chart_types/xy_chart/annotations/annotation_utils.ts b/src/chart_types/xy_chart/annotations/annotation_utils.ts
index 8cf152eeae..17c2061ee2 100644
--- a/src/chart_types/xy_chart/annotations/annotation_utils.ts
+++ b/src/chart_types/xy_chart/annotations/annotation_utils.ts
@@ -87,25 +87,36 @@ export type AnnotationDimensions = AnnotationLineProps[] | AnnotationRectProps[]
export type Bounds = { startX: number; endX: number; startY: number; endY: number };
/** @internal */
-export function scaleAndValidateDatum(dataValue: any, scale: Scale, alignWithTick: boolean): number | null {
+export function scaleAndValidateDatum(
+ value: any,
+ scale: Scale,
+ totalBarsInCluster: number,
+ lastValue?: boolean,
+): number | null {
const isContinuous = scale.type !== ScaleType.Ordinal;
- const scaledValue = scale.scale(dataValue);
+ const scaledValue = scale.scale(value);
// d3.scale will return 0 for '', rendering the line incorrectly at 0
- if (scaledValue === null || (isContinuous && dataValue === '')) {
+ if (scaledValue === null || (isContinuous && value === '')) {
return null;
}
if (isContinuous) {
const [domainStart, domainEnd] = scale.domain;
+ const adjustedDomainEnd = domainEnd + (totalBarsInCluster > 0 ? scale.minInterval : 0);
- // if we're not aligning the ticks, we need to extend the domain by one more tick for histograms
- const domainEndOffset = alignWithTick ? 0 : scale.minInterval;
-
- if (domainStart > dataValue || domainEnd + domainEndOffset < dataValue) {
- return null;
+ // limit the value to min or max of the domain
+ if (value < domainStart) {
+ return scale.scale(domainStart);
}
+ if (value > adjustedDomainEnd) {
+ return scale.scale(adjustedDomainEnd);
+ }
+ return scaledValue;
+ }
+ // is ordinal scale
+ if (lastValue) {
+ return scaledValue + scale.bandwidth * totalBarsInCluster;
}
-
return scaledValue;
}
@@ -187,9 +198,6 @@ export function computeAnnotationDimensions(
const { groupId, domainType } = annotationSpec;
const annotationAxisPosition = getAnnotationAxis(axesSpecs, groupId, domainType, chartRotation);
- if (!annotationAxisPosition) {
- return;
- }
const dimensions = computeLineAnnotationDimensions(
annotationSpec,
chartDimensions,
@@ -198,7 +206,7 @@ export function computeAnnotationDimensions(
xScale,
annotationAxisPosition,
xScaleOffset - clusterOffset,
- enableHistogramMode,
+ totalBarsInCluster,
);
if (dimensions) {
@@ -211,6 +219,7 @@ export function computeAnnotationDimensions(
xScale,
enableHistogramMode,
barsPadding,
+ totalBarsInCluster,
);
if (dimensions) {
diff --git a/src/chart_types/xy_chart/annotations/line_annotation.test.ts b/src/chart_types/xy_chart/annotations/line_annotation.test.ts
new file mode 100644
index 0000000000..f81140bedb
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/line_annotation.test.ts
@@ -0,0 +1,115 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License. */
+
+import { MockStore } from '../../../mocks/store';
+import { MockSeriesSpec, MockAnnotationSpec, MockGlobalSpec } from '../../../mocks/specs';
+import { computeAnnotationDimensionsSelector } from '../state/selectors/compute_annotations';
+import { ScaleType } from '../../../scales';
+
+function expectAnnotationAtPosition(
+ data: Array<{ x: number; y: number }>,
+ type: 'line' | 'bar',
+ indexPosition: number,
+ expectedLinePosition: number,
+ numOfSpecs = 1,
+ xScaleType: typeof ScaleType.Ordinal | typeof ScaleType.Linear | typeof ScaleType.Time = ScaleType.Linear,
+) {
+ const store = MockStore.default();
+ const settings = MockGlobalSpec.settingsNoMargins();
+ const specs = new Array(numOfSpecs).fill(0).map((d, i) => {
+ return MockSeriesSpec.byTypePartial(type)({
+ id: `spec_${i}`,
+ xScaleType,
+ data,
+ });
+ });
+ const annotation = MockAnnotationSpec.line({
+ dataValues: [
+ {
+ dataValue: indexPosition,
+ },
+ ],
+ });
+
+ MockStore.addSpecs([settings, ...specs, annotation], store);
+ const annotations = computeAnnotationDimensionsSelector(store.getState());
+ expect(annotations.get(annotation.id)).toEqual([
+ {
+ anchor: { left: expectedLinePosition, position: 'bottom', top: 100 },
+ details: { detailsText: undefined, headerText: `${indexPosition}` },
+ linePathPoints: {
+ start: { x1: expectedLinePosition, y1: 100 },
+ end: { x2: expectedLinePosition, y2: 0 },
+ },
+ marker: undefined,
+ },
+ ]);
+}
+
+describe('Render vertical line annotation within', () => {
+ it.each([
+ [0, 1, 12.5], // middle of 1st bar
+ [1, 1, 37.5], // middle of 2nd bar
+ [2, 1, 62.5], // middle of 3rd bar
+ [3, 1, 87.5], // middle of 4th bar
+ [1, 2, 37.5], // middle of 2nd bar
+ [1, 3, 37.5], // middle of 2nd bar
+ ])('a bar at position %i, %i specs, all scales', (dataValue, numOfSpecs, linePosition) => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ { x: 3, y: 2 },
+ ];
+ expectAnnotationAtPosition(data, 'bar', dataValue, linePosition, numOfSpecs);
+ expectAnnotationAtPosition(data, 'bar', dataValue, linePosition, numOfSpecs, ScaleType.Ordinal);
+ expectAnnotationAtPosition(data, 'bar', dataValue, linePosition, numOfSpecs, ScaleType.Time);
+ });
+
+ it.each([
+ [0, 1, 0], // the start of the chart
+ [1, 1, 50], // the middle of the chart
+ [2, 1, 100], // the end of the chart
+ [1, 2, 50], // the middle of the chart
+ [1, 3, 50], // the middle of the chart
+ ])('line point at position %i, %i specs, linear scale', (dataValue, numOfSpecs, linePosition) => {
+ const data = [
+ { x: 0, y: 1 },
+ { x: 1, y: 1 },
+ { x: 2, y: 2 },
+ ];
+ expectAnnotationAtPosition(data, 'line', dataValue, linePosition, numOfSpecs);
+ });
+
+ it.each([
+ [0, 1, 12.5], // 1st ordinal line point
+ [1, 1, 37.5], // 2nd ordinal line point
+ [2, 1, 62.5], // 3rd ordinal line point
+ [3, 1, 87.5], // 4th ordinal line point
+ [1, 2, 37.5], // 2nd ordinal line point
+ [1, 3, 37.5], // 2nd ordinal line point
+ ])('line point at position %i, %i specs, Ordinal scale', (dataValue, numOfSpecs, linePosition) => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ { x: 3, y: 2 },
+ ];
+ expectAnnotationAtPosition(data, 'line', dataValue, linePosition, numOfSpecs, ScaleType.Ordinal);
+ });
+});
diff --git a/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts
index e9c72a1b35..2e819cfa6c 100644
--- a/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts
+++ b/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts
@@ -38,7 +38,6 @@ import { isHorizontalAxis } from '../utils/axis_utils';
import { Dimensions } from '../../../utils/dimensions';
import { Scale } from '../../../scales';
import { GroupId } from '../../../utils/ids';
-import { LineAnnotationStyle } from '../../../utils/themes/theme';
import { Point } from '../../../utils/point';
import { isWithinRectBounds } from './rect_annotation_tooltip';
@@ -85,7 +84,7 @@ function computeYDomainLineAnnotationDimensions(
dataValues: LineAnnotationDatum[],
yScale: Scale,
chartRotation: Rotation,
- axisPosition: Position,
+ axisPosition: Position | null,
chartDimensions: Dimensions,
lineColor: string,
marker?: JSX.Element,
@@ -94,11 +93,14 @@ function computeYDomainLineAnnotationDimensions(
const chartHeight = chartDimensions.height;
const chartWidth = chartDimensions.width;
const isHorizontalChartRotation = isHorizontalRotation(chartRotation);
-
+ // let's use a default Bottom-X/Left-Y axis orientation if we are not showing an axis
+ // but we are displaying a line annotation
+ const anchorPosition =
+ axisPosition === null ? (isHorizontalChartRotation ? Position.Bottom : Position.Left) : axisPosition;
const lineProps: AnnotationLineProps[] = [];
dataValues.forEach((datum: LineAnnotationDatum) => {
- const { dataValue } = datum;
+ let { dataValue } = datum;
// avoid rendering invalid annotation value
if (dataValue === null || dataValue === undefined || dataValue === '') {
@@ -113,11 +115,15 @@ function computeYDomainLineAnnotationDimensions(
const [domainStart, domainEnd] = yScale.domain;
// avoid rendering annotation with values outside the scale domain
- if (domainStart > dataValue || domainEnd < dataValue) {
- return;
+ if (dataValue < domainStart) {
+ dataValue = domainStart;
}
+ if (dataValue > domainEnd) {
+ dataValue = domainEnd;
+ }
+
const anchor = {
- position: axisPosition,
+ position: anchorPosition,
top: 0,
left: 0,
};
@@ -129,7 +135,7 @@ function computeYDomainLineAnnotationDimensions(
// the Y axis is vertical, X axis is horizontal y|--x--|y
if (isHorizontalChartRotation) {
// y|__x__
- if (axisPosition === Position.Left) {
+ if (anchorPosition === Position.Left) {
anchor.left = 0;
markerPosition.left = -markerDimension.width;
linePathPoints.start.x1 = 0;
@@ -155,7 +161,7 @@ function computeYDomainLineAnnotationDimensions(
// the Y axis is horizontal, X axis is vertical x|--y--|x
} else {
// ¯¯y¯¯
- if (axisPosition === Position.Top) {
+ if (anchorPosition === Position.Top) {
anchor.top = 0;
markerPosition.top = -markerDimension.height;
linePathPoints.start.x1 = 0;
@@ -208,11 +214,11 @@ function computeXDomainLineAnnotationDimensions(
dataValues: LineAnnotationDatum[],
xScale: Scale,
chartRotation: Rotation,
- axisPosition: Position,
+ axisPosition: Position | null,
chartDimensions: Dimensions,
lineColor: string,
xScaleOffset: number,
- enableHistogramMode: boolean,
+ totalBarsInCluster: number,
marker?: JSX.Element,
markerDimension = { width: 0, height: 0 },
): AnnotationLineProps[] {
@@ -220,12 +226,15 @@ function computeXDomainLineAnnotationDimensions(
const chartWidth = chartDimensions.width;
const lineProps: AnnotationLineProps[] = [];
const isHorizontalChartRotation = isHorizontalRotation(chartRotation);
+ // let's use a default Bottom-X/Left-Y axis orientation if we are not showing an axis
+ // but we are displaying a line annotation
+ const anchorPosition =
+ axisPosition === null ? (isHorizontalChartRotation ? Position.Bottom : Position.Left) : axisPosition;
- const alignWithTick = xScale.bandwidth > 0 && !enableHistogramMode;
dataValues.forEach((datum: LineAnnotationDatum) => {
const { dataValue } = datum;
- const scaledXValue = scaleAndValidateDatum(dataValue, xScale, alignWithTick);
+ const scaledXValue = scaleAndValidateDatum(dataValue, xScale, totalBarsInCluster, false);
if (scaledXValue == null) {
return;
@@ -240,14 +249,14 @@ function computeXDomainLineAnnotationDimensions(
end: { x2: 0, y2: 0 },
};
const anchor = {
- position: axisPosition,
+ position: anchorPosition,
top: 0,
left: 0,
};
// the Y axis is vertical, X axis is horizontal y|--x--|y
if (isHorizontalChartRotation) {
// __x__
- if (axisPosition === Position.Bottom) {
+ if (anchorPosition === Position.Bottom) {
linePathPoints.start.y1 = chartHeight;
linePathPoints.end.y2 = 0;
anchor.top = chartHeight;
@@ -273,7 +282,7 @@ function computeXDomainLineAnnotationDimensions(
// the Y axis is horizontal, X axis is vertical x|--y--|x
} else {
// x|--y--
- if (axisPosition === Position.Left) {
+ if (anchorPosition === Position.Left) {
anchor.left = 0;
markerPosition.left = -markerDimension.width;
linePathPoints.start.x1 = annotationValueXposition;
@@ -330,9 +339,9 @@ export function computeLineAnnotationDimensions(
chartRotation: Rotation,
yScales: Map,
xScale: Scale,
- axisPosition: Position,
+ axisPosition: Position | null,
xScaleOffset: number,
- enableHistogramMode: boolean,
+ totalBarsInCluster: number = 0,
): AnnotationLineProps[] | null {
const { domainType, dataValues, marker, markerDimensions, hideLines } = annotationSpec;
@@ -341,8 +350,8 @@ export function computeLineAnnotationDimensions(
}
// this type is guaranteed as this has been merged with default
- const lineStyle = annotationSpec.style as LineAnnotationStyle;
- const lineColor = lineStyle.line.stroke;
+ const lineStyle = annotationSpec.style;
+ const lineColor = lineStyle?.line?.stroke ?? 'red';
if (domainType === AnnotationDomainTypes.XDomain) {
return computeXDomainLineAnnotationDimensions(
@@ -353,7 +362,7 @@ export function computeLineAnnotationDimensions(
chartDimensions,
lineColor,
xScaleOffset,
- enableHistogramMode,
+ totalBarsInCluster,
marker,
markerDimensions,
);
diff --git a/src/chart_types/xy_chart/annotations/rect_annotation.test.ts b/src/chart_types/xy_chart/annotations/rect_annotation.test.ts
new file mode 100644
index 0000000000..e4838b25e1
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/rect_annotation.test.ts
@@ -0,0 +1,249 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License. */
+import { MockStore } from '../../../mocks/store';
+import { MockSeriesSpec, MockAnnotationSpec, MockGlobalSpec } from '../../../mocks/specs';
+import { computeAnnotationDimensionsSelector } from '../state/selectors/compute_annotations';
+import { ScaleType } from '../../../scales';
+import { RectAnnotationDatum } from '../utils/specs';
+import { AnnotationRectProps } from './rect_annotation_tooltip';
+
+function expectAnnotationAtPosition(
+ data: Array<{ x: number; y: number }>,
+ type: 'line' | 'bar',
+ dataValues: RectAnnotationDatum[],
+ expectedRect: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ },
+ numOfSpecs = 1,
+ xScaleType: typeof ScaleType.Ordinal | typeof ScaleType.Linear | typeof ScaleType.Time = ScaleType.Linear,
+) {
+ const store = MockStore.default();
+ const settings = MockGlobalSpec.settingsNoMargins();
+ const specs = new Array(numOfSpecs).fill(0).map((d, i) => {
+ return MockSeriesSpec.byTypePartial(type)({
+ id: `spec_${i}`,
+ xScaleType,
+ data,
+ });
+ });
+ const annotation = MockAnnotationSpec.rect({
+ dataValues,
+ });
+
+ MockStore.addSpecs([settings, ...specs, annotation], store);
+ const annotations = computeAnnotationDimensionsSelector(store.getState());
+ const renderedAnnotations = annotations.get(annotation.id)!;
+ expect(renderedAnnotations.length).toBe(1);
+ const { rect } = renderedAnnotations[0] as AnnotationRectProps;
+ expect(rect.x).toBeCloseTo(expectedRect.x, 3);
+ expect(rect.y).toBeCloseTo(expectedRect.y, 3);
+ expect(rect.width).toBeCloseTo(expectedRect.width, 3);
+ expect(rect.height).toBeCloseTo(expectedRect.height, 3);
+}
+
+describe('Render rect annotation within', () => {
+ it.each([
+ // x0, numOfSpecs, x, width
+ [0, 1, 0, 100],
+ [1, 1, 25, 75],
+ [2, 1, 50, 50],
+ [3, 1, 75, 25],
+ [1, 2, 25, 75],
+ [2, 3, 50, 50],
+ ])('bars starting from %i, %i specs, all scales', (x0, numOfSpecs, x, width) => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ { x: 3, y: 2 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { x0 },
+ },
+ ];
+ const rect = { x, width, y: 0, height: 100 };
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, numOfSpecs);
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, numOfSpecs, ScaleType.Ordinal);
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, numOfSpecs, ScaleType.Time);
+ });
+
+ it.each([
+ // x1, numOfSpecs, x, width
+ [0, 1, 0, 25],
+ [1, 1, 0, 50],
+ [2, 1, 0, 75],
+ [3, 1, 0, 100],
+ [1, 2, 0, 50],
+ [2, 2, 0, 75],
+ ])('bars starting ending at %i, %i specs, all scales', (x1, numOfSpecs, x, width) => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ { x: 3, y: 2 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { x1 },
+ },
+ ];
+ const rect = { x, width, y: 0, height: 100 };
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, numOfSpecs);
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, numOfSpecs, ScaleType.Ordinal);
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, numOfSpecs, ScaleType.Time);
+ });
+
+ it.each([
+ // x0, x1, numOfSpecs, x, width
+ [0, 0, 1, 0, 25],
+ [0, 1, 1, 0, 50],
+ [1, 3, 1, 25, 75],
+ [0, 1, 2, 0, 50],
+ [1, 3, 3, 25, 75],
+ ])('bars starting starting at %i, ending at %i, %i specs, all scales', (x0, x1, numOfSpecs, x, width) => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ { x: 3, y: 2 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { x0, x1 },
+ },
+ ];
+ const rect = { x, width, y: 0, height: 100 };
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, numOfSpecs);
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, numOfSpecs, ScaleType.Ordinal);
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, numOfSpecs, ScaleType.Time);
+ });
+
+ it.each([
+ // x0, x1, numOfSpecs, x, width
+ [0, 0, 1, 0, 25],
+ [0, 1, 1, 0, 50],
+ [1, 3, 1, 25, 75],
+ [0, 1, 2, 0, 50],
+ [1, 3, 3, 25, 75],
+ ])('lines starting starting at %i, ending at %i, %i specs, ordinal scale', (x0, x1, numOfSpecs, x, width) => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ { x: 3, y: 2 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { x0, x1 },
+ },
+ ];
+ const rect = { x, width, y: 0, height: 100 };
+ expectAnnotationAtPosition(data, 'line', dataValues, rect, numOfSpecs, ScaleType.Ordinal);
+ });
+
+ it.each([
+ // x0, x1, numOfSpecs, x, width
+ [0, 0, 1, 0, 0],
+ [0, 1, 1, 0, 50],
+ [1, 2, 1, 50, 50],
+ [0, 2, 1, 0, 100],
+ [0, 1, 2, 0, 50],
+ [1, 2, 3, 50, 50],
+ ])('lines starting starting at %i, ending at %i, %i specs, continuous scale', (x0, x1, numOfSpecs, x, width) => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { x0, x1 },
+ },
+ ];
+ const rect = { x, width, y: 0, height: 100 };
+ expectAnnotationAtPosition(data, 'line', dataValues, rect, numOfSpecs, ScaleType.Linear);
+ });
+
+ it('out of bound annotations upper y', () => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { y1: 10 },
+ },
+ ];
+ const rect = { x: 0, width: 100, y: 0, height: 100 };
+ expectAnnotationAtPosition(data, 'line', dataValues, rect, 1, ScaleType.Linear);
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, 1, ScaleType.Linear);
+ });
+
+ it('out of bound annotations lower y', () => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { y0: -4 },
+ },
+ ];
+ const rect = { x: 0, width: 100, y: 0, height: 100 };
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, 1, ScaleType.Linear);
+ expectAnnotationAtPosition(data, 'line', dataValues, rect, 1, ScaleType.Linear);
+ });
+
+ it('out of bound annotations lower x', () => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { x0: -4 },
+ },
+ ];
+ const rect = { x: 0, width: 100, y: 0, height: 100 };
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, 1, ScaleType.Linear);
+ expectAnnotationAtPosition(data, 'line', dataValues, rect, 1, ScaleType.Linear);
+ });
+
+ it('out of bound annotations upper x', () => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { x1: 5 },
+ },
+ ];
+ const rect = { x: 0, width: 100, y: 0, height: 100 };
+ expectAnnotationAtPosition(data, 'bar', dataValues, rect, 1, ScaleType.Linear);
+ expectAnnotationAtPosition(data, 'line', dataValues, rect, 1, ScaleType.Linear);
+ });
+});
diff --git a/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts
index b62a4210c9..dbbf667f62 100644
--- a/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts
+++ b/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts
@@ -20,7 +20,7 @@ import { AnnotationTypes, RectAnnotationDatum, RectAnnotationSpec } from '../uti
import { Rotation } from '../../../utils/commons';
import { Dimensions } from '../../../utils/dimensions';
import { GroupId } from '../../../utils/ids';
-import { Scale } from '../../../scales';
+import { Scale, ScaleType } from '../../../scales';
import { Point } from '../../../utils/point';
import {
AnnotationTooltipFormatter,
@@ -98,14 +98,15 @@ export function computeRectAnnotationDimensions(
xScale: Scale,
enableHistogramMode: boolean,
barsPadding: number,
+ totalBarsInCluster: number,
): AnnotationRectProps[] | null {
const { dataValues } = annotationSpec;
-
const groupId = annotationSpec.groupId;
const yScale = yScales.get(groupId);
if (!yScale) {
return null;
}
+ const hasBars = totalBarsInCluster > 0;
const xDomain = xScale.domain;
const yDomain = yScale.domain;
@@ -115,16 +116,14 @@ export function computeRectAnnotationDimensions(
dataValues.forEach((dataValue: RectAnnotationDatum) => {
let { x0, x1, y0, y1 } = dataValue.coordinates;
-
// if everything is null, return; otherwise we coerce the other coordinates
if (x0 == null && x1 == null && y0 == null && y1 == null) {
return;
}
-
- if (x1 == null) {
- // if x1 is defined, we want the rect to draw to the end of the scale
- // if we're in histogram mode, extend domain end by min interval
- x1 = enableHistogramMode && !xScale.isSingleValue() ? lastX + xMinInterval : lastX;
+ x1 = x1 == null ? lastX : x1;
+ if (hasBars && xScale.type !== ScaleType.Ordinal) {
+ // if bar chart cover fully the last bar
+ x1 = x1 + xMinInterval;
}
if (x0 == null) {
@@ -133,22 +132,20 @@ export function computeRectAnnotationDimensions(
}
if (y0 == null) {
- // if y0 is defined, we want the rect to draw to the end of the scale
- y0 = yDomain[yDomain.length - 1];
+ // if y0 is not defined, start from the beginning of the yScale
+ y0 = yDomain[0];
}
if (y1 == null) {
- // if y1 is defined, we want the rect to draw to the start of the scale
- y1 = yDomain[0];
+ // if y1 is not defined, end the annotation at the end of the yScale
+ y1 = yDomain[yDomain.length - 1];
}
-
- const alignWithTick = xScale.bandwidth > 0 && !enableHistogramMode;
-
- let x0Scaled = scaleAndValidateDatum(x0, xScale, alignWithTick);
- let x1Scaled = scaleAndValidateDatum(x1, xScale, alignWithTick);
- const y0Scaled = scaleAndValidateDatum(y0, yScale, false);
- const y1Scaled = scaleAndValidateDatum(y1, yScale, false);
-
+ const barSpaces = totalBarsInCluster > 0 ? totalBarsInCluster : xScale.type === ScaleType.Ordinal ? 1 : 0;
+ let x0Scaled = scaleAndValidateDatum(x0, xScale, barSpaces);
+ let x1Scaled = scaleAndValidateDatum(x1, xScale, barSpaces, true);
+ // we don't consider bars in y scale
+ const y0Scaled = scaleAndValidateDatum(y0, yScale, 0);
+ const y1Scaled = scaleAndValidateDatum(y1, yScale, 0, true);
// TODO: surface this as a warning
if (x0Scaled === null || x1Scaled === null || y0Scaled === null || y1Scaled === null) {
return;
@@ -187,6 +184,5 @@ export function computeRectAnnotationDimensions(
details: dataValue.details,
});
});
-
return rectsProps;
}
diff --git a/src/chart_types/xy_chart/utils/axis_utils.test.ts b/src/chart_types/xy_chart/utils/axis_utils.test.ts
index 60beaf4710..5526381cc7 100644
--- a/src/chart_types/xy_chart/utils/axis_utils.test.ts
+++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts
@@ -18,7 +18,7 @@
import { XDomain } from '../domains/x_domain';
import { YDomain } from '../domains/y_domain';
-import { AxisSpec, DomainRange, AxisStyle } from './specs';
+import { AxisSpec, DomainRange, AxisStyle, DEFAULT_GLOBAL_ID } from './specs';
import { Position } from '../../../utils/commons';
import { LIGHT_THEME } from '../../../utils/themes/light_theme';
import { AxisId, GroupId } from '../../../utils/ids';
@@ -1452,7 +1452,7 @@ describe('Axis computational utils', () => {
showDuplicatedTicks: false,
chartType: 'xy_axis',
specType: 'axis',
- groupId: '__global__',
+ groupId: DEFAULT_GLOBAL_ID,
hide: false,
showOverlappingLabels: false,
showOverlappingTicks: false,
@@ -1492,7 +1492,7 @@ describe('Axis computational utils', () => {
showDuplicatedTicks: true,
chartType: 'xy_axis',
specType: 'axis',
- groupId: '__global__',
+ groupId: DEFAULT_GLOBAL_ID,
hide: false,
showOverlappingLabels: false,
showOverlappingTicks: false,
diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts
index e320aba0f2..6f8cf2b91b 100644
--- a/src/chart_types/xy_chart/utils/specs.ts
+++ b/src/chart_types/xy_chart/utils/specs.ts
@@ -668,7 +668,7 @@ export interface BaseAnnotationSpec<
D extends RectAnnotationDatum | LineAnnotationDatum,
S extends RectAnnotationStyle | LineAnnotationStyle
> extends Spec {
- chartType: ChartTypes;
+ chartType: typeof ChartTypes.XYAxis;
specType: typeof SpecTypes.Annotation;
/**
* Annotation type: line, rectangle, text
diff --git a/src/mocks/specs/specs.ts b/src/mocks/specs/specs.ts
index 06d830f329..e033aa2127 100644
--- a/src/mocks/specs/specs.ts
+++ b/src/mocks/specs/specs.ts
@@ -28,6 +28,10 @@ import {
BasicSeriesSpec,
SeriesTypes,
BubbleSeriesSpec,
+ LineAnnotationSpec,
+ RectAnnotationSpec,
+ AnnotationTypes,
+ AnnotationDomainTypes,
} from '../../chart_types/xy_chart/utils/specs';
import { ScaleType } from '../../scales';
import { ChartTypes } from '../../chart_types';
@@ -221,6 +225,17 @@ export class MockSeriesSpec {
return MockSeriesSpec.barBase;
}
}
+ static byTypePartial(type?: 'line' | 'bar' | 'area') {
+ switch (type) {
+ case 'line':
+ return MockSeriesSpec.line;
+ case 'area':
+ return MockSeriesSpec.area;
+ case 'bar':
+ default:
+ return MockSeriesSpec.bar;
+ }
+ }
}
export class MockSeriesSpecs {
@@ -264,7 +279,53 @@ export class MockGlobalSpec {
theme: LIGHT_THEME,
};
+ private static readonly settingsBaseNoMargings: SettingsSpec = {
+ ...MockGlobalSpec.settingsBase,
+ theme: {
+ ...LIGHT_THEME,
+ chartMargins: { top: 0, left: 0, right: 0, bottom: 0 },
+ chartPaddings: { top: 0, left: 0, right: 0, bottom: 0 },
+ scales: {
+ barsPadding: 0,
+ },
+ },
+ };
+
static settings(partial?: Partial): SettingsSpec {
return mergePartial(MockGlobalSpec.settingsBase, partial, { mergeOptionalPartialValues: true });
}
+ static settingsNoMargins(partial?: Partial): SettingsSpec {
+ return mergePartial(MockGlobalSpec.settingsBaseNoMargings, partial, {
+ mergeOptionalPartialValues: true,
+ });
+ }
+}
+
+/** @internal */
+export class MockAnnotationSpec {
+ private static readonly lineBase: LineAnnotationSpec = {
+ id: 'line_annotation_1',
+ groupId: DEFAULT_GLOBAL_ID,
+ chartType: ChartTypes.XYAxis,
+ specType: SpecTypes.Annotation,
+ annotationType: AnnotationTypes.Line,
+ dataValues: [],
+ domainType: AnnotationDomainTypes.XDomain,
+ };
+
+ private static readonly rectBase: RectAnnotationSpec = {
+ id: 'rect_annotation_1',
+ groupId: DEFAULT_GLOBAL_ID,
+ chartType: ChartTypes.XYAxis,
+ specType: SpecTypes.Annotation,
+ annotationType: AnnotationTypes.Rectangle,
+ dataValues: [],
+ };
+
+ static line(partial?: Partial): LineAnnotationSpec {
+ return mergePartial(MockAnnotationSpec.lineBase, partial, { mergeOptionalPartialValues: true });
+ }
+ static rect(partial?: Partial): RectAnnotationSpec {
+ return mergePartial(MockAnnotationSpec.rectBase, partial, { mergeOptionalPartialValues: true });
+ }
}
diff --git a/src/mocks/store/index.ts b/src/mocks/store/index.ts
new file mode 100644
index 0000000000..4702768221
--- /dev/null
+++ b/src/mocks/store/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License. */
+
+/** @internal */
+export * from './store';
diff --git a/src/mocks/store/store.ts b/src/mocks/store/store.ts
new file mode 100644
index 0000000000..14110e9398
--- /dev/null
+++ b/src/mocks/store/store.ts
@@ -0,0 +1,54 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License. */
+
+import { chartStoreReducer, GlobalChartState } from '../../state/chart_state';
+import { createStore, Store } from 'redux';
+import { specParsing, upsertSpec, specParsed } from '../../state/actions/specs';
+import { Spec } from '../../specs';
+import { updateParentDimensions } from '../../state/actions/chart_settings';
+
+/** @internal */
+export class MockStore {
+ static default(
+ { width, height, top, left } = { width: 100, height: 100, top: 0, left: 0 },
+ chartId = 'chartId',
+ ): Store {
+ const storeReducer = chartStoreReducer(chartId);
+ const store = createStore(storeReducer);
+ store.dispatch(updateParentDimensions({ width, height, top, left }));
+ return store;
+ }
+
+ static addSpecs(specs: Spec | Array, store: Store) {
+ store.dispatch(specParsing());
+ if (Array.isArray(specs)) {
+ const actions = specs.map(upsertSpec);
+ actions.forEach(store.dispatch);
+ } else {
+ store.dispatch(upsertSpec(specs));
+ }
+ store.dispatch(specParsed());
+ }
+
+ static updateDimensions(
+ { width, height, top, left } = { width: 100, height: 100, top: 0, left: 0 },
+ store: Store,
+ ) {
+ store.dispatch(updateParentDimensions({ width, height, top, left }));
+ }
+}