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 fe305c8bcd..3233ed3dcc 100644
--- a/.playground/index.html
+++ b/.playground/index.html
@@ -2,44 +2,22 @@
diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-linear-bar-chart-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-linear-bar-chart-visually-looks-correct-1-snap.png
index 63da9acf86..4cba31aca5 100644
Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-linear-bar-chart-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-linear-bar-chart-visually-looks-correct-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-linear-line-chart-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-linear-line-chart-visually-looks-correct-1-snap.png
index 6446828588..d9470c9625 100644
Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-linear-line-chart-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-linear-line-chart-visually-looks-correct-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-ordinal-bar-chart-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-ordinal-bar-chart-visually-looks-correct-1-snap.png
index 3a79d31379..33bfc727be 100644
Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-ordinal-bar-chart-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-ordinal-bar-chart-visually-looks-correct-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-tooltip-visibility-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-tooltip-visibility-visually-looks-correct-1-snap.png
index 63da9acf86..a62a0bedd0 100644
Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-tooltip-visibility-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-annotations-rects-tooltip-visibility-visually-looks-correct-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-test-histogram-mode-linear-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-test-histogram-mode-linear-visually-looks-correct-1-snap.png
index 2e045a44d3..cd398778ae 100644
Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-test-histogram-mode-linear-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-bar-chart-test-histogram-mode-linear-visually-looks-correct-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-0-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-0-1-snap.png
index 2e045a44d3..cd398778ae 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-0-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-0-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-180-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-180-1-snap.png
index 8b7d6c19dd..44fde0aa28 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-180-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-180-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-90-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-90-1-snap.png
index 70375cac0c..c662e46d59 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-90-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-90-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-negative-90-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-negative-90-1-snap.png
index 25a434a410..5a91e0f34d 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-negative-90-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-false-rotation-negative-90-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-0-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-0-1-snap.png
index 02a43108d9..ae5a133d8c 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-0-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-0-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-180-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-180-1-snap.png
index 142c764920..58ada2c3de 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-180-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-180-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-90-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-90-1-snap.png
index a3f4a5370e..c1ed6c452e 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-90-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-90-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-negative-90-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-negative-90-1-snap.png
index e55d1d04f9..78deaa6b9d 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-negative-90-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-enable-histogram-mode-is-true-rotation-negative-90-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-center-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-center-1-snap.png
index 77aabe9da9..547ec98609 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-center-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-center-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-end-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-end-1-snap.png
index c2946e9e94..9a87134d13 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-end-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-end-1-snap.png differ
diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-start-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-start-1-snap.png
index 9203dc5900..753571197f 100644
Binary files a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-start-1-snap.png and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-test-histogram-mode-linear-point-alignment-start-1-snap.png differ
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..1a468a794f 100644
--- a/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx
+++ b/src/chart_types/xy_chart/annotations/annotation_marker.test.tsx
@@ -23,9 +23,10 @@ import { DEFAULT_ANNOTATION_LINE_STYLE } from '../../../utils/themes/theme';
import { Dimensions } from '../../../utils/dimensions';
import { GroupId } from '../../../utils/ids';
import { Scale, ScaleType, ScaleContinuous } from '../../../scales';
-import { computeLineAnnotationDimensions, AnnotationLineProps } from './line_annotation_tooltip';
-import { ChartTypes } from '../..';
import { SpecTypes } from '../../../specs/settings';
+import { computeLineAnnotationDimensions } from './line/dimensions';
+import { AnnotationLineProps } from './line/types';
+import { ChartTypes } from '../..';
describe('annotation marker', () => {
const groupId = 'foo-group';
@@ -75,7 +76,6 @@ describe('annotation marker', () => {
yScales,
xScale,
Position.Left,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -130,7 +130,6 @@ describe('annotation marker', () => {
yScales,
xScale,
Position.Left,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -184,7 +183,6 @@ describe('annotation marker', () => {
yScales,
xScale,
Position.Bottom,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
diff --git a/src/chart_types/xy_chart/annotations/annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/annotation_tooltip.ts
deleted file mode 100644
index 7ee3f9946f..0000000000
--- a/src/chart_types/xy_chart/annotations/annotation_tooltip.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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 { Dimensions } from '../../../utils/dimensions';
-import { Position } from '../../../utils/commons';
-
-/** @internal */
-export function getFinalAnnotationTooltipPosition(
- /** the dimensions of the chart parent container */
- container: Dimensions,
- chartDimensions: Dimensions,
- /** the dimensions of the tooltip container */
- tooltip: Dimensions,
- /** the tooltip computed position not adjusted within chart bounds */
- tooltipAnchor: { top: number; left: number },
- /** the width of the tooltip portal container */
- portalWidth: number,
- padding = 10,
-): {
- left: string | null;
- top: string | null;
- anchor: 'left' | 'right';
-} {
- let left = 0;
- let anchor: Position = Position.Left;
-
- const annotationXOffset = window.pageXOffset + container.left + chartDimensions.left + tooltipAnchor.left;
- if (chartDimensions.left + tooltipAnchor.left + portalWidth + padding >= container.width) {
- left = annotationXOffset - portalWidth - padding;
- anchor = Position.Right;
- } else {
- left = annotationXOffset + padding;
- }
- let top = window.pageYOffset + container.top + chartDimensions.top + tooltipAnchor.top;
- if (chartDimensions.top + tooltipAnchor.top + tooltip.height + padding >= container.height) {
- top -= tooltip.height + padding;
- } else {
- top += padding;
- }
-
- return {
- left: `${Math.round(left)}px`,
- top: `${Math.round(top)}px`,
- anchor,
- };
-}
diff --git a/src/chart_types/xy_chart/annotations/annotation_utils.ts b/src/chart_types/xy_chart/annotations/annotation_utils.ts
deleted file mode 100644
index 8cf152eeae..0000000000
--- a/src/chart_types/xy_chart/annotations/annotation_utils.ts
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * 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 {
- AnnotationDomainType,
- AnnotationDomainTypes,
- AnnotationSpec,
- AnnotationType,
- AxisSpec,
- HistogramModeAlignments,
- isLineAnnotation,
- isRectAnnotation,
-} from '../utils/specs';
-import { Dimensions } from '../../../utils/dimensions';
-import { AnnotationId, GroupId } from '../../../utils/ids';
-import { Scale, ScaleType } from '../../../scales';
-import { computeXScaleOffset, getAxesSpecForSpecId, isHorizontalRotation, getSpecsById } from '../state/utils';
-import { Point } from '../../../utils/point';
-import {
- computeLineAnnotationTooltipState,
- AnnotationLineProps,
- computeLineAnnotationDimensions,
-} from './line_annotation_tooltip';
-import {
- computeRectAnnotationTooltipState,
- AnnotationRectProps,
- computeRectAnnotationDimensions,
-} from './rect_annotation_tooltip';
-import { Rotation, Position, Color } from '../../../utils/commons';
-
-export type AnnotationTooltipFormatter = (details?: string) => JSX.Element | null;
-
-/** @internal */
-export type AnnotationTooltipState = AnnotationTooltipVisibleState | AnnotationTooltipHiddenState;
-
-/** @internal */
-export interface AnnotationTooltipVisibleState {
- isVisible: true;
- annotationType: AnnotationType;
- header?: string;
- details?: string;
- anchor: { position?: Position; top: number; left: number };
- renderTooltip?: AnnotationTooltipFormatter;
-}
-
-/** @internal */
-export interface AnnotationTooltipHiddenState {
- isVisible: false;
-}
-/**
- * The header and description strings for an Annotation
- */
-export interface AnnotationDetails {
- headerText?: string;
- detailsText?: string;
-}
-
-/**
- * The marker for an Annotation. Usually a JSX element
- */
-export interface AnnotationMarker {
- icon: JSX.Element;
- position: { top: number; left: number };
- dimension: { width: number; height: number };
- color: Color;
-}
-
-/** @internal */
-export type AnnotationDimensions = AnnotationLineProps[] | AnnotationRectProps[];
-
-/** @internal */
-export type Bounds = { startX: number; endX: number; startY: number; endY: number };
-
-/** @internal */
-export function scaleAndValidateDatum(dataValue: any, scale: Scale, alignWithTick: boolean): number | null {
- const isContinuous = scale.type !== ScaleType.Ordinal;
- const scaledValue = scale.scale(dataValue);
- // d3.scale will return 0 for '', rendering the line incorrectly at 0
- if (scaledValue === null || (isContinuous && dataValue === '')) {
- return null;
- }
-
- if (isContinuous) {
- const [domainStart, domainEnd] = scale.domain;
-
- // 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;
- }
- }
-
- return scaledValue;
-}
-
-/** @internal */
-export function getAnnotationAxis(
- axesSpecs: AxisSpec[],
- groupId: GroupId,
- domainType: AnnotationDomainType,
- chartRotation: Rotation,
-): Position | null {
- const { xAxis, yAxis } = getAxesSpecForSpecId(axesSpecs, groupId);
- const isHorizontalRotated = isHorizontalRotation(chartRotation);
- const isXDomainAnnotation = isXDomain(domainType);
- const annotationAxis = isXDomainAnnotation ? xAxis : yAxis;
- const rotatedAnnotation = isHorizontalRotated ? annotationAxis : isXDomainAnnotation ? yAxis : xAxis;
- return rotatedAnnotation ? rotatedAnnotation.position : null;
-}
-
-/** @internal */
-export function computeClusterOffset(totalBarsInCluster: number, barsShift: number, bandwidth: number): number {
- if (totalBarsInCluster > 1) {
- return barsShift - bandwidth / 2;
- }
-
- return 0;
-}
-
-/** @internal */
-export function isXDomain(domainType: AnnotationDomainType): boolean {
- return domainType === AnnotationDomainTypes.XDomain;
-}
-
-/** @internal */
-export function getRotatedCursor(
- /** the cursor position relative to the projection area */
- cursorPosition: Point,
- chartDimensions: Dimensions,
- chartRotation: Rotation,
-): Point {
- const { x, y } = cursorPosition;
- const { height, width } = chartDimensions;
- switch (chartRotation) {
- case 0:
- return { x, y };
- case 90:
- return { x: y, y: width - x };
- case -90:
- return { x: height - y, y: x };
- case 180:
- return { x: width - x, y: height - y };
- }
-}
-
-/** @internal */
-export function computeAnnotationDimensions(
- annotations: AnnotationSpec[],
- chartDimensions: Dimensions,
- chartRotation: Rotation,
- yScales: Map
,
- xScale: Scale,
- axesSpecs: AxisSpec[],
- totalBarsInCluster: number,
- enableHistogramMode: boolean,
-): Map {
- const annotationDimensions = new Map();
-
- const barsShift = (totalBarsInCluster * xScale.bandwidth) / 2;
-
- const band = xScale.bandwidth / (1 - xScale.barsPadding);
- const halfPadding = (band - xScale.bandwidth) / 2;
- const barsPadding = halfPadding * totalBarsInCluster;
- const clusterOffset = computeClusterOffset(totalBarsInCluster, barsShift, xScale.bandwidth);
-
- // Annotations should always align with the axis line in histogram mode
- const xScaleOffset = computeXScaleOffset(xScale, enableHistogramMode, HistogramModeAlignments.Start);
- annotations.forEach((annotationSpec) => {
- const { id } = annotationSpec;
- if (isLineAnnotation(annotationSpec)) {
- const { groupId, domainType } = annotationSpec;
- const annotationAxisPosition = getAnnotationAxis(axesSpecs, groupId, domainType, chartRotation);
-
- if (!annotationAxisPosition) {
- return;
- }
- const dimensions = computeLineAnnotationDimensions(
- annotationSpec,
- chartDimensions,
- chartRotation,
- yScales,
- xScale,
- annotationAxisPosition,
- xScaleOffset - clusterOffset,
- enableHistogramMode,
- );
-
- if (dimensions) {
- annotationDimensions.set(id, dimensions);
- }
- } else if (isRectAnnotation(annotationSpec)) {
- const dimensions = computeRectAnnotationDimensions(
- annotationSpec,
- yScales,
- xScale,
- enableHistogramMode,
- barsPadding,
- );
-
- if (dimensions) {
- annotationDimensions.set(id, dimensions);
- }
- }
- });
-
- return annotationDimensions;
-}
-
-/** @internal */
-export function computeAnnotationTooltipState(
- cursorPosition: Point,
- annotationDimensions: Map,
- annotationSpecs: AnnotationSpec[],
- chartRotation: Rotation,
- axesSpecs: AxisSpec[],
- chartDimensions: Dimensions,
-): AnnotationTooltipState | null {
- for (const [annotationId, annotationDimension] of annotationDimensions) {
- const spec = getSpecsById(annotationSpecs, annotationId);
-
- if (!spec || spec.hideTooltips) {
- continue;
- }
-
- const groupId = spec.groupId;
-
- if (isLineAnnotation(spec)) {
- if (spec.hideLines) {
- continue;
- }
- const lineAnnotationTooltipState = computeLineAnnotationTooltipState(
- cursorPosition,
- annotationDimension as AnnotationLineProps[],
- groupId,
- spec.domainType,
- axesSpecs,
- );
-
- if (lineAnnotationTooltipState.isVisible) {
- return lineAnnotationTooltipState;
- }
- } else if (isRectAnnotation(spec)) {
- const rectAnnotationTooltipState = computeRectAnnotationTooltipState(
- cursorPosition,
- annotationDimension as AnnotationRectProps[],
- chartRotation,
- chartDimensions,
- spec.renderTooltip,
- );
-
- if (rectAnnotationTooltipState.isVisible) {
- return rectAnnotationTooltipState;
- }
- }
- }
-
- return null;
-}
diff --git a/src/chart_types/xy_chart/annotations/line/dimensions.integration.test.ts b/src/chart_types/xy_chart/annotations/line/dimensions.integration.test.ts
new file mode 100644
index 0000000000..fbe6622adc
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/line/dimensions.integration.test.ts
@@ -0,0 +1,163 @@
+/*
+ * 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 { AnnotationDomainTypes } from '../../utils/specs';
+import { Position } from '../../../../utils/commons';
+
+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);
+ });
+
+ it('histogramMode with line after the max value but before the max + minInterval ', () => {
+ const store = MockStore.default();
+ const settings = MockGlobalSpec.settingsNoMargins({
+ xDomain: {
+ min: 0,
+ max: 9,
+ minInterval: 1,
+ },
+ });
+ const spec = MockSeriesSpec.histogramBar({
+ xScaleType: ScaleType.Linear,
+ data: [
+ {
+ x: 0,
+ y: 1,
+ },
+ {
+ x: 9,
+ y: 20,
+ },
+ ],
+ });
+ const annotation = MockAnnotationSpec.line({
+ domainType: AnnotationDomainTypes.XDomain,
+ dataValues: [{ dataValue: 9.5, details: 'foo' }],
+ });
+
+ MockStore.addSpecs([settings, spec, annotation], store);
+ const annotations = computeAnnotationDimensionsSelector(store.getState());
+ expect(annotations.get(annotation.id)).toEqual([
+ {
+ anchor: {
+ top: 100,
+ left: 95,
+ position: Position.Bottom,
+ },
+ linePathPoints: {
+ start: { x1: 95, y1: 100 },
+ end: { x2: 95, y2: 0 },
+ },
+ details: { detailsText: 'foo', headerText: '9.5' },
+ marker: undefined,
+ },
+ ]);
+ });
+});
diff --git a/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/line/dimensions.ts
similarity index 62%
rename from src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts
rename to src/chart_types/xy_chart/annotations/line/dimensions.ts
index e9c72a1b35..28f56de25a 100644
--- a/src/chart_types/xy_chart/annotations/line_annotation_tooltip.ts
+++ b/src/chart_types/xy_chart/annotations/line/dimensions.ts
@@ -16,67 +16,16 @@
* specific language governing permissions and limitations
* under the License. */
-import {
- AnnotationDomainType,
- AnnotationDomainTypes,
- AnnotationTypes,
- LineAnnotationSpec,
- LineAnnotationDatum,
- AxisSpec,
-} from '../utils/specs';
-import { Position, Rotation } from '../../../utils/commons';
-import {
- AnnotationTooltipState,
- AnnotationDetails,
- AnnotationMarker,
- scaleAndValidateDatum,
- isXDomain,
- Bounds,
-} from './annotation_utils';
-import { isHorizontalRotation, getAxesSpecForSpecId } from '../state/utils';
-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';
-
-/** @internal */
-export type AnnotationLinePosition = [number, number, number, number];
-
-/**
- * Start and end points of a line annotation
- * @internal
- */
-export interface AnnotationLinePathPoints {
- /** x1,y1 the start point anchored to the linked axis */
- start: {
- x1: number;
- y1: number;
- };
- /** x2,y2 the end point */
- end: {
- x2: number;
- y2: number;
- };
-}
-
-/** @internal */
-export interface AnnotationLineProps {
- /** the position of the start point relative to the Chart */
- anchor: {
- position: Position;
- top: number;
- left: number;
- };
- /**
- * The path points of a line annotation
- */
- linePathPoints: AnnotationLinePathPoints;
- details: AnnotationDetails;
- marker?: AnnotationMarker;
-}
+import { AnnotationDomainTypes, LineAnnotationSpec, LineAnnotationDatum } from '../../utils/specs';
+import { Position, Rotation } from '../../../../utils/commons';
+import { AnnotationMarker } from '../types';
+import { isHorizontalRotation } from '../../state/utils';
+import { Dimensions } from '../../../../utils/dimensions';
+import { Scale } from '../../../../scales';
+import { GroupId } from '../../../../utils/ids';
+import { AnnotationLineProps, AnnotationLinePathPoints } from './types';
+import { isContinuousScale, isBandScale } from '../../../../scales/types';
+import { computeXScaleOffset } from '../../state/utils';
/** @internal */
export const DEFAULT_LINE_OVERFLOW = 0;
@@ -85,7 +34,7 @@ function computeYDomainLineAnnotationDimensions(
dataValues: LineAnnotationDatum[],
yScale: Scale,
chartRotation: Rotation,
- axisPosition: Position,
+ axisPosition: Position | null,
chartDimensions: Dimensions,
lineColor: string,
marker?: JSX.Element,
@@ -94,7 +43,10 @@ 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.Left : Position.Bottom) : axisPosition;
const lineProps: AnnotationLineProps[] = [];
dataValues.forEach((datum: LineAnnotationDatum) => {
@@ -113,11 +65,12 @@ function computeYDomainLineAnnotationDimensions(
const [domainStart, domainEnd] = yScale.domain;
// avoid rendering annotation with values outside the scale domain
- if (domainStart > dataValue || domainEnd < dataValue) {
+ if (dataValue < domainStart || dataValue > domainEnd) {
return;
}
+
const anchor = {
- position: axisPosition,
+ position: anchorPosition,
top: 0,
left: 0,
};
@@ -129,7 +82,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 +108,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 +161,10 @@ function computeXDomainLineAnnotationDimensions(
dataValues: LineAnnotationDatum[],
xScale: Scale,
chartRotation: Rotation,
- axisPosition: Position,
+ axisPosition: Position | null,
chartDimensions: Dimensions,
lineColor: string,
- xScaleOffset: number,
- enableHistogramMode: boolean,
+ isHistogramMode: boolean,
marker?: JSX.Element,
markerDimension = { width: 0, height: 0 },
): AnnotationLineProps[] {
@@ -220,19 +172,46 @@ 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);
-
- if (scaledXValue == null) {
+ let annotationValueXposition = xScale.scale(dataValue);
+ if (annotationValueXposition == null) {
+ return;
+ }
+ if (isContinuousScale(xScale) && typeof dataValue === 'number') {
+ const minDomain = xScale.domain[0];
+ const maxDomain = isHistogramMode ? xScale.domain[1] + xScale.minInterval : xScale.domain[1];
+ if (dataValue < minDomain || dataValue > maxDomain) {
+ return;
+ }
+ if (isHistogramMode) {
+ const offset = computeXScaleOffset(xScale, true);
+ const pureScaledValue = xScale.pureScale(dataValue);
+ if (pureScaledValue == null) {
+ return;
+ }
+ annotationValueXposition = pureScaledValue - offset;
+ } else {
+ annotationValueXposition = annotationValueXposition + (xScale.bandwidth * xScale.totalBarsInCluster) / 2;
+ }
+ } else if (isBandScale(xScale)) {
+ if (isHistogramMode) {
+ const padding = (xScale.step - xScale.originalBandwidth) / 2;
+ annotationValueXposition = annotationValueXposition - padding;
+ } else {
+ annotationValueXposition = annotationValueXposition + xScale.originalBandwidth / 2;
+ }
+ } else {
+ return;
+ }
+ if (isNaN(annotationValueXposition) || annotationValueXposition == null) {
return;
}
-
- const offset = xScale.bandwidth / 2 - xScaleOffset;
- const annotationValueXposition = scaledXValue + offset;
const markerPosition = { top: 0, left: 0 };
const linePathPoints: AnnotationLinePathPoints = {
@@ -240,14 +219,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 +252,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 +309,8 @@ export function computeLineAnnotationDimensions(
chartRotation: Rotation,
yScales: Map,
xScale: Scale,
- axisPosition: Position,
- xScaleOffset: number,
- enableHistogramMode: boolean,
+ axisPosition: Position | null,
+ isHistogramMode: boolean,
): AnnotationLineProps[] | null {
const { domainType, dataValues, marker, markerDimensions, hideLines } = annotationSpec;
@@ -341,8 +319,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(
@@ -352,8 +330,7 @@ export function computeLineAnnotationDimensions(
axisPosition,
chartDimensions,
lineColor,
- xScaleOffset,
- enableHistogramMode,
+ isHistogramMode,
marker,
markerDimensions,
);
@@ -376,100 +353,3 @@ export function computeLineAnnotationDimensions(
markerDimensions,
);
}
-
-/** @internal */
-export function getAnnotationLineTooltipXOffset(chartRotation: Rotation, axisPosition: Position): number {
- let xOffset = 0;
- const isChartHorizontalRotation = isHorizontalRotation(chartRotation);
-
- if (isHorizontalAxis(axisPosition)) {
- xOffset = isChartHorizontalRotation ? 50 : 0;
- } else {
- if (isChartHorizontalRotation) {
- xOffset = axisPosition === Position.Right ? 100 : 0;
- } else {
- xOffset = 50;
- }
- }
-
- return xOffset;
-}
-
-/** @internal */
-export function getAnnotationLineTooltipYOffset(chartRotation: Rotation, axisPosition: Position): number {
- let yOffset = 0;
- const isChartHorizontalRotation = isHorizontalRotation(chartRotation);
-
- if (isHorizontalAxis(axisPosition)) {
- if (isChartHorizontalRotation) {
- yOffset = axisPosition === Position.Top ? 0 : 100;
- } else {
- yOffset = 50;
- }
- } else {
- yOffset = isChartHorizontalRotation ? 50 : 100;
- }
-
- return yOffset;
-}
-
-/** @internal */
-export function isVerticalAnnotationLine(isXDomainAnnotation: boolean, isHorizontalChartRotation: boolean): boolean {
- if (isXDomainAnnotation) {
- return isHorizontalChartRotation;
- }
-
- return !isHorizontalChartRotation;
-}
-
-/**
- * Checks if the cursorPosition is within the line annotation marker
- * @param cursorPosition the cursor position relative to the projected area
- * @param marker the line annotation marker
- */
-function isWithinLineMarkerBounds(cursorPosition: Point, marker: AnnotationMarker): boolean {
- const { top, left } = marker.position;
- const { width, height } = marker.dimension;
- const markerRect: Bounds = { startX: left, startY: top, endX: left + width, endY: top + height };
- return isWithinRectBounds(cursorPosition, markerRect);
-}
-
-/** @internal */
-export function computeLineAnnotationTooltipState(
- cursorPosition: Point,
- annotationLines: AnnotationLineProps[],
- groupId: GroupId,
- domainType: AnnotationDomainType,
- axesSpecs: AxisSpec[],
-): AnnotationTooltipState {
- const { xAxis, yAxis } = getAxesSpecForSpecId(axesSpecs, groupId);
- const isXDomainAnnotation = isXDomain(domainType);
- const annotationAxis = isXDomainAnnotation ? xAxis : yAxis;
-
- if (!annotationAxis) {
- return {
- isVisible: false,
- };
- }
-
- const totalAnnotationLines = annotationLines.length;
- for (let i = 0; i < totalAnnotationLines; i++) {
- const line = annotationLines[i];
- const isWithinBounds = line.marker && isWithinLineMarkerBounds(cursorPosition, line.marker);
-
- if (isWithinBounds) {
- return {
- annotationType: AnnotationTypes.Line,
- isVisible: true,
- anchor: {
- ...line.anchor,
- },
- ...(line.details && { header: line.details.headerText }),
- ...(line.details && { details: line.details.detailsText }),
- };
- }
- }
- return {
- isVisible: false,
- };
-}
diff --git a/src/chart_types/xy_chart/annotations/line/marker.test.tsx b/src/chart_types/xy_chart/annotations/line/marker.test.tsx
new file mode 100644
index 0000000000..5ba827f24a
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/line/marker.test.tsx
@@ -0,0 +1,216 @@
+/*
+ * 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 React from 'react';
+import { AnnotationDomainTypes, AnnotationSpec, AnnotationTypes } from '../../utils/specs';
+import { Position, Rotation } from '../../../../utils/commons';
+import { DEFAULT_ANNOTATION_LINE_STYLE } from '../../../../utils/themes/theme';
+import { Dimensions } from '../../../../utils/dimensions';
+import { GroupId } from '../../../../utils/ids';
+import { Scale, ScaleType, ScaleContinuous } from '../../../../scales';
+import { computeLineAnnotationDimensions } from './dimensions';
+import { AnnotationLineProps } from './types';
+import { ChartTypes } from '../../..';
+import { SpecTypes } from '../../../../specs/settings';
+
+describe('annotation marker', () => {
+ const groupId = 'foo-group';
+
+ const minRange = 0;
+ const maxRange = 100;
+
+ const continuousData = [0, 10];
+ const continuousScale = new ScaleContinuous({
+ type: ScaleType.Linear,
+ domain: continuousData,
+ range: [minRange, maxRange],
+ });
+
+ const chartDimensions: Dimensions = {
+ width: 10,
+ height: 20,
+ top: 5,
+ left: 15,
+ };
+
+ const yScales: Map = new Map();
+ yScales.set(groupId, continuousScale);
+
+ const xScale: Scale = continuousScale;
+
+ test('should compute line annotation dimensions with marker if defined (y domain)', () => {
+ const chartRotation: Rotation = 0;
+
+ const id = 'foo-line';
+ const lineAnnotation: AnnotationSpec = {
+ chartType: ChartTypes.XYAxis,
+ specType: SpecTypes.Annotation,
+ annotationType: AnnotationTypes.Line,
+ id,
+ domainType: AnnotationDomainTypes.YDomain,
+ dataValues: [{ dataValue: 2, details: 'foo' }],
+ groupId,
+ style: DEFAULT_ANNOTATION_LINE_STYLE,
+ marker: ,
+ };
+
+ const dimensions = computeLineAnnotationDimensions(
+ lineAnnotation,
+ chartDimensions,
+ chartRotation,
+ yScales,
+ xScale,
+ Position.Left,
+ false,
+ );
+ const expectedDimensions: AnnotationLineProps[] = [
+ {
+ anchor: {
+ position: Position.Left,
+ top: 20,
+ left: 0,
+ },
+ linePathPoints: {
+ start: {
+ x1: 0,
+ y1: 20,
+ },
+ end: {
+ x2: 10,
+ y2: 20,
+ },
+ },
+ details: { detailsText: 'foo', headerText: '2' },
+
+ marker: {
+ icon: ,
+ color: '#777',
+ dimension: { width: 0, height: 0 },
+ position: { left: -0, top: 20 },
+ },
+ },
+ ];
+ expect(dimensions).toEqual(expectedDimensions);
+ });
+
+ test('should compute line annotation dimensions with marker if defined (y domain: 180 deg rotation)', () => {
+ const chartRotation: Rotation = 180;
+
+ const lineAnnotation: AnnotationSpec = {
+ chartType: ChartTypes.XYAxis,
+ specType: SpecTypes.Annotation,
+ annotationType: AnnotationTypes.Line,
+ id: 'foo-line',
+ domainType: AnnotationDomainTypes.YDomain,
+ dataValues: [{ dataValue: 2, details: 'foo' }],
+ groupId,
+ style: DEFAULT_ANNOTATION_LINE_STYLE,
+ marker: ,
+ };
+
+ const dimensions = computeLineAnnotationDimensions(
+ lineAnnotation,
+ chartDimensions,
+ chartRotation,
+ yScales,
+ xScale,
+ Position.Left,
+ false,
+ );
+ const expectedDimensions: AnnotationLineProps[] = [
+ {
+ anchor: {
+ position: Position.Left,
+ top: 0,
+ left: 0,
+ },
+ linePathPoints: {
+ start: {
+ x1: 0,
+ y1: 20,
+ },
+ end: {
+ x2: 10,
+ y2: 20,
+ },
+ },
+ details: { detailsText: 'foo', headerText: '2' },
+ marker: {
+ icon: ,
+ color: '#777',
+ dimension: { width: 0, height: 0 },
+ position: { left: -0, top: 0 },
+ },
+ },
+ ];
+ expect(dimensions).toEqual(expectedDimensions);
+ });
+
+ test('should compute line annotation dimensions with marker if defined (x domain)', () => {
+ const chartRotation: Rotation = 0;
+
+ const lineAnnotation: AnnotationSpec = {
+ chartType: ChartTypes.XYAxis,
+ specType: SpecTypes.Annotation,
+ annotationType: AnnotationTypes.Line,
+ id: 'foo-line',
+ domainType: AnnotationDomainTypes.XDomain,
+ dataValues: [{ dataValue: 2, details: 'foo' }],
+ groupId,
+ style: DEFAULT_ANNOTATION_LINE_STYLE,
+ marker: ,
+ };
+
+ const dimensions = computeLineAnnotationDimensions(
+ lineAnnotation,
+ chartDimensions,
+ chartRotation,
+ yScales,
+ xScale,
+ Position.Bottom,
+ false,
+ );
+ const expectedDimensions: AnnotationLineProps[] = [
+ {
+ anchor: {
+ position: Position.Bottom,
+ top: 20,
+ left: 20,
+ },
+ details: { detailsText: 'foo', headerText: '2' },
+ linePathPoints: {
+ start: {
+ x1: 20,
+ y1: 20,
+ },
+ end: {
+ x2: 20,
+ y2: 0,
+ },
+ },
+ marker: {
+ icon: ,
+ color: '#777',
+ dimension: { width: 0, height: 0 },
+ position: { top: 20, left: 20 },
+ },
+ },
+ ];
+ expect(dimensions).toEqual(expectedDimensions);
+ });
+});
diff --git a/src/chart_types/xy_chart/annotations/line/tooltip.ts b/src/chart_types/xy_chart/annotations/line/tooltip.ts
new file mode 100644
index 0000000000..e91fb5c123
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/line/tooltip.ts
@@ -0,0 +1,78 @@
+/*
+ * 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 { AnnotationLineProps } from './types';
+import { isWithinRectBounds } from '../rect/dimensions';
+import { isXDomain } from '../utils';
+import { AnnotationTooltipState, AnnotationMarker, Bounds } from '../types';
+import { getAxesSpecForSpecId } from '../../state/utils';
+import { AnnotationDomainType, AnnotationTypes, AxisSpec } from '../../utils/specs';
+import { GroupId } from '../../../../utils/ids';
+import { Point } from '../../../../utils/point';
+
+/** @internal */
+export function computeLineAnnotationTooltipState(
+ cursorPosition: Point,
+ annotationLines: AnnotationLineProps[],
+ groupId: GroupId,
+ domainType: AnnotationDomainType,
+ axesSpecs: AxisSpec[],
+): AnnotationTooltipState {
+ const { xAxis, yAxis } = getAxesSpecForSpecId(axesSpecs, groupId);
+ const isXDomainAnnotation = isXDomain(domainType);
+ const annotationAxis = isXDomainAnnotation ? xAxis : yAxis;
+
+ if (!annotationAxis) {
+ return {
+ isVisible: false,
+ };
+ }
+
+ const totalAnnotationLines = annotationLines.length;
+ for (let i = 0; i < totalAnnotationLines; i++) {
+ const line = annotationLines[i];
+ const isWithinBounds = line.marker && isWithinLineMarkerBounds(cursorPosition, line.marker);
+
+ if (isWithinBounds) {
+ return {
+ annotationType: AnnotationTypes.Line,
+ isVisible: true,
+ anchor: {
+ ...line.anchor,
+ },
+ ...(line.details && { header: line.details.headerText }),
+ ...(line.details && { details: line.details.detailsText }),
+ };
+ }
+ }
+ return {
+ isVisible: false,
+ };
+}
+
+/**
+ * Checks if the cursorPosition is within the line annotation marker
+ * @param cursorPosition the cursor position relative to the projected area
+ * @param marker the line annotation marker
+ */
+function isWithinLineMarkerBounds(cursorPosition: Point, marker: AnnotationMarker): boolean {
+ const { top, left } = marker.position;
+ const { width, height } = marker.dimension;
+ const markerRect: Bounds = { startX: left, startY: top, endX: left + width, endY: top + height };
+ return isWithinRectBounds(cursorPosition, markerRect);
+}
diff --git a/src/chart_types/xy_chart/annotations/line/types.ts b/src/chart_types/xy_chart/annotations/line/types.ts
new file mode 100644
index 0000000000..1955d57aad
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/line/types.ts
@@ -0,0 +1,53 @@
+/*
+ * 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 { Position } from '../../../../utils/commons';
+import { AnnotationDetails, AnnotationMarker } from '../types';
+
+/**
+ * Start and end points of a line annotation
+ * @internal
+ */
+export interface AnnotationLinePathPoints {
+ /** x1,y1 the start point anchored to the linked axis */
+ start: {
+ x1: number;
+ y1: number;
+ };
+ /** x2,y2 the end point */
+ end: {
+ x2: number;
+ y2: number;
+ };
+}
+
+/** @internal */
+export interface AnnotationLineProps {
+ /** the position of the start point relative to the Chart */
+ anchor: {
+ position: Position;
+ top: number;
+ left: number;
+ };
+ /**
+ * The path points of a line annotation
+ */
+ linePathPoints: AnnotationLinePathPoints;
+ details: AnnotationDetails;
+ marker?: AnnotationMarker;
+}
diff --git a/src/chart_types/xy_chart/annotations/rect/dimensions.integration.test.ts b/src/chart_types/xy_chart/annotations/rect/dimensions.integration.test.ts
new file mode 100644
index 0000000000..7a2d9dd9c4
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/rect/dimensions.integration.test.ts
@@ -0,0 +1,258 @@
+/*
+ * 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 './types';
+
+function expectAnnotationAtPosition(
+ data: Array<{ x: number; y: number }>,
+ type: 'line' | 'bar' | 'histogram',
+ 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 $x0, $numOfSpecs 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 $x1, $numOfSpecs 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 at $x0, ending at $x1, $numOfSpecs 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 at $x0, ending at $x1, $numOfSpecs 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}
+ `(
+ 'on line starting at $x0, ending at $x1, $numOfSpecs 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.each`
+ x0 | x1 | numOfSpecs | x | width
+ ${0} | ${0} | ${1} | ${0} | ${0}
+ ${0} | ${1} | ${1} | ${0} | ${25}
+ ${1} | ${2} | ${1} | ${25} | ${25}
+ ${0} | ${2} | ${1} | ${0} | ${50}
+ ${0} | ${1} | ${2} | ${0} | ${25}
+ ${1} | ${2} | ${3} | ${25} | ${25}
+ `(
+ 'on histogram starting at $x0, ending at $x1, $numOfSpecs specs, continuous scale',
+ ({ x0, x1, numOfSpecs, x, width }) => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ { x: 3, y: 3 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { x0, x1 },
+ },
+ ];
+ const rect = { x, width, y: 0, height: 100 };
+ expectAnnotationAtPosition(data, 'histogram', dataValues, rect, numOfSpecs, ScaleType.Linear);
+ },
+ );
+
+ it.each`
+ prop | x | y | width | height
+ ${'x0'} | ${50} | ${0} | ${50} | ${100}
+ ${'x1'} | ${0} | ${0} | ${50} | ${100}
+ ${'y0'} | ${0} | ${0} | ${100} | ${75}
+ ${'y1'} | ${0} | ${75} | ${100} | ${25}
+ `('expand annotation with only one prop configured: $prop', ({ prop, x, y, width, height }) => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 2 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { [prop]: 1 },
+ },
+ ];
+ const rect = { x, width, y, height };
+ expectAnnotationAtPosition(data, 'line', dataValues, rect, 1, ScaleType.Linear);
+ });
+
+ it.each`
+ value | prop
+ ${10} | ${'y1'}
+ ${-4} | ${'y0'}
+ ${-4} | ${'x0'}
+ ${5} | ${'x1'}
+ `('out of bound annotations for $prop', ({ prop, value }) => {
+ const data = [
+ { x: 0, y: 4 },
+ { x: 1, y: 1 },
+ { x: 2, y: 3 },
+ ];
+ const dataValues: RectAnnotationDatum[] = [
+ {
+ coordinates: { [prop]: value },
+ },
+ ];
+ 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);
+ });
+});
diff --git a/src/chart_types/xy_chart/annotations/rect/dimensions.ts b/src/chart_types/xy_chart/annotations/rect/dimensions.ts
new file mode 100644
index 0000000000..2af690dd9c
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/rect/dimensions.ts
@@ -0,0 +1,204 @@
+/*
+ * 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 { RectAnnotationDatum, RectAnnotationSpec } from '../../utils/specs';
+import { GroupId } from '../../../../utils/ids';
+import { Scale, ScaleBand, ScaleContinuous } from '../../../../scales';
+import { Point } from '../../../../utils/point';
+import { Bounds } from '../types';
+import { AnnotationRectProps } from './types';
+import { isBandScale, isContinuousScale } from '../../../../scales/types';
+import { PrimitiveValue } from '../../../partition_chart/layout/utils/group_by_rollup';
+
+/** @internal */
+export function isWithinRectBounds({ x, y }: Point, { startX, endX, startY, endY }: Bounds): boolean {
+ const withinXBounds = x >= startX && x <= endX;
+ const withinYBounds = y >= startY && y <= endY;
+
+ return withinXBounds && withinYBounds;
+}
+
+/** @internal */
+export function computeRectAnnotationDimensions(
+ annotationSpec: RectAnnotationSpec,
+ yScales: Map,
+ xScale: Scale,
+ isHistogram: boolean = false,
+): AnnotationRectProps[] | null {
+ const { dataValues } = annotationSpec;
+ const groupId = annotationSpec.groupId;
+ const yScale = yScales.get(groupId);
+ if (!yScale) {
+ return null;
+ }
+
+ const rectsProps: AnnotationRectProps[] = [];
+ 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;
+ }
+ [x0, x1] = limitValueToDomainRange(xScale, x0, x1, isHistogram);
+ [y0, y1] = limitValueToDomainRange(yScale, y0, y1);
+
+ // something is wrong with the data types, don't draw this annotation
+ if (x0 == null || x1 == null || y0 == null || y1 == null) {
+ return;
+ }
+
+ let xAndWidth: { x: number; width: number } | null = null;
+ if (isBandScale(xScale)) {
+ xAndWidth = scaleXonBandScale(xScale, x0, x1);
+ } else if (isContinuousScale(xScale)) {
+ xAndWidth = scaleXonContinuousScale(xScale, x0, x1, isHistogram);
+ }
+
+ // something is wrong with scales, don't draw
+ if (!xAndWidth) {
+ return;
+ }
+ const scaledY1 = yScale.pureScale(y1);
+ const scaledY0 = yScale.pureScale(y0);
+ if (scaledY1 == null || scaledY0 == null) {
+ return;
+ }
+ const height = Math.abs(scaledY0 - scaledY1);
+
+ const rectDimensions = {
+ ...xAndWidth,
+ y: scaledY1,
+ height,
+ };
+
+ rectsProps.push({
+ rect: rectDimensions,
+ details: dataValue.details,
+ });
+ });
+ return rectsProps;
+}
+
+function scaleXonBandScale(
+ xScale: ScaleBand,
+ x0: PrimitiveValue,
+ x1: PrimitiveValue,
+): { x: number; width: number } | null {
+ // the band scale return the start of the band, we need to cover
+ // also the inner padding of the bar
+ const padding = (xScale.step - xScale.originalBandwidth) / 2;
+ let scaledX1 = xScale.scale(x1);
+ let scaledX0 = xScale.scale(x0);
+ if (scaledX1 == null || scaledX0 == null) {
+ return null;
+ }
+ // extend the x1 scaled value to fully cover the last bar
+ scaledX1 += xScale.originalBandwidth + padding;
+ // give the x1 value a maximum of the chart range
+ if (scaledX1 > xScale.range[1]) {
+ scaledX1 = xScale.range[1];
+ }
+
+ scaledX0 -= padding;
+ if (scaledX0 < xScale.range[0]) {
+ scaledX0 = xScale.range[0];
+ }
+ const width = Math.abs(scaledX1 - scaledX0);
+ return {
+ x: scaledX0,
+ width,
+ };
+}
+
+function scaleXonContinuousScale(
+ xScale: ScaleContinuous,
+ x0: PrimitiveValue,
+ x1: PrimitiveValue,
+ isHistogramModeEnabled: boolean = false,
+): { x: number; width: number } | null {
+ if (typeof x1 !== 'number' || typeof x0 !== 'number') {
+ return null;
+ }
+ const scaledX0 = xScale.scale(x0);
+ let scaledX1: number | null;
+ if (xScale.totalBarsInCluster > 0 && !isHistogramModeEnabled) {
+ scaledX1 = xScale.scale(x1 + xScale.minInterval);
+ } else {
+ scaledX1 = xScale.scale(x1);
+ }
+ if (scaledX1 == null || scaledX0 == null) {
+ return null;
+ }
+ // the width needs to be computed before adjusting the x anchor
+ const width = Math.abs(scaledX1 - scaledX0);
+ return {
+ x: scaledX0 - (xScale.bandwidthPadding / 2) * xScale.totalBarsInCluster,
+ width,
+ };
+}
+
+/**
+ * This function extend and limits the values in a scale domain
+ * @param scale the scale
+ * @param minValue a min value
+ * @param maxValue a max value
+ */
+function limitValueToDomainRange(
+ scale: Scale,
+ minValue?: PrimitiveValue,
+ maxValue?: PrimitiveValue,
+ isHistogram: boolean = false,
+): [PrimitiveValue, PrimitiveValue] {
+ const domainStartValue = scale.domain[0];
+ // this fix the case where rendering on categorical scale and we have only one element
+ const domainEndValue = scale.domain.length > 0 ? scale.domain[scale.domain.length - 1] : scale.domain[0];
+
+ // extend to edge values if values are null/undefined
+ let min = minValue == null ? domainStartValue : minValue;
+ let max = maxValue == null ? domainEndValue : maxValue;
+
+ if (isContinuousScale(scale)) {
+ if (minValue == null) {
+ // we expand null/undefined values to the edge
+ min = domainStartValue;
+ } else if (typeof minValue !== 'number') {
+ // we need to restrict to number only for continuous scales
+ min = null;
+ } else if (minValue < domainStartValue) {
+ // we limit values to the edge
+ min = domainStartValue;
+ } else {
+ min = minValue;
+ }
+
+ if (maxValue == null) {
+ // we expand null/undefined values to the edge
+ max = isHistogram ? domainEndValue + scale.minInterval : domainEndValue;
+ } else if (typeof maxValue !== 'number') {
+ // we need to restrict to number only for continuous scales
+ max = null;
+ } else if (maxValue > domainEndValue) {
+ // we limit values to the edge
+ max = domainEndValue;
+ } else {
+ max = maxValue;
+ }
+ }
+ return [min, max];
+}
diff --git a/src/chart_types/xy_chart/annotations/rect/tooltip.ts b/src/chart_types/xy_chart/annotations/rect/tooltip.ts
new file mode 100644
index 0000000000..e411786e84
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/rect/tooltip.ts
@@ -0,0 +1,64 @@
+/*
+ * 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 { AnnotationTypes } from '../../utils/specs';
+import { Rotation } from '../../../../utils/commons';
+import { Dimensions } from '../../../../utils/dimensions';
+import { Point } from '../../../../utils/point';
+import { getRotatedCursor } from '../utils';
+import { AnnotationTooltipFormatter, AnnotationTooltipState, Bounds } from '../types';
+import { AnnotationRectProps } from './types';
+import { isWithinRectBounds } from './dimensions';
+
+/** @internal */
+export function computeRectAnnotationTooltipState(
+ /** the cursor position relative to the projection area */
+ cursorPosition: Point,
+ annotationRects: AnnotationRectProps[],
+ chartRotation: Rotation,
+ chartDimensions: Dimensions,
+ renderTooltip?: AnnotationTooltipFormatter,
+): AnnotationTooltipState {
+ const rotatedCursorPosition = getRotatedCursor(cursorPosition, chartDimensions, chartRotation);
+ const totalAnnotationRect = annotationRects.length;
+ for (let i = 0; i < totalAnnotationRect; i++) {
+ const rectProps = annotationRects[i];
+ const { rect, details } = rectProps;
+ const startX = rect.x;
+ const endX = startX + rect.width;
+ const startY = rect.y;
+ const endY = startY + rect.height;
+ const bounds: Bounds = { startX, endX, startY, endY };
+ const isWithinBounds = isWithinRectBounds(rotatedCursorPosition, bounds);
+ if (isWithinBounds) {
+ return {
+ isVisible: true,
+ annotationType: AnnotationTypes.Rectangle,
+ anchor: {
+ left: rotatedCursorPosition.x,
+ top: rotatedCursorPosition.y,
+ },
+ ...(details && { details }),
+ ...(renderTooltip && { renderTooltip }),
+ };
+ }
+ }
+ return {
+ isVisible: false,
+ };
+}
diff --git a/src/chart_types/xy_chart/annotations/rect/types.ts b/src/chart_types/xy_chart/annotations/rect/types.ts
new file mode 100644
index 0000000000..24e4d7c263
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/rect/types.ts
@@ -0,0 +1,28 @@
+/*
+ * 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 interface AnnotationRectProps {
+ rect: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ };
+ details?: string;
+}
diff --git a/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts
deleted file mode 100644
index b62a4210c9..0000000000
--- a/src/chart_types/xy_chart/annotations/rect_annotation_tooltip.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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 { AnnotationTypes, RectAnnotationDatum, RectAnnotationSpec } from '../utils/specs';
-import { Rotation } from '../../../utils/commons';
-import { Dimensions } from '../../../utils/dimensions';
-import { GroupId } from '../../../utils/ids';
-import { Scale } from '../../../scales';
-import { Point } from '../../../utils/point';
-import {
- AnnotationTooltipFormatter,
- AnnotationTooltipState,
- getRotatedCursor,
- scaleAndValidateDatum,
- Bounds,
-} from './annotation_utils';
-
-/** @internal */
-export interface AnnotationRectProps {
- rect: {
- x: number;
- y: number;
- width: number;
- height: number;
- };
- details?: string;
-}
-
-/** @internal */
-export function computeRectAnnotationTooltipState(
- /** the cursor position relative to the projection area */
- cursorPosition: Point,
- annotationRects: AnnotationRectProps[],
- chartRotation: Rotation,
- chartDimensions: Dimensions,
- renderTooltip?: AnnotationTooltipFormatter,
-): AnnotationTooltipState {
- const rotatedCursorPosition = getRotatedCursor(cursorPosition, chartDimensions, chartRotation);
-
- const totalAnnotationRect = annotationRects.length;
- for (let i = 0; i < totalAnnotationRect; i++) {
- const rectProps = annotationRects[i];
- const { rect, details } = rectProps;
- const startX = rect.x;
- const endX = startX + rect.width;
-
- const startY = rect.y;
- const endY = startY + rect.height;
-
- const bounds: Bounds = { startX, endX, startY, endY };
-
- const isWithinBounds = isWithinRectBounds(rotatedCursorPosition, bounds);
- if (isWithinBounds) {
- return {
- isVisible: true,
- annotationType: AnnotationTypes.Rectangle,
- anchor: {
- left: rotatedCursorPosition.x,
- top: rotatedCursorPosition.y,
- },
- ...(details && { details }),
- ...(renderTooltip && { renderTooltip }),
- };
- }
- }
- return {
- isVisible: false,
- };
-}
-
-/** @internal */
-export function isWithinRectBounds({ x, y }: Point, { startX, endX, startY, endY }: Bounds): boolean {
- const withinXBounds = x >= startX && x <= endX;
- const withinYBounds = y >= startY && y <= endY;
-
- return withinXBounds && withinYBounds;
-}
-
-/** @internal */
-export function computeRectAnnotationDimensions(
- annotationSpec: RectAnnotationSpec,
- yScales: Map,
- xScale: Scale,
- enableHistogramMode: boolean,
- barsPadding: number,
-): AnnotationRectProps[] | null {
- const { dataValues } = annotationSpec;
-
- const groupId = annotationSpec.groupId;
- const yScale = yScales.get(groupId);
- if (!yScale) {
- return null;
- }
-
- const xDomain = xScale.domain;
- const yDomain = yScale.domain;
- const lastX = xDomain[xDomain.length - 1];
- const xMinInterval = xScale.minInterval;
- const rectsProps: AnnotationRectProps[] = [];
-
- 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;
- }
-
- if (x0 == null) {
- // if x0 is defined, we want the rect to draw to the start of the scale
- x0 = xDomain[0];
- }
-
- 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 (y1 == null) {
- // if y1 is defined, we want the rect to draw to the start of the scale
- y1 = yDomain[0];
- }
-
- 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);
-
- // TODO: surface this as a warning
- if (x0Scaled === null || x1Scaled === null || y0Scaled === null || y1Scaled === null) {
- return;
- }
-
- let xOffset = 0;
- if (xScale.bandwidth > 0) {
- const xBand = xScale.bandwidth / (1 - xScale.barsPadding);
- xOffset = enableHistogramMode ? (xBand - xScale.bandwidth) / 2 : barsPadding;
- }
-
- x0Scaled = x0Scaled - xOffset;
- x1Scaled = x1Scaled - xOffset;
-
- const minX = Math.min(x0Scaled, x1Scaled);
- const minY = Math.min(y0Scaled, y1Scaled);
-
- const deltaX = Math.abs(x0Scaled - x1Scaled);
- const deltaY = Math.abs(y0Scaled - y1Scaled);
-
- const xOrigin = minX;
- const yOrigin = minY;
-
- const width = deltaX;
- const height = deltaY;
-
- const rectDimensions = {
- x: xOrigin,
- y: yOrigin,
- width,
- height,
- };
-
- rectsProps.push({
- rect: rectDimensions,
- details: dataValue.details,
- });
- });
-
- return rectsProps;
-}
diff --git a/src/chart_types/xy_chart/annotations/tooltip.ts b/src/chart_types/xy_chart/annotations/tooltip.ts
new file mode 100644
index 0000000000..9e3a41211d
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/tooltip.ts
@@ -0,0 +1,123 @@
+/*
+ * 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 { AnnotationId } from '../../../utils/ids';
+import { AnnotationSpec, AxisSpec, isLineAnnotation, isRectAnnotation } from '../utils/specs';
+import { Rotation, Position } from '../../../utils/commons';
+import { AnnotationDimensions, AnnotationTooltipState } from './types';
+import { Dimensions } from '../../../utils/dimensions';
+import { computeLineAnnotationTooltipState } from './line/tooltip';
+import { computeRectAnnotationTooltipState } from './rect/tooltip';
+import { AnnotationRectProps } from './rect/types';
+import { Point } from '../../../utils/point';
+import { AnnotationLineProps } from './line/types';
+
+/** @internal */
+export function computeAnnotationTooltipState(
+ cursorPosition: Point,
+ annotationDimensions: Map,
+ annotationSpecs: AnnotationSpec[],
+ chartRotation: Rotation,
+ axesSpecs: AxisSpec[],
+ chartDimensions: Dimensions,
+): AnnotationTooltipState | null {
+ // allow picking up the last spec added as the top most or use it's zIndex value
+ const sortedSpecs = annotationSpecs
+ .slice()
+ .reverse()
+ .sort(({ zIndex: a = Number.MIN_SAFE_INTEGER }, { zIndex: b = Number.MIN_SAFE_INTEGER }) => b - a);
+ for (const spec of sortedSpecs) {
+ const annotationDimension = annotationDimensions.get(spec.id);
+ if (spec.hideTooltips || !annotationDimension) {
+ continue;
+ }
+ const groupId = spec.groupId;
+
+ if (isLineAnnotation(spec)) {
+ if (spec.hideLines) {
+ continue;
+ }
+ const lineAnnotationTooltipState = computeLineAnnotationTooltipState(
+ cursorPosition,
+ annotationDimension as AnnotationLineProps[],
+ groupId,
+ spec.domainType,
+ axesSpecs,
+ );
+
+ if (lineAnnotationTooltipState.isVisible) {
+ return lineAnnotationTooltipState;
+ }
+ } else if (isRectAnnotation(spec)) {
+ const rectAnnotationTooltipState = computeRectAnnotationTooltipState(
+ cursorPosition,
+ annotationDimension as AnnotationRectProps[],
+ chartRotation,
+ chartDimensions,
+ spec.renderTooltip,
+ );
+
+ if (rectAnnotationTooltipState.isVisible) {
+ return rectAnnotationTooltipState;
+ }
+ }
+ }
+
+ return null;
+}
+
+/** @internal */
+export function getFinalAnnotationTooltipPosition(
+ /** the dimensions of the chart parent container */
+ container: Dimensions,
+ chartDimensions: Dimensions,
+ /** the dimensions of the tooltip container */
+ tooltip: Dimensions,
+ /** the tooltip computed position not adjusted within chart bounds */
+ tooltipAnchor: { top: number; left: number },
+ /** the width of the tooltip portal container */
+ portalWidth: number,
+ padding = 10,
+): {
+ left: string | null;
+ top: string | null;
+ anchor: typeof Position.Left | typeof Position.Right;
+} {
+ let left = 0;
+ let anchor: Position = Position.Left;
+
+ const annotationXOffset = window.pageXOffset + container.left + chartDimensions.left + tooltipAnchor.left;
+ if (chartDimensions.left + tooltipAnchor.left + portalWidth + padding >= container.width) {
+ left = annotationXOffset - portalWidth - padding;
+ anchor = Position.Right;
+ } else {
+ left = annotationXOffset + padding;
+ }
+ let top = window.pageYOffset + container.top + chartDimensions.top + tooltipAnchor.top;
+ if (chartDimensions.top + tooltipAnchor.top + tooltip.height + padding >= container.height) {
+ top -= tooltip.height + padding;
+ } else {
+ top += padding;
+ }
+
+ return {
+ left: `${Math.round(left)}px`,
+ top: `${Math.round(top)}px`,
+ anchor,
+ };
+}
diff --git a/src/chart_types/xy_chart/annotations/types.ts b/src/chart_types/xy_chart/annotations/types.ts
new file mode 100644
index 0000000000..1039bd3c59
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/types.ts
@@ -0,0 +1,83 @@
+/*
+ * 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 { AnnotationType } from '../utils/specs';
+import { AnnotationLineProps } from './line/types';
+import { AnnotationRectProps } from './rect/types';
+import { Position, Color } from '../../../utils/commons';
+
+export type AnnotationTooltipFormatter = (details?: string) => JSX.Element | null;
+
+/**
+ * The header and description strings for an Annotation
+ * @internal
+ */
+export interface AnnotationDetails {
+ headerText?: string;
+ detailsText?: string;
+}
+
+/**
+ * The marker for an Annotation. Usually a JSX element
+ * @internal
+ */
+export interface AnnotationMarker {
+ icon: JSX.Element;
+ position: {
+ top: number;
+ left: number;
+ };
+ dimension: {
+ width: number;
+ height: number;
+ };
+ color: Color;
+}
+
+/** @internal */
+export type AnnotationTooltipState = AnnotationTooltipVisibleState | AnnotationTooltipHiddenState;
+
+/** @internal */
+export interface AnnotationTooltipVisibleState {
+ isVisible: true;
+ annotationType: AnnotationType;
+ header?: string;
+ details?: string;
+ anchor: {
+ position?: Position;
+ top: number;
+ left: number;
+ };
+ renderTooltip?: AnnotationTooltipFormatter;
+}
+
+/** @internal */
+export interface AnnotationTooltipHiddenState {
+ isVisible: false;
+}
+
+/** @internal */
+export type AnnotationDimensions = AnnotationLineProps[] | AnnotationRectProps[];
+
+/** @internal */
+export type Bounds = {
+ startX: number;
+ endX: number;
+ startY: number;
+ endY: number;
+};
diff --git a/src/chart_types/xy_chart/annotations/annotation_utils.test.ts b/src/chart_types/xy_chart/annotations/utils.test.ts
similarity index 76%
rename from src/chart_types/xy_chart/annotations/annotation_utils.test.ts
rename to src/chart_types/xy_chart/annotations/utils.test.ts
index 4d885075ca..1cea87c840 100644
--- a/src/chart_types/xy_chart/annotations/annotation_utils.test.ts
+++ b/src/chart_types/xy_chart/annotations/utils.test.ts
@@ -30,33 +30,20 @@ import { DEFAULT_ANNOTATION_LINE_STYLE } from '../../../utils/themes/theme';
import { Dimensions } from '../../../utils/dimensions';
import { GroupId, AnnotationId } from '../../../utils/ids';
import { Scale, ScaleType, ScaleBand, ScaleContinuous } from '../../../scales';
-import {
- computeAnnotationDimensions,
- computeAnnotationTooltipState,
- computeClusterOffset,
- getAnnotationAxis,
- getRotatedCursor,
- scaleAndValidateDatum,
- AnnotationDimensions,
- AnnotationTooltipState,
- Bounds,
-} from './annotation_utils';
-import {
- AnnotationLineProps,
- computeLineAnnotationDimensions,
- computeLineAnnotationTooltipState,
- isVerticalAnnotationLine,
- getAnnotationLineTooltipXOffset,
- getAnnotationLineTooltipYOffset,
-} from './line_annotation_tooltip';
-import {
- computeRectAnnotationDimensions,
- isWithinRectBounds,
- computeRectAnnotationTooltipState,
-} from './rect_annotation_tooltip';
+import { computeAnnotationDimensions, getAnnotationAxis, getRotatedCursor } from './utils';
+import { AnnotationDimensions, AnnotationTooltipState, Bounds } from './types';
+import { computeLineAnnotationDimensions } from './line/dimensions';
+import { AnnotationLineProps } from './line/types';
+import { computeRectAnnotationDimensions, isWithinRectBounds } from './rect/dimensions';
+import { computeRectAnnotationTooltipState } from './rect/tooltip';
import { Point } from '../../../utils/point';
import { ChartTypes } from '../..';
import { SpecTypes } from '../../../specs/settings';
+import { computeLineAnnotationTooltipState } from './line/tooltip';
+import { computeAnnotationTooltipState } from './tooltip';
+import { MockStore } from '../../../mocks/store';
+import { MockGlobalSpec, MockSeriesSpec, MockAnnotationSpec } from '../../../mocks/specs';
+import { computeAnnotationDimensionsSelector } from '../state/selectors/compute_annotations';
describe('annotation utils', () => {
const minRange = 0;
@@ -116,70 +103,58 @@ describe('annotation utils', () => {
axesSpecs.push(verticalAxisSpec);
- test('should compute annotation dimensions', () => {
- const chartRotation: Rotation = 0;
- const yScales: Map = new Map();
- yScales.set(groupId, continuousScale);
-
- const xScale: Scale = ordinalScale;
+ test('should compute rect annotation in x ordinal scale', () => {
+ const store = MockStore.default();
+ const settings = MockGlobalSpec.settingsNoMargins();
+ const spec = MockSeriesSpec.bar({
+ xScaleType: ScaleType.Ordinal,
+ groupId,
+ data: [
+ { x: 'a', y: 1 },
+ { x: 'b', y: 0 },
+ { x: 'c', y: 10 },
+ { x: 'd', y: 5 },
+ ],
+ });
- const annotations: AnnotationSpec[] = [];
- const annotationId = 'foo';
- const lineAnnotation: LineAnnotationSpec = {
- chartType: ChartTypes.XYAxis,
- specType: SpecTypes.Annotation,
- annotationType: AnnotationTypes.Line,
- id: annotationId,
+ const lineAnnotation = MockAnnotationSpec.line({
+ id: 'foo',
+ groupId,
domainType: AnnotationDomainTypes.YDomain,
dataValues: [{ dataValue: 2, details: 'foo' }],
- groupId,
- style: DEFAULT_ANNOTATION_LINE_STYLE,
- };
+ });
- const rectAnnotationId = 'rect';
- const rectAnnotation: AnnotationSpec = {
- chartType: ChartTypes.XYAxis,
- specType: SpecTypes.Annotation,
- id: rectAnnotationId,
+ const rectAnnotation = MockAnnotationSpec.rect({
+ id: 'rect',
groupId,
- annotationType: AnnotationTypes.Rectangle,
dataValues: [{ coordinates: { x0: 'a', x1: 'b', y0: 3, y1: 5 } }],
- };
+ });
- annotations.push(lineAnnotation);
- annotations.push(rectAnnotation);
+ MockStore.addSpecs([settings, spec, lineAnnotation, rectAnnotation], store);
+ const dimensions = computeAnnotationDimensionsSelector(store.getState());
- const dimensions = computeAnnotationDimensions(
- annotations,
- chartDimensions,
- chartRotation,
- yScales,
- xScale,
- axesSpecs,
- 1,
- false,
- );
const expectedDimensions = new Map();
- expectedDimensions.set(annotationId, [
+ expectedDimensions.set('foo', [
{
anchor: {
- top: 20,
+ top: 80,
left: 0,
position: Position.Left,
},
linePathPoints: {
- start: { x1: 0, y1: 20 },
- end: { x2: 10, y2: 20 },
+ start: { x1: 0, y1: 80 },
+ end: { x2: 100, y2: 80 },
},
+ marker: undefined,
details: { detailsText: 'foo', headerText: '2' },
},
]);
- expectedDimensions.set(rectAnnotationId, [{ rect: { x: 0, y: 30, width: 25, height: 20 } }]);
+ expectedDimensions.set('rect', [{ details: undefined, rect: { x: 0, y: 50, 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);
@@ -208,11 +183,9 @@ describe('annotation utils', () => {
yScales,
xScale,
[], // empty axesSpecs
- 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)', () => {
@@ -241,7 +214,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Left,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -287,7 +259,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Right,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -333,7 +304,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Left,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -378,7 +348,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Left,
- 0,
false,
);
expect(dimensions).toEqual(null);
@@ -408,7 +377,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Left,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -452,7 +420,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Top,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -497,7 +464,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Bottom,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -518,51 +484,6 @@ describe('annotation utils', () => {
expect(dimensions).toEqual(expectedDimensions);
});
- test('should compute line annotation dimensions for xDomain in histogramMode with extended upper bound', () => {
- const chartRotation: Rotation = 0;
- const yScales: Map = new Map();
- const xScale: Scale = continuousScale;
-
- const annotationId = 'foo-line';
- const lineAnnotation: LineAnnotationSpec = {
- chartType: ChartTypes.XYAxis,
- specType: SpecTypes.Annotation,
- annotationType: AnnotationTypes.Line,
- id: annotationId,
- domainType: AnnotationDomainTypes.XDomain,
- dataValues: [{ dataValue: 10.5, details: 'foo' }],
- groupId,
- style: DEFAULT_ANNOTATION_LINE_STYLE,
- };
-
- const dimensions = computeLineAnnotationDimensions(
- lineAnnotation,
- chartDimensions,
- chartRotation,
- yScales,
- xScale,
- Position.Bottom,
- 0,
- true,
- );
- const expectedDimensions: AnnotationLineProps[] = [
- {
- anchor: {
- top: 20,
- left: 110,
- position: Position.Bottom,
- },
- linePathPoints: {
- start: { x1: 110, y1: 20 },
- end: { x2: 110, y2: 0 },
- },
- details: { detailsText: 'foo', headerText: '10.5' },
- marker: undefined,
- },
- ];
- expect(dimensions).toEqual(expectedDimensions);
- });
-
test('should compute line annotation dimensions for xDomain on a xScale (chartRotation 90, ordinal scale)', () => {
const chartRotation: Rotation = 90;
const yScales: Map = new Map();
@@ -588,7 +509,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Left,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -634,7 +554,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Left,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -680,7 +599,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Left,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -726,7 +644,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Top,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -771,7 +688,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Bottom,
- 0,
false,
);
const expectedDimensions: AnnotationLineProps[] = [
@@ -792,7 +708,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);
@@ -818,7 +734,6 @@ describe('annotation utils', () => {
yScales,
xScale,
Position.Right,
- 0,
false,
);
@@ -842,7 +757,6 @@ describe('annotation utils', () => {
yScales,
continuousScale,
Position.Right,
- 0,
false,
);
@@ -866,11 +780,9 @@ describe('annotation utils', () => {
yScales,
continuousScale,
Position.Right,
- 0,
false,
);
-
- expect(emptyOutOfBoundsXDimensions).toEqual([]);
+ expect(emptyOutOfBoundsXDimensions).toHaveLength(0);
const invalidYLineAnnotation: AnnotationSpec = {
chartType: ChartTypes.XYAxis,
@@ -883,18 +795,17 @@ describe('annotation utils', () => {
style: DEFAULT_ANNOTATION_LINE_STYLE,
};
- const emptyYDimensions = computeLineAnnotationDimensions(
+ const emptyOutOfBoundsYDimensions = computeLineAnnotationDimensions(
invalidYLineAnnotation,
chartDimensions,
chartRotation,
yScales,
xScale,
Position.Right,
- 0,
false,
);
- expect(emptyYDimensions).toEqual([]);
+ expect(emptyOutOfBoundsYDimensions).toHaveLength(0);
const outOfBoundsYLineAnnotation: AnnotationSpec = {
chartType: ChartTypes.XYAxis,
@@ -907,18 +818,17 @@ describe('annotation utils', () => {
style: DEFAULT_ANNOTATION_LINE_STYLE,
};
- const emptyOutOfBoundsYDimensions = computeLineAnnotationDimensions(
+ const outOfBoundsYAnn = computeLineAnnotationDimensions(
outOfBoundsYLineAnnotation,
chartDimensions,
chartRotation,
yScales,
xScale,
Position.Right,
- 0,
false,
);
- expect(emptyOutOfBoundsYDimensions).toEqual([]);
+ expect(outOfBoundsYAnn).toHaveLength(0);
const invalidStringYLineAnnotation: AnnotationSpec = {
chartType: ChartTypes.XYAxis,
@@ -938,7 +848,6 @@ describe('annotation utils', () => {
yScales,
continuousScale,
Position.Right,
- 0,
false,
);
@@ -963,79 +872,12 @@ describe('annotation utils', () => {
yScales,
continuousScale,
Position.Right,
- 0,
false,
);
expect(hiddenAnnotationDimensions).toEqual(null);
});
- test('should determine if an annotation line is vertical dependent on domain type & chart rotation', () => {
- const isHorizontal = true;
- const isXDomain = true;
- const xDomainHorizontalRotation = isVerticalAnnotationLine(isXDomain, isHorizontal);
- expect(xDomainHorizontalRotation).toBe(true);
-
- const xDomainVerticalRotation = isVerticalAnnotationLine(isXDomain, !isHorizontal);
- expect(xDomainVerticalRotation).toBe(false);
-
- const yDomainHorizontalRotation = isVerticalAnnotationLine(!isXDomain, isHorizontal);
- expect(yDomainHorizontalRotation).toBe(false);
-
- const yDomainVerticalRotation = isVerticalAnnotationLine(isXDomain, !isHorizontal);
- expect(yDomainVerticalRotation).toBe(false);
- });
- test('should get the x offset for an annotation line tooltip', () => {
- const bottomHorizontalRotationOffset = getAnnotationLineTooltipXOffset(0, Position.Bottom);
- expect(bottomHorizontalRotationOffset).toBe(50);
-
- const topHorizontalRotationOffset = getAnnotationLineTooltipXOffset(0, Position.Top);
- expect(topHorizontalRotationOffset).toBe(50);
-
- const bottomVerticalRotationOffset = getAnnotationLineTooltipXOffset(90, Position.Bottom);
- expect(bottomVerticalRotationOffset).toBe(0);
-
- const topVerticalRotationOffset = getAnnotationLineTooltipXOffset(90, Position.Top);
- expect(topVerticalRotationOffset).toBe(0);
-
- const leftHorizontalRotationOffset = getAnnotationLineTooltipXOffset(0, Position.Left);
- expect(leftHorizontalRotationOffset).toBe(0);
-
- const rightHorizontalRotationOffset = getAnnotationLineTooltipXOffset(0, Position.Right);
- expect(rightHorizontalRotationOffset).toBe(100);
-
- const leftVerticalRotationOffset = getAnnotationLineTooltipXOffset(90, Position.Left);
- expect(leftVerticalRotationOffset).toBe(50);
-
- const rightVerticalRotationOffset = getAnnotationLineTooltipXOffset(90, Position.Right);
- expect(rightVerticalRotationOffset).toBe(50);
- });
- test('should get the y offset for an annotation line tooltip', () => {
- const bottomHorizontalRotationOffset = getAnnotationLineTooltipYOffset(0, Position.Bottom);
- expect(bottomHorizontalRotationOffset).toBe(100);
-
- const topHorizontalRotationOffset = getAnnotationLineTooltipYOffset(0, Position.Top);
- expect(topHorizontalRotationOffset).toBe(0);
-
- const bottomVerticalRotationOffset = getAnnotationLineTooltipYOffset(90, Position.Bottom);
- expect(bottomVerticalRotationOffset).toBe(50);
-
- const topVerticalRotationOffset = getAnnotationLineTooltipYOffset(90, Position.Top);
- expect(topVerticalRotationOffset).toBe(50);
-
- const leftHorizontalRotationOffset = getAnnotationLineTooltipYOffset(0, Position.Left);
- expect(leftHorizontalRotationOffset).toBe(50);
-
- const rightHorizontalRotationOffset = getAnnotationLineTooltipYOffset(0, Position.Right);
- expect(rightHorizontalRotationOffset).toBe(50);
-
- const leftVerticalRotationOffset = getAnnotationLineTooltipYOffset(90, Position.Left);
- expect(leftVerticalRotationOffset).toBe(100);
-
- const rightVerticalRotationOffset = getAnnotationLineTooltipYOffset(90, Position.Right);
- expect(rightVerticalRotationOffset).toBe(100);
- });
-
test('should compute the tooltip state for an annotation line', () => {
const cursorPosition: Point = { x: 1, y: 2 };
const annotationLines: AnnotationLineProps[] = [
@@ -1379,7 +1221,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);
expect(noYScale).toBe(null);
});
@@ -1401,17 +1243,27 @@ describe('annotation utils', () => {
],
};
- const skippedInvalid = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0);
+ const skippedInvalid = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale);
- expect(skippedInvalid).toEqual([]);
+ expect(skippedInvalid).toHaveLength(1);
});
test('should compute rectangle dimensions shifted for histogram mode', () => {
const yScales: Map = new Map();
- yScales.set(groupId, continuousScale);
+ yScales.set(
+ groupId,
+ new ScaleContinuous(
+ {
+ type: ScaleType.Linear,
+ domain: continuousData,
+ range: [minRange, maxRange],
+ },
+ { bandwidth: 0, minInterval: 1 },
+ ),
+ );
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,115 +1280,30 @@ describe('annotation utils', () => {
],
};
- const dimensions = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, true, 0);
+ const dimensions = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale);
const [dims1, dims2, dims3, dims4] = dimensions;
expect(dims1.rect.x).toBe(10);
- expect(dims1.rect.y).toBe(0);
- expect(dims1.rect.width).toBeCloseTo(100);
+ expect(dims1.rect.y).toBe(100);
expect(dims1.rect.height).toBe(100);
+ expect(dims1.rect.width).toBeCloseTo(100);
expect(dims2.rect.x).toBe(0);
- expect(dims2.rect.y).toBe(0);
- expect(dims2.rect.width).toBe(10);
+ expect(dims2.rect.y).toBe(100);
+ 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(100);
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.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();
- yScales.set(groupId, continuousScale);
-
- const xScale: Scale = continuousScale;
-
- const annotationRectangle: RectAnnotationSpec = {
- chartType: ChartTypes.XYAxis,
- specType: SpecTypes.Annotation,
- id: 'rect',
- groupId,
- annotationType: AnnotationTypes.Rectangle,
- dataValues: [
- { coordinates: { x0: 1, x1: null, y0: null, y1: null } },
- { coordinates: { x0: null, x1: 1, y0: null, y1: null } },
- { coordinates: { x0: null, x1: null, y0: 1, y1: null } },
- { coordinates: { x0: null, x1: null, y0: null, y1: 1 } },
- ],
- };
- const dimensions = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 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 } },
- ];
-
- expect(dimensions).toEqual(expectedDimensions);
- });
- test('should compute rectangle annotation dimensions continuous (0 deg rotation)', () => {
- const yScales: Map = new Map();
- yScales.set(groupId, continuousScale);
-
- const xScale: Scale = continuousScale;
-
- const annotationRectangle: RectAnnotationSpec = {
- chartType: ChartTypes.XYAxis,
- specType: SpecTypes.Annotation,
- id: 'rect',
- groupId,
- annotationType: AnnotationTypes.Rectangle,
- dataValues: [{ coordinates: { x0: 1, x1: 2, y0: 3, y1: 5 } }],
- };
-
- const unrotated = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0);
-
- expect(unrotated).toEqual([{ rect: { x: 10, y: 30, width: 10, height: 20 } }]);
- });
- test('should compute rectangle annotation dimensions ordinal (0 deg rotation)', () => {
- const yScales: Map = new Map();
- yScales.set(groupId, continuousScale);
-
- const xScale: Scale = ordinalScale;
-
- const annotationRectangle: RectAnnotationSpec = {
- chartType: ChartTypes.XYAxis,
- specType: SpecTypes.Annotation,
- id: 'rect',
- groupId,
- annotationType: AnnotationTypes.Rectangle,
- dataValues: [{ coordinates: { x0: 'a', x1: 'b', y0: 0, y1: 2 } }],
- };
-
- const unrotated = computeRectAnnotationDimensions(annotationRectangle, yScales, xScale, false, 0);
-
- expect(unrotated).toEqual([{ rect: { x: 0, y: 0, width: 25, 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);
-
- // 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('a', ordinalScale, false)).toBe(0);
- expect(scaleAndValidateDatum(0, continuousScale, false)).toBe(0);
-
- // aligned with tick
- expect(scaleAndValidateDatum(1.25, continuousScale, true)).toBe(12.5);
- });
test('should determine if a point is within a rectangle annotation', () => {
const cursorPosition = { x: 3, y: 4 };
@@ -1590,12 +1357,6 @@ describe('annotation utils', () => {
expect(visibleTooltip).toEqual(expectedVisibleTooltipState);
});
- test('should determine if line is vertical annotation', () => {
- expect(isVerticalAnnotationLine(true, true)).toBe(true);
- expect(isVerticalAnnotationLine(true, false)).toBe(false);
- expect(isVerticalAnnotationLine(false, true)).toBe(false);
- expect(isVerticalAnnotationLine(false, false)).toBe(true);
- });
test('should get rotated cursor position', () => {
const cursorPosition = { x: 1, y: 2 };
@@ -1605,15 +1366,4 @@ describe('annotation utils', () => {
expect(getRotatedCursor(cursorPosition, chartDimensions, -90)).toEqual({ x: 18, y: 1 });
expect(getRotatedCursor(cursorPosition, chartDimensions, 180)).toEqual({ x: 9, y: 18 });
});
-
- test('should compute cluster offset', () => {
- const singleBarCluster = 1;
- const multiBarCluster = 2;
-
- const barsShift = 4;
- const bandwidth = 2;
-
- expect(computeClusterOffset(singleBarCluster, barsShift, bandwidth)).toBe(0);
- expect(computeClusterOffset(multiBarCluster, barsShift, bandwidth)).toBe(3);
- });
});
diff --git a/src/chart_types/xy_chart/annotations/utils.ts b/src/chart_types/xy_chart/annotations/utils.ts
new file mode 100644
index 0000000000..1d7487e227
--- /dev/null
+++ b/src/chart_types/xy_chart/annotations/utils.ts
@@ -0,0 +1,119 @@
+/*
+ * 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 {
+ AnnotationDomainType,
+ AnnotationDomainTypes,
+ AnnotationSpec,
+ AxisSpec,
+ isLineAnnotation,
+ isRectAnnotation,
+} from '../utils/specs';
+import { Dimensions } from '../../../utils/dimensions';
+import { AnnotationId, GroupId } from '../../../utils/ids';
+import { Scale } from '../../../scales';
+import { getAxesSpecForSpecId, isHorizontalRotation } from '../state/utils';
+import { Point } from '../../../utils/point';
+import { computeLineAnnotationDimensions } from './line/dimensions';
+import { computeRectAnnotationDimensions } from './rect/dimensions';
+import { Rotation, Position } from '../../../utils/commons';
+import { AnnotationDimensions } from './types';
+
+/** @internal */
+export function getAnnotationAxis(
+ axesSpecs: AxisSpec[],
+ groupId: GroupId,
+ domainType: AnnotationDomainType,
+ chartRotation: Rotation,
+): Position | null {
+ const { xAxis, yAxis } = getAxesSpecForSpecId(axesSpecs, groupId);
+ const isHorizontalRotated = isHorizontalRotation(chartRotation);
+ const isXDomainAnnotation = isXDomain(domainType);
+ const annotationAxis = isXDomainAnnotation ? xAxis : yAxis;
+ const rotatedAnnotation = isHorizontalRotated ? annotationAxis : isXDomainAnnotation ? yAxis : xAxis;
+ return rotatedAnnotation ? rotatedAnnotation.position : null;
+}
+
+/** @internal */
+export function isXDomain(domainType: AnnotationDomainType): boolean {
+ return domainType === AnnotationDomainTypes.XDomain;
+}
+
+/** @internal */
+export function getRotatedCursor(
+ /** the cursor position relative to the projection area */
+ cursorPosition: Point,
+ chartDimensions: Dimensions,
+ chartRotation: Rotation,
+): Point {
+ const { x, y } = cursorPosition;
+ const { height, width } = chartDimensions;
+ switch (chartRotation) {
+ case 0:
+ return { x, y };
+ case 90:
+ return { x: y, y: width - x };
+ case -90:
+ return { x: height - y, y: x };
+ case 180:
+ return { x: width - x, y: height - y };
+ }
+}
+
+/** @internal */
+export function computeAnnotationDimensions(
+ annotations: AnnotationSpec[],
+ chartDimensions: Dimensions,
+ chartRotation: Rotation,
+ yScales: Map,
+ xScale: Scale,
+ axesSpecs: AxisSpec[],
+ isHistogramModeEnabled: boolean,
+): Map {
+ const annotationDimensions = new Map();
+
+ annotations.forEach((annotationSpec) => {
+ const { id } = annotationSpec;
+ if (isLineAnnotation(annotationSpec)) {
+ const { groupId, domainType } = annotationSpec;
+ const annotationAxisPosition = getAnnotationAxis(axesSpecs, groupId, domainType, chartRotation);
+
+ const dimensions = computeLineAnnotationDimensions(
+ annotationSpec,
+ chartDimensions,
+ chartRotation,
+ yScales,
+ xScale,
+ annotationAxisPosition,
+ isHistogramModeEnabled,
+ );
+
+ if (dimensions) {
+ annotationDimensions.set(id, dimensions);
+ }
+ } else if (isRectAnnotation(annotationSpec)) {
+ const dimensions = computeRectAnnotationDimensions(annotationSpec, yScales, xScale, isHistogramModeEnabled);
+
+ if (dimensions) {
+ annotationDimensions.set(id, dimensions);
+ }
+ }
+ });
+
+ return annotationDimensions;
+}
diff --git a/src/chart_types/xy_chart/legend/legend.ts b/src/chart_types/xy_chart/legend/legend.ts
index 27efa96728..0da1997809 100644
--- a/src/chart_types/xy_chart/legend/legend.ts
+++ b/src/chart_types/xy_chart/legend/legend.ts
@@ -48,8 +48,7 @@ function getPostfix(spec: BasicSeriesSpec): Postfixes {
return {};
}
-/** @internal */
-export function getBandedLegendItemLabel(name: string, yAccessor: BandedAccessorType, postfixes: Postfixes) {
+function getBandedLegendItemLabel(name: string, yAccessor: BandedAccessorType, postfixes: Postfixes) {
return yAccessor === BandedAccessorType.Y1
? `${name}${postfixes.y1AccessorFormat}`
: `${name}${postfixes.y0AccessorFormat}`;
diff --git a/src/chart_types/xy_chart/renderer/canvas/annotations/index.ts b/src/chart_types/xy_chart/renderer/canvas/annotations/index.ts
index 18ab7398be..a3c0c6bc68 100644
--- a/src/chart_types/xy_chart/renderer/canvas/annotations/index.ts
+++ b/src/chart_types/xy_chart/renderer/canvas/annotations/index.ts
@@ -16,15 +16,15 @@
* specific language governing permissions and limitations
* under the License. */
-import { AnnotationDimensions } from '../../../annotations/annotation_utils';
+import { AnnotationDimensions } from '../../../annotations/types';
import { AnnotationSpec, isLineAnnotation, isRectAnnotation } from '../../../utils/specs';
import { getSpecsById } from '../../../state/utils';
import { AnnotationId } from '../../../../../utils/ids';
import { mergeWithDefaultAnnotationLine, mergeWithDefaultAnnotationRect } from '../../../../../utils/themes/theme';
import { renderLineAnnotations } from './lines';
-import { AnnotationLineProps } from '../../../annotations/line_annotation_tooltip';
+import { AnnotationLineProps } from '../../../annotations/line/types';
import { renderRectAnnotations } from './rect';
-import { AnnotationRectProps } from '../../../annotations/rect_annotation_tooltip';
+import { AnnotationRectProps } from '../../../annotations/rect/types';
interface AnnotationProps {
annotationDimensions: Map;
diff --git a/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts b/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts
index 8d6e6fae62..216787f21b 100644
--- a/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts
+++ b/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts
@@ -18,7 +18,7 @@
import { Stroke, Line } from '../../../../../geoms/types';
import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils';
-import { AnnotationLineProps } from '../../../annotations/line_annotation_tooltip';
+import { AnnotationLineProps } from '../../../annotations/line/types';
import { LineAnnotationStyle } from '../../../../../utils/themes/theme';
import { renderMultiLine } from '../primitives/line';
diff --git a/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts b/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts
index 78e77c62da..914a54cc03 100644
--- a/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts
+++ b/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts
@@ -18,7 +18,7 @@
import { renderRect } from '../primitives/rect';
import { Rect, Fill, Stroke } from '../../../../../geoms/types';
-import { AnnotationRectProps } from '../../../annotations/rect_annotation_tooltip';
+import { AnnotationRectProps } from '../../../annotations/rect/types';
import { RectAnnotationStyle } from '../../../../../utils/themes/theme';
import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils';
import { withContext } from '../../../../../renderers/canvas';
diff --git a/src/chart_types/xy_chart/renderer/canvas/primitives/rect.ts b/src/chart_types/xy_chart/renderer/canvas/primitives/rect.ts
index ce09ed5426..35a54abf5f 100644
--- a/src/chart_types/xy_chart/renderer/canvas/primitives/rect.ts
+++ b/src/chart_types/xy_chart/renderer/canvas/primitives/rect.ts
@@ -33,7 +33,6 @@ export function renderRect(
if (fill) {
const borderOffset = !disableBoardOffset && stroke && stroke.width > 0.001 ? stroke.width : 0;
- // console.log(stroke, borderOffset);
const x = rect.x + borderOffset;
const y = rect.y + borderOffset;
const width = rect.width - borderOffset * 2;
@@ -45,7 +44,6 @@ export function renderRect(
if (stroke && stroke.width > 0.001) {
const borderOffset = !disableBoardOffset && stroke && stroke.width > 0.001 ? stroke.width / 2 : 0;
- // console.log(stroke, borderOffset);
const x = rect.x + borderOffset;
const y = rect.y + borderOffset;
const width = rect.width - borderOffset * 2;
diff --git a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx
index e20876f73e..07c04b81d5 100644
--- a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx
+++ b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx
@@ -30,7 +30,7 @@ import { Dimensions } from '../../../../utils/dimensions';
import { AnnotationId, AxisId } from '../../../../utils/ids';
import { LIGHT_THEME } from '../../../../utils/themes/light_theme';
import { Theme } from '../../../../utils/themes/theme';
-import { AnnotationDimensions } from '../../annotations/annotation_utils';
+import { AnnotationDimensions } from '../../annotations/types';
import { LegendItem } from '../../../../commons/legend';
import { computeAnnotationDimensionsSelector } from '../../state/selectors/compute_annotations';
import { computeAxisTicksDimensionsSelector } from '../../state/selectors/compute_axis_ticks_dimensions';
diff --git a/src/chart_types/xy_chart/renderer/dom/annotation_tooltips.tsx b/src/chart_types/xy_chart/renderer/dom/annotation_tooltips.tsx
index b88ff7cf39..64e13428ec 100644
--- a/src/chart_types/xy_chart/renderer/dom/annotation_tooltips.tsx
+++ b/src/chart_types/xy_chart/renderer/dom/annotation_tooltips.tsx
@@ -19,11 +19,7 @@
import React from 'react';
import { isLineAnnotation, AnnotationSpec, AnnotationTypes } from '../../utils/specs';
import { AnnotationId } from '../../../../utils/ids';
-import {
- AnnotationDimensions,
- AnnotationTooltipState,
- AnnotationTooltipFormatter,
-} from '../../annotations/annotation_utils';
+import { AnnotationDimensions, AnnotationTooltipState, AnnotationTooltipFormatter } from '../../annotations/types';
import { connect } from 'react-redux';
import { Dimensions } from '../../../../utils/dimensions';
import { GlobalChartState, BackwardRef } from '../../../../state/chart_state';
@@ -32,11 +28,12 @@ import { computeAnnotationDimensionsSelector } from '../../state/selectors/compu
import { getAnnotationSpecsSelector } from '../../state/selectors/get_specs';
import { getAnnotationTooltipStateSelector } from '../../state/selectors/get_annotation_tooltip_state';
import { isChartEmptySelector } from '../../state/selectors/is_chart_empty';
-import { AnnotationLineProps } from '../../annotations/line_annotation_tooltip';
+import { AnnotationLineProps } from '../../annotations/line/types';
import { computeChartDimensionsSelector } from '../../state/selectors/compute_chart_dimensions';
import { createPortal } from 'react-dom';
-import { getFinalAnnotationTooltipPosition } from '../../annotations/annotation_tooltip';
+import { getFinalAnnotationTooltipPosition } from '../../annotations/tooltip';
import { getSpecsById } from '../../state/utils';
+import { Position } from '../../../../utils/commons';
interface AnnotationTooltipStateProps {
isChartEmpty: boolean;
@@ -124,8 +121,8 @@ class AnnotationTooltipComponent extends React.Component
if (tooltipStyle.left) {
this.portalNode.style.left = tooltipStyle.left;
if (this.tooltipRef.current) {
- this.tooltipRef.current.style.left = tooltipStyle.anchor === 'right' ? 'auto' : '0px';
- this.tooltipRef.current.style.right = tooltipStyle.anchor === 'right' ? '0px' : 'auto';
+ this.tooltipRef.current.style.left = tooltipStyle.anchor === Position.Right ? 'auto' : '0px';
+ this.tooltipRef.current.style.right = tooltipStyle.anchor === Position.Right ? '0px' : 'auto';
}
}
if (tooltipStyle.top) {
diff --git a/src/chart_types/xy_chart/rendering/rendering.ts b/src/chart_types/xy_chart/rendering/rendering.ts
index 90c604b160..bbd27f2f0d 100644
--- a/src/chart_types/xy_chart/rendering/rendering.ts
+++ b/src/chart_types/xy_chart/rendering/rendering.ts
@@ -28,7 +28,7 @@ import {
LineStyle,
BubbleSeriesStyle,
} from '../../../utils/themes/theme';
-import { Scale, ScaleType, isLogarithmicScale } from '../../../scales';
+import { Scale, ScaleType } from '../../../scales';
import { CurveType, getCurveFactory } from '../../../utils/curves';
import { DataSeriesDatum, DataSeries, XYChartSeriesIdentifier } from '../utils/series';
import { DisplayValueSpec, PointStyleAccessor, BarStyleAccessor } from '../utils/specs';
@@ -47,6 +47,7 @@ import { LegendItem } from '../../../commons/legend';
import { IndexedGeometryMap, GeometryType } from '../utils/indexed_geometry_map';
import { getDistance } from '../state/utils';
import { MarkBuffer } from '../../../specs';
+import { isLogarithmicScale } from '../../../scales/types';
export const DEFAULT_HIGHLIGHT_PADDING = 10;
diff --git a/src/chart_types/xy_chart/state/selectors/compute_annotations.ts b/src/chart_types/xy_chart/state/selectors/compute_annotations.ts
index 51d9a243d9..3e9003a7b4 100644
--- a/src/chart_types/xy_chart/state/selectors/compute_annotations.ts
+++ b/src/chart_types/xy_chart/state/selectors/compute_annotations.ts
@@ -20,9 +20,9 @@ import createCachedSelector from 're-reselect';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
import { getAxisSpecsSelector, getAnnotationSpecsSelector } from './get_specs';
import { computeChartDimensionsSelector } from './compute_chart_dimensions';
-import { countBarsInClusterSelector } from './count_bars_in_cluster';
import { isHistogramModeEnabledSelector } from './is_histogram_mode_enabled';
-import { computeAnnotationDimensions, AnnotationDimensions } from '../../annotations/annotation_utils';
+import { computeAnnotationDimensions } from '../../annotations/utils';
+import { AnnotationDimensions } from '../../annotations/types';
import { computeSeriesGeometriesSelector } from './compute_series_geometries';
import { AnnotationId } from '../../../../utils/ids';
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
@@ -35,7 +35,6 @@ export const computeAnnotationDimensionsSelector = createCachedSelector(
getSettingsSpecSelector,
computeSeriesGeometriesSelector,
getAxisSpecsSelector,
- countBarsInClusterSelector,
isHistogramModeEnabledSelector,
getAxisSpecsSelector,
],
@@ -43,12 +42,10 @@ export const computeAnnotationDimensionsSelector = createCachedSelector(
annotationSpecs,
chartDimensions,
settingsSpec,
- seriesGeometries,
+ { scales: { yScales, xScale } },
axesSpecs,
- totalBarsInCluster,
isHistogramMode,
): Map => {
- const { yScales, xScale } = seriesGeometries.scales;
return computeAnnotationDimensions(
annotationSpecs,
chartDimensions.chartDimensions,
@@ -56,7 +53,6 @@ export const computeAnnotationDimensionsSelector = createCachedSelector(
yScales,
xScale,
axesSpecs,
- totalBarsInCluster,
isHistogramMode,
);
},
diff --git a/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts b/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts
index 0dab98fa53..c653258f19 100644
--- a/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts
+++ b/src/chart_types/xy_chart/state/selectors/get_annotation_tooltip_state.ts
@@ -23,11 +23,8 @@ import { computeChartDimensionsSelector } from './compute_chart_dimensions';
import { getAxisSpecsSelector, getAnnotationSpecsSelector } from './get_specs';
import { AxisSpec, AnnotationSpec, AnnotationTypes } from '../../utils/specs';
import { Rotation } from '../../../../utils/commons';
-import {
- computeAnnotationTooltipState,
- AnnotationTooltipState,
- AnnotationDimensions,
-} from '../../annotations/annotation_utils';
+import { computeAnnotationTooltipState } from '../../annotations/tooltip';
+import { AnnotationTooltipState, AnnotationDimensions } from '../../annotations/types';
import { computeAnnotationDimensionsSelector } from './compute_annotations';
import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation';
import { AnnotationId } from '../../../../utils/ids';
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..30e98914bd 100644
--- a/src/chart_types/xy_chart/utils/specs.ts
+++ b/src/chart_types/xy_chart/utils/specs.ts
@@ -33,9 +33,10 @@ import { AxisId, GroupId } from '../../../utils/ids';
import { ScaleContinuousType, ScaleType } from '../../../scales';
import { CurveType } from '../../../utils/curves';
import { RawDataSeriesDatum, XYChartSeriesIdentifier } from './series';
-import { AnnotationTooltipFormatter } from '../annotations/annotation_utils';
+import { AnnotationTooltipFormatter } from '../annotations/types';
import { SpecTypes, Spec } from '../../../specs';
import { ChartTypes } from '../..';
+import { PrimitiveValue } from '../../partition_chart/layout/utils/group_by_rollup';
export type BarStyleOverride = RecursivePartial | Color | null;
export type PointStyleOverride = RecursivePartial | Color | null;
@@ -630,19 +631,19 @@ export interface RectAnnotationDatum {
/**
* The minuimum value on the x axis. If undefined, the minuimum value of the x domain will be used.
*/
- x0?: any;
+ x0?: PrimitiveValue;
/**
* The maximum value on the x axis. If undefined, the maximum value of the x domain will be used.
*/
- x1?: any;
+ x1?: PrimitiveValue;
/**
* The minimum value on the y axis. If undefined, the minimum value of the y domain will be used.
*/
- y0?: any;
+ y0?: PrimitiveValue;
/**
* The maximum value on the y axis. If undefined, the maximum value of the y domain will be used.
*/
- y1?: any;
+ y1?: PrimitiveValue;
};
/**
* A textual description of the annotation
@@ -668,7 +669,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/index.ts b/src/index.ts
index 3d187a7ee2..b3a3b0c30f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -32,7 +32,7 @@ export { timeFormatter, niceTimeFormatter, niceTimeFormatByDay } from './utils/d
export { Datum, Position, Rendering, Rotation } from './utils/commons';
export { SeriesIdentifier } from './commons/series_id';
export { XYChartSeriesIdentifier } from './chart_types/xy_chart/utils/series';
-export { AnnotationTooltipFormatter } from './chart_types/xy_chart/annotations/annotation_utils';
+export { AnnotationTooltipFormatter } from './chart_types/xy_chart/annotations/types';
export { GeometryValue } from './utils/geometry';
export {
Config as PartitionConfig,
diff --git a/src/mocks/specs/specs.ts b/src/mocks/specs/specs.ts
index 06d830f329..d25386618b 100644
--- a/src/mocks/specs/specs.ts
+++ b/src/mocks/specs/specs.ts
@@ -28,6 +28,11 @@ import {
BasicSeriesSpec,
SeriesTypes,
BubbleSeriesSpec,
+ LineAnnotationSpec,
+ RectAnnotationSpec,
+ AnnotationTypes,
+ AnnotationDomainTypes,
+ AxisSpec,
} from '../../chart_types/xy_chart/utils/specs';
import { ScaleType } from '../../scales';
import { ChartTypes } from '../../chart_types';
@@ -208,7 +213,7 @@ export class MockSeriesSpec {
});
}
- static byType(type?: SeriesTypes): BasicSeriesSpec {
+ static byType(type?: SeriesTypes | 'histogram'): BasicSeriesSpec {
switch (type) {
case SeriesTypes.Line:
return MockSeriesSpec.lineBase;
@@ -216,11 +221,26 @@ export class MockSeriesSpec {
return MockSeriesSpec.areaBase;
case SeriesTypes.Bubble:
return MockSeriesSpec.bubbleBase;
+ case 'histogram':
+ return MockSeriesSpec.histogramBarBase;
case SeriesTypes.Bar:
default:
return MockSeriesSpec.barBase;
}
}
+ static byTypePartial(type?: 'line' | 'bar' | 'area' | 'histogram') {
+ switch (type) {
+ case 'line':
+ return MockSeriesSpec.line;
+ case 'area':
+ return MockSeriesSpec.area;
+ case 'histogram':
+ return MockSeriesSpec.histogramBar;
+ case 'bar':
+ default:
+ return MockSeriesSpec.bar;
+ }
+ }
}
export class MockSeriesSpecs {
@@ -264,7 +284,72 @@ export class MockGlobalSpec {
theme: LIGHT_THEME,
};
+ private static readonly axisBase: AxisSpec = {
+ id: 'yAxis',
+ chartType: ChartTypes.XYAxis,
+ specType: SpecTypes.Axis,
+ groupId: DEFAULT_GLOBAL_ID,
+ hide: false,
+ showOverlappingTicks: false,
+ showOverlappingLabels: false,
+ position: Position.Left,
+ tickSize: 10,
+ tickPadding: 10,
+ tickFormat: (tick: any) => `${tick}`,
+ tickLabelRotation: 0,
+ };
+
+ 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,
+ histogramPadding: 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,
+ });
+ }
+ static axis(partial?: Partial): AxisSpec {
+ return mergePartial(MockGlobalSpec.axisBase, 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 }));
+ }
+}
diff --git a/src/scales/index.ts b/src/scales/index.ts
index 12c30a5626..99bffc752a 100644
--- a/src/scales/index.ts
+++ b/src/scales/index.ts
@@ -79,11 +79,3 @@ export { ScaleBand } from './scale_band';
/** @internal */
export { ScaleContinuous } from './scale_continuous';
-
-/**
- * Check if a scale is logaritmic
- * @internal
- */
-export function isLogarithmicScale(scale: Scale) {
- return scale.type === ScaleType.Log;
-}
diff --git a/src/scales/scale_band.ts b/src/scales/scale_band.ts
index ed8efa41e1..86a4eace8b 100644
--- a/src/scales/scale_band.ts
+++ b/src/scales/scale_band.ts
@@ -31,6 +31,9 @@ export class ScaleBand implements Scale {
readonly bandwidth: number;
readonly bandwidthPadding: number;
readonly step: number;
+ readonly outerPadding: number;
+ readonly innerPadding: number;
+ readonly originalBandwidth: number;
readonly type: ScaleType;
readonly domain: any[];
readonly range: number[];
@@ -59,7 +62,10 @@ export class ScaleBand implements Scale {
this.barsPadding = safeBarPadding;
this.d3Scale.paddingInner(safeBarPadding);
this.d3Scale.paddingOuter(safeBarPadding / 2);
+ this.outerPadding = this.d3Scale.paddingOuter();
+ this.innerPadding = this.d3Scale.paddingInner();
this.bandwidth = this.d3Scale.bandwidth() || 0;
+ this.originalBandwidth = this.d3Scale.bandwidth() || 0;
this.step = this.d3Scale.step();
this.domain = this.d3Scale.domain();
this.range = range.slice();
diff --git a/src/scales/scale_continuous.test.ts b/src/scales/scale_continuous.test.ts
index 8501da4c21..fa9a8a2045 100644
--- a/src/scales/scale_continuous.test.ts
+++ b/src/scales/scale_continuous.test.ts
@@ -20,7 +20,8 @@ import { XDomain } from '../chart_types/xy_chart/domains/x_domain';
import { computeXScale } from '../chart_types/xy_chart/utils/scales';
import { Domain } from '../utils/domain';
import { DateTime, Settings } from 'luxon';
-import { ScaleContinuous, ScaleType, ScaleBand, isLogarithmicScale } from '.';
+import { ScaleContinuous, ScaleType, ScaleBand } from '.';
+import { isLogarithmicScale } from './types';
describe('Scale Continuous', () => {
test('shall invert on continuous scale linear', () => {
diff --git a/src/scales/types.ts b/src/scales/types.ts
new file mode 100644
index 0000000000..23cc8dbe9b
--- /dev/null
+++ b/src/scales/types.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { ScaleContinuous } from './scale_continuous';
+import { Scale, ScaleType } from '.';
+import { ScaleBand } from './scale_band';
+
+/**
+ * Check if a scale is logaritmic
+ * @internal
+ */
+export function isLogarithmicScale(scale: Scale): scale is ScaleContinuous {
+ return scale.type === ScaleType.Log;
+}
+
+/**
+ * Check if a scale is Band
+ * @internal
+ */
+export function isBandScale(scale: Scale): scale is ScaleBand {
+ return scale.type === ScaleType.Ordinal;
+}
+
+/**
+ * Check if a scale is continuous
+ * @internal
+ */
+export function isContinuousScale(scale: Scale): scale is ScaleContinuous {
+ return scale.type !== ScaleType.Ordinal;
+}
diff --git a/stories/annotations/rects/1_linear_bar_chart.tsx b/stories/annotations/rects/1_linear_bar_chart.tsx
index 57256aaf27..a466233eff 100644
--- a/stories/annotations/rects/1_linear_bar_chart.tsx
+++ b/stories/annotations/rects/1_linear_bar_chart.tsx
@@ -26,22 +26,24 @@ export const example = () => {
const debug = boolean('debug', false);
const rotation = getChartRotationKnob();
- const dataValues = [
- {
- coordinates: {
- x0: 0,
- x1: 1,
- y0: 0,
- y1: 7,
- },
- details: 'details about this annotation',
- },
- ];
-
return (
-
+
{
yAccessors={['y']}
data={[
{ x: 0, y: 2 },
- { x: 1, y: 7 },
+ { x: 1, y: 3 },
{ x: 3, y: 6 },
]}
/>
);
};
+
+example.story = {
+ parameters: {
+ info: {
+ text: `A \`\` can be used to create a rectangular annotation.
+As for most chart component, the required props are: \`id\` to uniquely identify the annotation and
+a \`dataValues\` prop that describes one or more annotations.
+
+The \`dataValues\` prop takes an array of objects adhering to the following type:
+
+\`\`\`ts
+
+interface RectAnnotationDatum {
+ coordinates: {
+ x0?: PrimitiveValue;
+ x1?: PrimitiveValue;
+ y0?: PrimitiveValue;
+ y1?: PrimitiveValue;
+ };
+ details?: string;
+}
+
+type PrimitiveValue = string | number | null;
+\`\`\`
+
+Each coordinate value can be omitted, if omitted then the corresponding min or max value is used instead.
+A text can be issued to be shown within the tooltip. If omitted, no tooltip will be shown.
+
+In the above example, we are using a fixed set of coordinates:
+\`\`\`
+coordinates: {
+ x0: 0,
+ x1: 1,
+ y0: 0,
+ y1: 7,
+}
+\`\`\`
+
+This annotation will cover the X axis starting from the \`0\` value to the \`1\` value included. The \`y\` is covered from 0 to 7.
+In a barchart with linear or ordinal x scale, the interval covered by the annotation fully include the \`x0\` and \`x1\` values.
+If one value is out of the relative domain, we will clip the annotation to the max/min value of the chart domain.
+ `,
+ },
+ },
+};
diff --git a/stories/annotations/rects/2_ordinal_bar_chart.tsx b/stories/annotations/rects/2_ordinal_bar_chart.tsx
index 359e4cdde6..8ab086e9fb 100644
--- a/stories/annotations/rects/2_ordinal_bar_chart.tsx
+++ b/stories/annotations/rects/2_ordinal_bar_chart.tsx
@@ -26,20 +26,22 @@ export const example = () => {
const debug = boolean('debug', false);
const rotation = getChartRotationKnob();
- const dataValues = [
- {
- coordinates: {
- x0: 'a',
- x1: 'b',
- },
- details: 'details about this annotation',
- },
- ];
-
return (
-
+
{
yAccessors={['y']}
data={[
{ x: 'a', y: 2 },
- { x: 'b', y: 7 },
+ { x: 'b', y: 3 },
{ x: 'c', y: 0 },
{ x: 'd', y: 6 },
]}
@@ -58,3 +60,14 @@ export const example = () => {
);
};
+
+example.story = {
+ parameters: {
+ info: {
+ text: `On Ordinal Bar charts, you can draw a rectangular annotation the same way it's done within a linear bar chart.
+The annotation will cover fully the extent defined by the \`coordinate\` object, extending to the max/min domain values any
+missing/out-of-range parameters.
+ `,
+ },
+ },
+};
diff --git a/stories/annotations/rects/3_linear_line_chart.tsx b/stories/annotations/rects/3_linear_line_chart.tsx
index 7de9e38c02..48eb2adc9a 100644
--- a/stories/annotations/rects/3_linear_line_chart.tsx
+++ b/stories/annotations/rects/3_linear_line_chart.tsx
@@ -28,7 +28,7 @@ export const example = () => {
const rotation = getChartRotationKnob();
const definedCoordinate = select(
- 'defined coordinate',
+ 'green annotation defined coordinate',
{
x0: 'x0',
x1: 'x1',
@@ -38,7 +38,7 @@ export const example = () => {
'x0',
);
- const dataValues: RectAnnotationDatum[] = [
+ const dataValuesRed: RectAnnotationDatum[] = [
{
coordinates: {
x0: 1,
@@ -46,8 +46,10 @@ export const example = () => {
y0: 0,
y1: 7,
},
- details: 'details about this annotation',
+ details: 'red annotation',
},
+ ];
+ const dataValuesBlue: RectAnnotationDatum[] = [
{
coordinates: {
x0: 2.0,
@@ -55,16 +57,18 @@ export const example = () => {
y0: 0,
y1: 7,
},
- details: 'details about this annotation',
+ details: 'blue annotation',
},
+ ];
+ const dataValuesGreen: RectAnnotationDatum[] = [
{
coordinates: {
- x0: definedCoordinate === 'x0' ? 0.25 : null,
- x1: definedCoordinate === 'x1' ? 2.75 : null,
- y0: definedCoordinate === BandedAccessorType.Y0 ? 0.25 : null,
- y1: definedCoordinate === BandedAccessorType.Y1 ? 6.75 : null,
+ x0: definedCoordinate === 'x0' ? 0.5 : null,
+ x1: definedCoordinate === 'x1' ? 2.5 : null,
+ y0: definedCoordinate === BandedAccessorType.Y0 ? 1.5 : null,
+ y1: definedCoordinate === BandedAccessorType.Y1 ? 5.5 : null,
},
- details: 'can have null values',
+ details: 'green annotation',
},
];
@@ -79,7 +83,9 @@ export const example = () => {
return (
-
+
+
+
{
marker={}
/>
+