diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-basic-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-basic-visually-looks-correct-1-snap.png index 816b0ac36e6..51df50f3a89 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-basic-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-basic-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-categorical-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-categorical-visually-looks-correct-1-snap.png index 38ad4f6a187..5c55c07ee7b 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-categorical-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-categorical-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-label-rotation-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-label-rotation-visually-looks-correct-1-snap.png new file mode 100644 index 00000000000..885a9204b08 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-label-rotation-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-theming-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-theming-visually-looks-correct-1-snap.png index 5e433a928ee..0ae995b5b5d 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-theming-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-theming-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-time-snap-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-time-snap-visually-looks-correct-1-snap.png index 8dbe6fa548b..83085eba290 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-time-snap-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-time-snap-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-time-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-time-visually-looks-correct-1-snap.png index b0c392f36d5..ee4ebb16093 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-time-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-heatmap-alpha-time-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-rotate-categorical-axis-labels-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-rotate-categorical-axis-labels-1-snap.png new file mode 100644 index 00000000000..4ee50df4728 Binary files /dev/null and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-rotate-categorical-axis-labels-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-rotate-time-axis-labels-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-rotate-time-axis-labels-1-snap.png new file mode 100644 index 00000000000..ba5e7fce9e8 Binary files /dev/null and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-rotate-time-axis-labels-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-maximize-the-label-font-size-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-maximize-the-label-font-size-1-snap.png index 36e7156e57d..a330c768ba1 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-maximize-the-label-font-size-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-maximize-the-label-font-size-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-maximize-the-label-with-an-unique-font-size-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-maximize-the-label-with-an-unique-font-size-1-snap.png index 1490fdbeaaa..be26b285751 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-maximize-the-label-with-an-unique-font-size-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-maximize-the-label-with-an-unique-font-size-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-not-have-brush-tool-extend-into-axes-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-not-have-brush-tool-extend-into-axes-1-snap.png index 1c8c9ad5a26..ac44328b879 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-not-have-brush-tool-extend-into-axes-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-not-have-brush-tool-extend-into-axes-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-show-x-and-y-axis-titles-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-show-x-and-y-axis-titles-1-snap.png index a1c08e1195b..2dc7ba0c327 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-show-x-and-y-axis-titles-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-should-show-x-and-y-axis-titles-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-dark-should-render-basic-heatmap-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-dark-should-render-basic-heatmap-1-snap.png index 3837c5a1df3..b2657836517 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-dark-should-render-basic-heatmap-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-dark-should-render-basic-heatmap-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-dark-should-render-correct-brush-area-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-dark-should-render-correct-brush-area-1-snap.png index ece1d39a2f4..f1d906d8c1c 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-dark-should-render-correct-brush-area-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-dark-should-render-correct-brush-area-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-dark-should-render-basic-heatmap-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-dark-should-render-basic-heatmap-1-snap.png index e0e70bd0974..4da20619dbb 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-dark-should-render-basic-heatmap-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-dark-should-render-basic-heatmap-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-dark-should-render-correct-brush-area-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-dark-should-render-correct-brush-area-1-snap.png index 8f5ba5300a3..af404efa4f9 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-dark-should-render-correct-brush-area-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-dark-should-render-correct-brush-area-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-light-should-render-basic-heatmap-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-light-should-render-basic-heatmap-1-snap.png index a875648be84..d941530b898 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-light-should-render-basic-heatmap-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-light-should-render-basic-heatmap-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-light-should-render-correct-brush-area-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-light-should-render-correct-brush-area-1-snap.png index 6c989b5606f..05b30f932e4 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-light-should-render-correct-brush-area-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-eui-light-should-render-correct-brush-area-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-light-should-render-basic-heatmap-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-light-should-render-basic-heatmap-1-snap.png index 816b0ac36e6..51df50f3a89 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-light-should-render-basic-heatmap-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-light-should-render-basic-heatmap-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-light-should-render-correct-brush-area-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-light-should-render-correct-brush-area-1-snap.png index 6b2c69398ef..cd02801ae68 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-light-should-render-correct-brush-area-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-theme-light-should-render-correct-brush-area-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-2-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-2-1-snap.png index ba30985070b..bb44d9bcb55 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-2-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-2-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-3-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-3-1-snap.png index ca92e994e3e..9bbab5aabe1 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-3-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-3-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-4-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-4-1-snap.png index 72027168191..9e47947f3bf 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-4-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-4-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-5-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-5-1-snap.png index 2f54e9617ae..6c44c3e3799 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-5-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-5-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-6-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-6-1-snap.png index d237e61c44f..6efa9b4787d 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-6-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-6-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-7-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-7-1-snap.png index a1fae5f1a52..a868c69fc4a 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-7-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-7-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-8-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-8-1-snap.png index a07fe2944c8..6e71c84ff34 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-8-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-8-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-9-1-snap.png b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-9-1-snap.png index 61ad3c21cb8..1b61fcdcb65 100644 Binary files a/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-9-1-snap.png and b/integration/tests/__image_snapshots__/heatmap-stories-test-ts-heatmap-stories-time-snap-with-dataset-9-1-snap.png differ diff --git a/integration/tests/heatmap_stories.test.ts b/integration/tests/heatmap_stories.test.ts index b342a1f4b70..c7ab2e9d777 100644 --- a/integration/tests/heatmap_stories.test.ts +++ b/integration/tests/heatmap_stories.test.ts @@ -58,4 +58,16 @@ describe('Heatmap stories', () => { 'http://localhost:9001/?path=/story/heatmap-alpha--basic&knob-Show%20x%20axis%20title=true&knob-Show%20y%20axis%20title=true', ); }); + + test('rotate categorical axis labels', async () => { + await common.expectChartAtUrlToMatchScreenshot( + 'http://localhost:9001/?path=/story/heatmap-alpha--label-rotation&globals=theme:light&knob-Y-axis auto width=true&knob-Y-axis width=50&knob-X-Axis visible=true&knob-X-Axis label fontSize=12&knob-X-Axis label padding=6&knob-X-Axis label rotation=45&knob-Use categorical data=true&knob-Show legend=', + ); + }); + + test('rotate time axis labels', async () => { + await common.expectChartAtUrlToMatchScreenshot( + 'http://localhost:9001/?path=/story/heatmap-alpha--label-rotation&globals=theme:light&knob-Y-axis auto width=true&knob-Y-axis width=50&knob-X-Axis visible=true&knob-X-Axis label fontSize=12&knob-X-Axis label padding=6&knob-X-Axis label rotation=45&knob-Use categorical data=&knob-Show legend=', + ); + }); }); diff --git a/packages/charts/api/charts.api.md b/packages/charts/api/charts.api.md index 34e3261f893..be15fa1fc78 100644 --- a/packages/charts/api/charts.api.md +++ b/packages/charts/api/charts.api.md @@ -1086,7 +1086,7 @@ export type GroupId = string; export type GroupKeysOrKeyFn = Array | GroupByKeyFn; // @alpha -export const Heatmap: (props: SFProps, "chartType" | "specType", "data" | "timeZone" | "valueAccessor" | "valueFormatter" | "xAccessor" | "yAccessor" | "xSortPredicate" | "ySortPredicate" | "xScale" | "xAxisLabelFormatter" | "xAxisTitle" | "yAxisTitle" | "yAxisLabelFormatter" | "xAxisLabelName" | "yAxisLabelName", "name" | "onBrushEnd" | "highlightedData", "id" | "colorScale">) => null; +export const Heatmap: (props: SFProps, "chartType" | "specType", "data" | "timeZone" | "valueAccessor" | "valueFormatter" | "xAccessor" | "yAccessor" | "xSortPredicate" | "ySortPredicate" | "xScale" | "xAxisTitle" | "yAxisTitle" | "xAxisLabelFormatter" | "yAxisLabelFormatter" | "xAxisLabelName" | "yAxisLabelName", "name" | "onBrushEnd" | "highlightedData", "id" | "colorScale">) => null; // @alpha (undocumented) export interface HeatmapBandsColorScale { @@ -1222,11 +1222,9 @@ export interface HeatmapStyle { // (undocumented) xAxisLabel: Font & { fontSize: Pixels; - width: Pixels | 'auto'; - align: TextAlign; - baseline: TextBaseline; visible: boolean; padding: Pixels | Padding; + rotation: number; }; // (undocumented) yAxisLabel: Font & { @@ -1234,7 +1232,6 @@ export interface HeatmapStyle { width: Pixels | 'auto' | { max: Pixels; }; - baseline: TextBaseline; visible: boolean; padding: Pixels | Padding; }; @@ -2683,8 +2680,6 @@ export type YDomainRange = YDomainBase & DomainRange & LogScaleOptions; // Warnings were encountered during analysis: // // src/chart_types/partition_chart/layout/types/config.ts:60:5 - (ae-forgotten-export) The symbol "TimeMs" needs to be exported by the entry point index.d.ts -// src/utils/themes/theme.ts:217:5 - (ae-forgotten-export) The symbol "TextAlign" needs to be exported by the entry point index.d.ts -// src/utils/themes/theme.ts:218:5 - (ae-forgotten-export) The symbol "TextBaseline" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/charts/src/chart_types/heatmap/layout/types/viewmodel_types.ts b/packages/charts/src/chart_types/heatmap/layout/types/viewmodel_types.ts index dcf991e3bbc..07d43409966 100644 --- a/packages/charts/src/chart_types/heatmap/layout/types/viewmodel_types.ts +++ b/packages/charts/src/chart_types/heatmap/layout/types/viewmodel_types.ts @@ -9,7 +9,7 @@ import { ChartType } from '../../..'; import { Color, Colors } from '../../../../common/colors'; import { Pixels } from '../../../../common/geometry'; -import { Box, Font } from '../../../../common/text_utils'; +import { Box, Font, TextAlign } from '../../../../common/text_utils'; import { Fill, Line, Rect, Stroke } from '../../../../geoms/types'; import { HeatmapBrushEvent } from '../../../../specs/settings'; import { Point } from '../../../../utils/point'; @@ -40,6 +40,7 @@ export interface TextBox extends Box { value: NonNullable; x: number; y: number; + align: TextAlign; } /** @internal */ diff --git a/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts b/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts index dfc017a1c40..cda42c6d48a 100644 --- a/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts +++ b/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts @@ -13,13 +13,12 @@ import { colorToRgba } from '../../../../common/color_library_wrappers'; import { fillTextColor } from '../../../../common/fill_text_color'; import { Pixels } from '../../../../common/geometry'; import { Box, Font, maximiseFontSize } from '../../../../common/text_utils'; -import { ScaleContinuous } from '../../../../scales'; import { ScaleType } from '../../../../scales/constants'; import { LinearScale, OrdinalScale, RasterTimeScale } from '../../../../specs'; -import { TextMeasure, withTextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator'; +import { TextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator'; import { addIntervalToTime } from '../../../../utils/chrono/elasticsearch'; import { clamp, Datum } from '../../../../utils/common'; -import { Dimensions, horizontalPad, innerPad, pad } from '../../../../utils/dimensions'; +import { innerPad, pad } from '../../../../utils/dimensions'; import { Logger } from '../../../../utils/logger'; import { HeatmapStyle, Theme, Visible } from '../../../../utils/themes/theme'; import { PrimitiveValue } from '../../../partition_chart/layout/utils/group_by_rollup'; @@ -54,29 +53,6 @@ function getValuesInRange( return values.slice(startIndex, endIndex); } -function estimatedNonOverlappingTickCount( - chartWidth: number, - style: HeatmapStyle['xAxisLabel'], - sampleLabel: string, -): number { - return withTextMeasure((textMeasure) => { - const { width } = textMeasure( - sampleLabel, - { - fontFamily: style.fontFamily, - fontWeight: style.fontWeight, - fontVariant: style.fontVariant, - fontStyle: style.fontStyle, - }, - style.fontSize, - ); - const maxTicks = chartWidth / (width + horizontalPad(style.padding)); - // Dividing by 2 is a temp fix to make sure {@link ScaleContinuous} won't produce - // to many ticks creating nice rounded tick values - // TODO add support for limiting the number of tick in {@link ScaleContinuous} - return Math.floor(maxTicks / 2); - }); -} /** @internal */ export function shapeViewModel( textMeasure: TextMeasure, @@ -125,7 +101,7 @@ export function shapeViewModel( const currentGridHeight = elementSizes.grid.height; // compute the position of each column label - const textXValues = getXTicks(spec, heatmapTheme, elementSizes.grid, xScale, heatmapTable); + const textXValues = getXTicks(spec, heatmapTheme.xAxisLabel, xScale, heatmapTable.xValues); const { padding } = heatmapTheme.yAxisLabel; @@ -136,6 +112,7 @@ export function shapeViewModel( // position of the Y labels x: -pad(padding, 'right'), y: cellHeight / 2 + (yScale(d.value) || 0), + align: 'right', }; }); @@ -337,20 +314,23 @@ export function shapeViewModel( return pickHighlightedArea(area.x, area.y); }; - // vertical lines - const xLines = Array.from({ length: xValues.length + 1 }, (d, i) => ({ - x1: elementSizes.grid.left + i * cellWidth, - x2: elementSizes.grid.left + i * cellWidth, - y1: elementSizes.grid.top, - y2: currentGridHeight, - })); + // ordered left-right vertical lines + const xLines = Array.from({ length: xValues.length + 1 }, (d, i) => { + const xAxisExtension = i % elementSizes.xAxisTickCadence === 0 ? 5 : 0; + return { + x1: elementSizes.grid.left + i * cellWidth, + x2: elementSizes.grid.left + i * cellWidth, + y1: elementSizes.grid.top, + y2: currentGridHeight + xAxisExtension, + }; + }); // horizontal lines const yLines = Array.from({ length: elementSizes.visibleNumberOfRows + 1 }, (d, i) => ({ x1: elementSizes.grid.left, x2: elementSizes.grid.left + elementSizes.grid.width, - y1: i * cellHeight, - y2: i * cellHeight, + y1: elementSizes.grid.top + i * cellHeight, + y2: elementSizes.grid.top + i * cellHeight, })); const cells = Object.values(cellMap); @@ -434,45 +414,21 @@ export function isRasterTimeScale(scale: RasterTimeScale | OrdinalScale | Linear function getXTicks( spec: HeatmapSpec, - style: HeatmapStyle, - grid: Dimensions, - xScale: ScaleBand, - { xValues, xNumericExtent }: HeatmapTable, + style: HeatmapStyle['xAxisLabel'], + scale: ScaleBand, + values: NonNullable[], ): Array { - const getTextValue = ( - formatter: HeatmapSpec['xAxisLabelFormatter'], - scaleCallback: (x: string | number) => number | undefined | null, - ) => (value: string | number): TextBox => { + const isTimeScale = isRasterTimeScale(spec.xScale); + const isRotated = style.rotation !== 0; + return values.map((value) => { return { - text: formatter(value), + text: spec.xAxisLabelFormatter(value), value, isValue: false, - ...style.xAxisLabel, - x: scaleCallback(value) ?? 0, - y: style.xAxisLabel.fontSize / 2 + pad(style.xAxisLabel.padding, 'top'), - }; - }; - if (isRasterTimeScale(spec.xScale)) { - const sampleLabel = spec.xAxisLabelFormatter(xNumericExtent[0]); - const timeScale = new ScaleContinuous( - { - type: ScaleType.Time, - domain: xNumericExtent, - range: [0, grid.width], - nice: false, - }, - { - desiredTickCount: estimatedNonOverlappingTickCount(grid.width, style.xAxisLabel, sampleLabel), - timeZone: spec.timeZone, - }, - ); - return timeScale.ticks().map(getTextValue(spec.xAxisLabelFormatter, (x) => timeScale.scale(x))); - } - - return xValues.map((textBox: string | number) => { - return { - ...getTextValue(spec.xAxisLabelFormatter, xScale)(textBox), - x: (xScale(textBox) || 0) + xScale.bandwidth() / 2, + ...style, + y: style.fontSize / 2 + pad(style.padding, 'top'), + x: (scale(value) ?? 0) + (isTimeScale ? 0 : scale.bandwidth() / 2), + align: isRotated ? 'right' : isTimeScale ? 'left' : 'center', }; }); } diff --git a/packages/charts/src/chart_types/heatmap/renderer/canvas/canvas_renderers.ts b/packages/charts/src/chart_types/heatmap/renderer/canvas/canvas_renderers.ts index 9455745e413..25b17ff9bed 100644 --- a/packages/charts/src/chart_types/heatmap/renderer/canvas/canvas_renderers.ts +++ b/packages/charts/src/chart_types/heatmap/renderer/canvas/canvas_renderers.ts @@ -6,12 +6,13 @@ * Side Public License, v 1. */ -import { Color, Colors } from '../../../../common/colors'; -import { Font } from '../../../../common/text_utils'; +import { Color } from '../../../../common/colors'; import { clearCanvas, renderLayers, withContext } from '../../../../renderers/canvas'; +import { radToDeg } from '../../../../utils/common'; +import { horizontalPad } from '../../../../utils/dimensions'; import { renderMultiLine } from '../../../xy_chart/renderer/canvas/primitives/line'; import { renderRect } from '../../../xy_chart/renderer/canvas/primitives/rect'; -import { renderText, wrapLines } from '../../../xy_chart/renderer/canvas/primitives/text'; +import { renderText, TextFont, wrapLines } from '../../../xy_chart/renderer/canvas/primitives/text'; import { ShapeViewModel } from '../../layout/types/viewmodel_types'; import { ChartElementSizes } from '../../state/selectors/compute_chart_dimensions'; @@ -37,6 +38,7 @@ export function renderCanvas2d( // text rendering must be y-flipped, which is a bit easier this way ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; + ctx.lineCap = 'square'; // ctx.translate(chartCenter.x, chartCenter.y); // this applies the mathematical x/y conversion (+y is North) which is easier when developing geometry // functions - also, all renderers have flexibility (eg. SVG scale) and WebGL NDC is also +y up @@ -117,34 +119,23 @@ export function renderCanvas2d( // render text on Y axis theme.yAxisLabel.visible && withContext(ctx, () => { + // the text is right aligned so the canvas needs to be aligned to the right of the Y axis box ctx.translate(elementSizes.yAxis.left + elementSizes.yAxis.width, elementSizes.yAxis.top); - filteredYValues.forEach((yValue) => { - const font: Font = { - fontFamily: theme.yAxisLabel.fontFamily, - fontStyle: theme.yAxisLabel.fontStyle ? theme.yAxisLabel.fontStyle : 'normal', - fontVariant: 'normal', - fontWeight: 'normal', - textColor: Colors.Black.keyword, - }; - const { padding } = theme.yAxisLabel; - const horizontalPadding = - typeof padding === 'number' ? padding * 2 : (padding.left ?? 0) + (padding.right ?? 0); - const [resultText] = wrapLines( + const font: TextFont = { ...theme.yAxisLabel, baseline: 'middle' /* fixed */, align: 'right' /* fixed */ }; + const { padding } = theme.yAxisLabel; + const horizontalPadding = horizontalPad(padding); + filteredYValues.forEach(({ x, y, text }) => { + const textLines = wrapLines( ctx, - yValue.text, + text, font, theme.yAxisLabel.fontSize, heatmapViewModel.gridOrigin.x - horizontalPadding, - 16, + theme.yAxisLabel.fontSize, { shouldAddEllipsis: true, wrapAtWord: false }, ).lines; - renderText( - ctx, - { x: yValue.x, y: yValue.y }, - resultText, - // the alignment for y axis labels is fixed to the right - { ...theme.yAxisLabel, align: 'right' }, - ); + // TODO improve the `wrapLines` code to handle results with short width + renderText(ctx, { x, y }, textLines.length > 0 ? textLines[0] : '…', font); }); }), @@ -153,9 +144,28 @@ export function renderCanvas2d( theme.xAxisLabel.visible && withContext(ctx, () => { ctx.translate(elementSizes.xAxis.left, elementSizes.xAxis.top); - heatmapViewModel.xValues.forEach((xValue) => - renderText(ctx, { x: xValue.x, y: xValue.y }, xValue.text, theme.xAxisLabel), - ); + heatmapViewModel.xValues + .filter((_, i) => i % elementSizes.xAxisTickCadence === 0) + .forEach(({ x, y, text, align }) => { + const textLines = wrapLines( + ctx, + text, + theme.xAxisLabel, + theme.xAxisLabel.fontSize, + // TODO wrap into multilines + Infinity, + 16, + { shouldAddEllipsis: true, wrapAtWord: false }, + ).lines; + renderText( + ctx, + { x, y }, + textLines.length > 0 ? textLines[0] : '…', + { ...theme.xAxisLabel, baseline: 'middle', align }, + // negative rotation due to the canvas rotation direction + radToDeg(-elementSizes.xLabelRotation), + ); + }); }), () => diff --git a/packages/charts/src/chart_types/heatmap/renderer/canvas/connected_component.tsx b/packages/charts/src/chart_types/heatmap/renderer/canvas/connected_component.tsx index b189b6089b1..51937e1e5d5 100644 --- a/packages/charts/src/chart_types/heatmap/renderer/canvas/connected_component.tsx +++ b/packages/charts/src/chart_types/heatmap/renderer/canvas/connected_component.tsx @@ -164,6 +164,8 @@ const DEFAULT_PROPS: ReactiveChartStateProps = { fullHeatmapHeight: 0, rowHeight: 0, visibleNumberOfRows: 0, + xAxisTickCadence: 1, + xLabelRotation: 0, }, debug: false, }; diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/compute_chart_dimensions.ts b/packages/charts/src/chart_types/heatmap/state/selectors/compute_chart_dimensions.ts index f1d1757f387..10560baa4de 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/compute_chart_dimensions.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/compute_chart_dimensions.ts @@ -6,19 +6,26 @@ * Side Public License, v 1. */ +import { scaleBand } from 'd3-scale'; + +import { Radian } from '../../../../common/geometry'; +import { extent } from '../../../../common/math'; +import { rotate2, sub2, Vec2 } from '../../../../common/vectors'; +import { screenspaceMarkerScaleCompressor } from '../../../../solvers/screenspace_marker_scale_compressor'; import { GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { getLegendSizeSelector } from '../../../../state/selectors/get_legend_size'; import { TextMeasure, withTextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator'; -import { Dimensions, horizontalPad, innerPad, outerPad, verticalPad } from '../../../../utils/dimensions'; +import { degToRad, isFiniteNumber } from '../../../../utils/common'; +import { Dimensions, horizontalPad, innerPad, outerPad, pad, Size } from '../../../../utils/dimensions'; import { isHorizontalLegend } from '../../../../utils/legend'; import { AxisStyle, HeatmapStyle } from '../../../../utils/themes/theme'; -import { HeatmapCellDatum } from '../../layout/viewmodel/viewmodel'; -import { HeatmapSpec } from './../../specs/heatmap'; +import { PrimitiveValue } from '../../../partition_chart/layout/utils/group_by_rollup'; +import { HeatmapCellDatum, isRasterTimeScale } from '../../layout/viewmodel/viewmodel'; +import { HeatmapSpec } from '../../specs/heatmap'; import { getHeatmapSpecSelector } from './get_heatmap_spec'; import { getHeatmapTableSelector } from './get_heatmap_table'; -import { getXAxisRightOverflow } from './get_x_axis_right_overflow'; /** @internal */ export interface HeatmapTable { @@ -39,6 +46,8 @@ export type ChartElementSizes = { fullHeatmapHeight: number; rowHeight: number; visibleNumberOfRows: number; + xAxisTickCadence: number; + xLabelRotation: number; }; /** @@ -46,21 +55,13 @@ export type ChartElementSizes = { * @internal */ export const computeChartElementSizesSelector = createCustomCachedSelector( - [ - getParentDimension, - getLegendSizeSelector, - getHeatmapTableSelector, - getChartThemeSelector, - getXAxisRightOverflow, - getHeatmapSpecSelector, - ], + [getParentDimension, getLegendSizeSelector, getHeatmapTableSelector, getChartThemeSelector, getHeatmapSpecSelector], ( container, legendSize, - { yValues }, + { yValues, xValues }, { heatmap, axes: { axisTitle: axisTitleStyle } }, - rightOverflow, - { xAxisTitle, yAxisTitle, yAxisLabelFormatter }, + { xAxisTitle, yAxisTitle, xAxisLabelFormatter, yAxisLabelFormatter, xScale }, ): ChartElementSizes => { return withTextMeasure((textMeasure) => { const isLegendHorizontal = isHorizontalLegend(legendSize.position); @@ -73,11 +74,21 @@ export const computeChartElementSizesSelector = createCustomCachedSelector( const yAxisWidth = getYAxisHorizontalUsedSpace(yValues, heatmap.yAxisLabel, yAxisLabelFormatter, textMeasure); const xAxisTitleVerticalSize = getTextSizeDimension(xAxisTitle, axisTitleStyle, textMeasure, 'height'); - const xAxisHeight = heatmap.xAxisLabel.visible - ? heatmap.xAxisLabel.fontSize + verticalPad(heatmap.xAxisLabel.padding) - : 0; + const xAxisSize = getXAxisSize( + !isRasterTimeScale(xScale), + heatmap.xAxisLabel, + xAxisLabelFormatter, + xValues, + textMeasure, + container.width - legendWidth - heatmap.grid.stroke.width / 2, // we should consider also the grid width + [ + yAxisTitleHorizontalSize + yAxisWidth, + 0, // this can be used if we have a right Y axis + ], + ); - const availableHeightForGrid = container.height - xAxisTitleVerticalSize - xAxisHeight - legendHeight; + const availableHeightForGrid = + container.height - xAxisTitleVerticalSize - xAxisSize.height - legendHeight - heatmap.grid.stroke.width / 2; const rowHeight = getGridCellHeight(yValues.length, heatmap.grid, availableHeightForGrid); const fullHeatmapHeight = rowHeight * yValues.length; @@ -88,10 +99,10 @@ export const computeChartElementSizesSelector = createCustomCachedSelector( : yValues.length; const grid: Dimensions = { - width: container.width - yAxisWidth - yAxisTitleHorizontalSize - rightOverflow - legendWidth, - height: visibleNumberOfRows * rowHeight, - left: container.left + yAxisTitleHorizontalSize + yAxisWidth, - top: container.top, + width: xAxisSize.width, + height: visibleNumberOfRows * rowHeight - heatmap.grid.stroke.width / 2, + left: container.left + xAxisSize.left, + top: container.top + heatmap.grid.stroke.width / 2, }; const yAxis: Dimensions = { @@ -103,12 +114,21 @@ export const computeChartElementSizesSelector = createCustomCachedSelector( const xAxis: Dimensions = { width: grid.width, - height: xAxisHeight, + height: xAxisSize.height, top: grid.top + grid.height, left: grid.left, }; - return { grid, yAxis, xAxis, visibleNumberOfRows, fullHeatmapHeight, rowHeight }; + return { + grid, + yAxis, + xAxis, + visibleNumberOfRows, + fullHeatmapHeight, + rowHeight, + xAxisTickCadence: xAxisSize.tickCadence, + xLabelRotation: xAxisSize.minRotation, + }; }); }, ); @@ -118,24 +138,20 @@ function getYAxisHorizontalUsedSpace( style: HeatmapStyle['yAxisLabel'], formatter: HeatmapSpec['yAxisLabelFormatter'], textMeasure: TextMeasure, -) { +): number { if (!style.visible) { return 0; } + if (typeof style.width === 'number' && isFiniteNumber(style.width)) { + return style.width; + } // account for the space required to show the longest Y axis label const longestLabelWidth = yValues.reduce((acc, value) => { const { width } = textMeasure(formatter(value), style, style.fontSize); - return Math.max(width, acc); + return Math.max(width + horizontalPad(style.padding), acc); }, 0); - const labelsWidth = - style.width === 'auto' - ? longestLabelWidth - : typeof style.width === 'number' - ? style.width - : Math.max(longestLabelWidth, style.width.max); - - return labelsWidth + horizontalPad(style.padding); + return style.width === 'auto' ? longestLabelWidth : Math.max(longestLabelWidth, style.width.max); } function getTextSizeDimension( @@ -180,3 +196,230 @@ function getGridCellHeight(rows: number, grid: HeatmapStyle['grid'], height: num return stretchedHeight; } + +function getXAxisSize( + isCategoricalScale: boolean, + style: HeatmapStyle['xAxisLabel'], + formatter: HeatmapSpec['xAxisLabelFormatter'], + labels: (string | number)[], + textMeasure: TextMeasure, + containerWidth: number, + surroundingSpace: [number, number], +): Size & { right: number; left: number; tickCadence: number; minRotation: Radian } { + if (!style.visible) { + return { + height: 0, + width: Math.max(containerWidth - surroundingSpace[0] - surroundingSpace[1], 0), + left: surroundingSpace[0], + right: surroundingSpace[1], + tickCadence: NaN, + minRotation: 0, + }; + } + const isRotated = style.rotation !== 0; + const normalizedScale = scaleBand>().domain(labels).range([0, 1]); + + const alignment = isRotated ? 'right' : isCategoricalScale ? 'center' : 'left'; + const alignmentOffset = isCategoricalScale ? normalizedScale.bandwidth() / 2 : 0; + const scale = (d: NonNullable) => (normalizedScale(d) ?? 0) + alignmentOffset; + + // use positive angle from 0 to 90 only + const rotationRad = degToRad(style.rotation); + + const measuredLabels = labels.map((label) => ({ + ...textMeasure(formatter(label), style, style.fontSize), + label, + })); + + // don't filter ticks if categorical scale or with rotated labels + if (isCategoricalScale || isRotated) { + const maxLabelBBox = measuredLabels.reduce( + (acc, curr) => { + return { + height: Math.max(acc.height, curr.height), + width: Math.max(acc.width, curr.width), + }; + }, + { height: 0, width: 0 }, + ); + const compressedScale = computeCompressedScale( + style, + scale, + measuredLabels, + containerWidth, + surroundingSpace, + alignment, + rotationRad, + ); + const scaleStep = compressedScale.width / labels.length; + // this optimal rotation is computed on a suboptimal compressed scale, it can be further enhanced with a monotonic hill climber + const optimalRotation = + scaleStep > maxLabelBBox.width ? 0 : Math.asin(Math.min(maxLabelBBox.height / scaleStep, 1)); + // if the current requested rotation is not at least bigger then the optimal one, recalculate the compression + // using the optimal one forcing the rotation to be without overlaps + const { width, height, left, right, minRotation } = { + ...(rotationRad !== 0 && optimalRotation > rotationRad + ? computeCompressedScale( + style, + scale, + measuredLabels, + containerWidth, + surroundingSpace, + alignment, + optimalRotation, + ) + : compressedScale), + minRotation: isRotated ? Math.max(optimalRotation, rotationRad) : 0, + }; + + const validCompression = isFiniteNumber(width); + return { + height: validCompression ? height : 0, + width: validCompression ? width : Math.max(containerWidth - surroundingSpace[0] - surroundingSpace[1], 0), + left: validCompression ? left : surroundingSpace[0], + right: validCompression ? right : surroundingSpace[1], + tickCadence: validCompression ? 1 : NaN, + minRotation, + }; + } + + // TODO refactor and move to monotonic hill climber and no mutations + // reduce the tick cadence on time scale to avoid overlaps and overflows + let tickCadence = 1; + let dimension = computeCompressedScale( + style, + scale, + measuredLabels, + containerWidth, + surroundingSpace, + alignment, + rotationRad, + ); + + for (let i = 1; i < measuredLabels.length; i++) { + if ((!dimension.overlaps && !dimension.overflow.right) || !isFiniteNumber(dimension.width)) { + break; + } + dimension = computeCompressedScale( + style, + scale, + measuredLabels.filter((_, index) => index % (i + 1) === 0), + containerWidth, + surroundingSpace, + alignment, + rotationRad, + ); + tickCadence++; + } + + // hide the axis because there is no space for labels + if (!isFiniteNumber(dimension.width)) { + return { + // hide the whole axis + height: 0, + width: Math.max(containerWidth - surroundingSpace[0] - surroundingSpace[1], 0), + left: surroundingSpace[0], + right: surroundingSpace[1], + // hide all ticks + tickCadence: NaN, + minRotation: rotationRad, + }; + } + + return { + ...dimension, + tickCadence, + minRotation: rotationRad, + }; +} + +function computeCompressedScale( + style: HeatmapStyle['xAxisLabel'], + scale: (d: NonNullable) => number, + labels: Array }>, + containerWidth: number, + surroundingSpace: [number, number], + alignment: 'left' | 'right' | 'center', + rotation: Radian, +): Size & { left: number; right: number; overlaps: boolean; overflow: { left: boolean; right: boolean } } { + const { itemsPerSideSize, domainPositions, hMax } = labels.reduce<{ + wMax: number; + hMax: number; + itemsPerSideSize: [number, number][]; + domainPositions: number[]; + }>( + (acc, { width, height, label }) => { + // rotate the label box coordinates + const labelRect: Vec2[] = [ + [0, 0], + [width, 0], + [width, height], + [0, height], + ]; + + const rotationOrigin: Vec2 = + alignment === 'right' ? [width, height / 2] : alignment === 'left' ? [0, height / 2] : [width / 2, height / 2]; + + const rotatedVectors = labelRect.map((vector) => rotate2(rotation, sub2(vector, rotationOrigin))); + + // find the rotated bounding box + const x = extent(rotatedVectors.map((v) => v[0])); + const y = extent(rotatedVectors.map((v) => v[1])); + acc.wMax = Math.max(acc.wMax, Math.abs(x[1] - x[0])); + acc.hMax = Math.max(acc.hMax, Math.abs(y[1] - y[0])); + + // describe the item width as the left and right vector size from the rotation origin + acc.itemsPerSideSize.push([Math.abs(x[0]), Math.abs(x[1])]); + + // use a categorical scale with labels aligned to the center to compute the domain position + const domainPosition = scale(label); + acc.domainPositions.push(domainPosition); + return acc; + }, + { wMax: -Infinity, hMax: -Infinity, itemsPerSideSize: [], domainPositions: [] }, + ); + + // account for the left and right space (Y axes, overflows etc) + const globalDomainPositions = [0, ...domainPositions, 1]; + const globalItemWidth: [number, number][] = [[surroundingSpace[0], 0], ...itemsPerSideSize, [0, surroundingSpace[1]]]; + + const { scaleMultiplier, bounds } = screenspaceMarkerScaleCompressor( + globalDomainPositions, + globalItemWidth, + containerWidth, + ); + + // check label overlaps using the computed compressed scale + const overlaps = itemsPerSideSize.some(([, rightSide], i) => { + if (i >= itemsPerSideSize.length - 2) { + return false; + } + const currentItemRightSide = domainPositions[i] * scaleMultiplier + rightSide + pad(style.padding, 'right'); + const nextItemLeftSize = + domainPositions[i + 1] * scaleMultiplier - itemsPerSideSize[i + 1][0] - pad(style.padding, 'left'); + return currentItemRightSide > nextItemLeftSize; + }); + + const leftMargin = isFiniteNumber(bounds[0]) + ? globalItemWidth[bounds[0]][0] - scaleMultiplier * globalDomainPositions[bounds[0]] + : 0; + const rightMargin = isFiniteNumber(bounds[1]) ? globalItemWidth[bounds[1]][1] : 0; + + return { + // the horizontal space + width: scaleMultiplier, + right: rightMargin, + left: leftMargin, + // the height represent the height of the max rotated bbox plus the padding and the vertical position of the rotation origin + height: hMax + pad(style.padding, 'top') + style.fontSize / 2, + overlaps, + overflow: { + // true if a label exist protrude to the left making the scale shrink from the left + // the current check is based on the way we construct globalItemWidth and globalDomainPositions + left: bounds[0] !== 0, + // true if a label exist protrude to the right making the scale shrink from the right + // the current check is based on the way we construct globalItemWidth and globalDomainPositions + right: bounds[1] !== globalDomainPositions.length - 1, + }, + }; +} diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts deleted file mode 100644 index 3ffbf89cfce..00000000000 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ScaleContinuous } from '../../../../scales'; -import { ScaleType } from '../../../../scales/constants'; -import { createCustomCachedSelector } from '../../../../state/create_selector'; -import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; -import { withTextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator'; -import { horizontalPad } from '../../../../utils/dimensions'; -import { getHeatmapSpecSelector } from './get_heatmap_spec'; -import { getHeatmapTableSelector } from './get_heatmap_table'; - -/** - * @internal - * Gets color scale based on specification and values range. - */ -export const getXAxisRightOverflow = createCustomCachedSelector( - [getHeatmapSpecSelector, getChartThemeSelector, getHeatmapTableSelector], - ({ xScale, timeZone, xAxisLabelFormatter }, { heatmap: { xAxisLabel: style } }, { xNumericExtent }) => { - return xScale.type !== ScaleType.Time - ? 0 - : typeof style.width === 'number' - ? style.width / 2 - : withTextMeasure((measure) => { - return new ScaleContinuous( - { type: ScaleType.Time, domain: xNumericExtent, range: [0, 1] }, - { timeZone: xScale.type === ScaleType.Time ? timeZone : undefined }, - ) - .ticks() - .reduce( - (max, n) => - Math.max( - max, - measure(xAxisLabelFormatter(n), { ...style }, style.fontSize).width + horizontalPad(style.padding), - ), - 0, - ); - }) / 2; - }, -); diff --git a/packages/charts/src/common/math.ts b/packages/charts/src/common/math.ts index ffd061eef2f..789d3ec44ac 100644 --- a/packages/charts/src/common/math.ts +++ b/packages/charts/src/common/math.ts @@ -10,3 +10,19 @@ export function logarithm(base: number, y: number) { return Math.log(y) / Math.log(base); } + +/** + * Computes the min and max values of an array of numbers + * @internal + */ +export function extent(array: number[]): [min: number, max: number] { + const len = array.length; + let min = Infinity; + let max = -Infinity; + for (let i = 0; i < len; i += 1) { + const value = array[i]; + if (min > value) min = value; + if (max < value) max = value; + } + return [min, max]; +} diff --git a/packages/charts/src/common/vectors.ts b/packages/charts/src/common/vectors.ts new file mode 100644 index 00000000000..5adb1d58226 --- /dev/null +++ b/packages/charts/src/common/vectors.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Radian } from './geometry'; + +/** @internal */ +export type Vec2 = [number, number]; + +/** + * Rotate a Vec2 vector by radians + * @internal + */ +export function rotate2(radian: Radian, vector: Vec2): Vec2 { + return [ + Math.cos(radian) * vector[0] - Math.sin(radian) * vector[1], + Math.sin(radian) * vector[0] + Math.cos(radian) * vector[1], + ]; +} + +/** + * Subtract vector b from a + * @internal + */ +export function sub2(a: Vec2, b: Vec2): Vec2 { + return [a[0] - b[0], a[1] - b[1]]; +} diff --git a/packages/charts/src/scales/scale_continuous.ts b/packages/charts/src/scales/scale_continuous.ts index 7a3ee29043a..53bfadebff1 100644 --- a/packages/charts/src/scales/scale_continuous.ts +++ b/packages/charts/src/scales/scale_continuous.ts @@ -289,7 +289,10 @@ function getPixelPaddedDomain( const orderedDomain: [number, number] = inverted ? [domain[1], domain[0]] : domain; const { scaleMultiplier } = screenspaceMarkerScaleCompressor( orderedDomain, - [2 * desiredPixelPadding, 2 * desiredPixelPadding], + [ + [desiredPixelPadding, desiredPixelPadding], + [desiredPixelPadding, desiredPixelPadding], + ], chartHeight, ); const baselinePaddedDomainLo = orderedDomain[0] - desiredPixelPadding / scaleMultiplier; @@ -301,14 +304,26 @@ function getPixelPaddedDomain( : crossAbove ? orderedDomain[0] - desiredPixelPadding / - screenspaceMarkerScaleCompressor([orderedDomain[0], intercept], [2 * desiredPixelPadding, 0], chartHeight) - .scaleMultiplier + screenspaceMarkerScaleCompressor( + [orderedDomain[0], intercept], + [ + [desiredPixelPadding, desiredPixelPadding], + [0, 0], + ], + chartHeight, + ).scaleMultiplier : baselinePaddedDomainLo; const paddedDomainHigh = crossBelow ? orderedDomain[1] + desiredPixelPadding / - screenspaceMarkerScaleCompressor([intercept, orderedDomain[1]], [0, 2 * desiredPixelPadding], chartHeight) - .scaleMultiplier + screenspaceMarkerScaleCompressor( + [intercept, orderedDomain[1]], + [ + [0, 0], + [desiredPixelPadding, desiredPixelPadding], + ], + chartHeight, + ).scaleMultiplier : crossAbove ? intercept : baselinePaddedDomainHigh; diff --git a/packages/charts/src/solvers/screenspace_marker_scale_compressor.ts b/packages/charts/src/solvers/screenspace_marker_scale_compressor.ts index 98a70073844..4f1441f61cf 100644 --- a/packages/charts/src/solvers/screenspace_marker_scale_compressor.ts +++ b/packages/charts/src/solvers/screenspace_marker_scale_compressor.ts @@ -29,7 +29,7 @@ export interface ScaleCompression { */ export const screenspaceMarkerScaleCompressor = ( domainPositions: Cartesian[], - itemWidths: Pixels[], + itemWidths: Array<[Pixels, Pixels]>, outerWidth: Pixels, ): ScaleCompression => { const result: ScaleCompression = { bounds: [], scaleMultiplier: Infinity }; @@ -38,7 +38,7 @@ export const screenspaceMarkerScaleCompressor = ( for (let right = 0; right < itemCount; right++) { if (domainPositions[left] > domainPositions[right]) continue; // must adhere to left <= right - const range = outerWidth - itemWidths[left] / 2 - itemWidths[right] / 2; // negative if not enough room + const range = outerWidth - itemWidths[left][0] - itemWidths[right][1]; // negative if not enough room const domain = domainPositions[right] - domainPositions[left]; // always non-negative and finite const scaleMultiplier = range / domain; // may not be finite, and that's OK diff --git a/packages/charts/src/state/selectors/get_chart_theme.ts b/packages/charts/src/state/selectors/get_chart_theme.ts index 4b94da04c9b..90d7df48de9 100644 --- a/packages/charts/src/state/selectors/get_chart_theme.ts +++ b/packages/charts/src/state/selectors/get_chart_theme.ts @@ -7,7 +7,7 @@ */ import { colorToRgba, overrideOpacity, RGBATupleToString } from '../../common/color_library_wrappers'; -import { mergePartial } from '../../utils/common'; +import { clamp, mergePartial } from '../../utils/common'; import { Logger } from '../../utils/logger'; import { LIGHT_THEME } from '../../utils/themes/light_theme'; import { PartialTheme, Theme } from '../../utils/themes/theme'; @@ -44,5 +44,8 @@ function validateTheme(theme: Theme): Theme { theme.background.fallbackColor = RGBATupleToString(newFallback); } + // heatmap rotation constraint: + theme.heatmap.xAxisLabel.rotation = clamp(theme.heatmap.xAxisLabel.rotation, 0, 90); + return theme; } diff --git a/packages/charts/src/utils/common.ts b/packages/charts/src/utils/common.ts index 3f64e3073e2..816d461c85a 100644 --- a/packages/charts/src/utils/common.ts +++ b/packages/charts/src/utils/common.ts @@ -159,6 +159,9 @@ export function getColorFromVariant(seriesColor: Color, color?: Color | ColorVar /** @internal */ export const degToRad = (angle: Degrees): Radian => (angle / 180) * Math.PI; +/** @internal */ +export const radToDeg = (radian: Radian): Degrees => (radian * 180) / Math.PI; + /** * This function returns a function to generate ids. * This can be used to generate unique, but predictable ids to pair labels diff --git a/packages/charts/src/utils/data_samples/test_dataset_heatmap.ts b/packages/charts/src/utils/data_samples/test_dataset_heatmap.ts index c68d6fd46ae..efb7f3b799d 100644 --- a/packages/charts/src/utils/data_samples/test_dataset_heatmap.ts +++ b/packages/charts/src/utils/data_samples/test_dataset_heatmap.ts @@ -25,6 +25,7 @@ export const DATA_1: HeatmapDataSets = { xFormatter: (value: string | number) => { return DateTime.fromMillis(value as number).toFormat('dd/MM HHa', { timeZone: 'Europe/Rome' }); }, + timeZone: 'Europe/Rome', data: [ { y: 'AE', @@ -4240,3 +4241,117 @@ export const DATA_9: HeatmapDataSets = { timeZone: 'Europe/Rome', data: DATA_8.data, }; + +/** @internal */ +export const ECOMMERCE_DATA = [ + { + x: 'Tigress Haute Couture Haute Couture', + y: 'Dress', + value: 816, + }, + { + x: 'Tigress Haute Couture Haute Couture', + y: 'Shoes', + value: 556, + }, + { + x: 'Tigress Haute Couture Haute Couture', + y: 'Shirts', + value: 395, + }, + { + x: 'Low Tide Media', + y: 'T-Shirts', + value: 1125, + }, + { + x: 'Low Tide Media', + y: 'Sweaters', + value: 595, + }, + { + x: 'Low Tide Media', + y: 'Pullovers', + value: 355, + }, + { + x: 'Low Tide Media', + y: 'Shoes', + value: 233, + }, + { + x: 'Low Tide Media', + y: 'Dress', + value: 136, + }, + { + x: 'Elitelligence', + y: 'T-Shirts', + value: 1242, + }, + { + x: 'Elitelligence', + y: 'Sweaters', + value: 528, + }, + { + x: 'Elitelligence', + y: 'Jumper', + value: 2338, + }, + { + x: 'Elitelligence', + y: 'Shirts', + value: 46, + }, + { + x: 'Elitelligence', + y: 'Dress', + value: 1, + }, + { + x: 'Oceanavigations', + y: 'T-Shirts', + value: 600, + }, + { + x: 'Oceanavigations', + y: 'Dress', + value: 418, + }, + { + x: 'Oceanavigations', + y: 'Cardigans', + value: 286, + }, + { + x: 'Oceanavigations', + y: 'Jumpsuit', + value: 123, + }, + { + x: 'Oceanavigations', + y: 'Sweaters', + value: 258, + }, + { + x: 'Oceanavigations', + y: 'Shirts', + value: 239, + }, + { + x: 'Pyramidustries Young Moda', + y: 'Dress', + value: 747, + }, + { + x: 'Pyramidustries Young Moda', + y: 'Shoes', + value: 425, + }, + { + x: 'Pyramidustries Young Moda', + y: 'Shirts', + value: 377, + }, +]; diff --git a/packages/charts/src/utils/themes/dark_theme.ts b/packages/charts/src/utils/themes/dark_theme.ts index c4cf60284ba..29e56a4cd5f 100644 --- a/packages/charts/src/utils/themes/dark_theme.ts +++ b/packages/charts/src/utils/themes/dark_theme.ts @@ -328,16 +328,14 @@ export const DARK_THEME: Theme = { xAxisLabel: { visible: true, - width: 'auto', fontSize: 12, fontFamily: 'Sans-Serif', fontStyle: 'normal', textColor: Colors.White.keyword, fontVariant: 'normal', fontWeight: 'normal', - align: 'center', - baseline: 'middle', padding: { top: 5, bottom: 5, left: 5, right: 5 }, + rotation: 0, }, yAxisLabel: { visible: true, @@ -348,7 +346,6 @@ export const DARK_THEME: Theme = { textColor: Colors.White.keyword, fontVariant: 'normal', fontWeight: 'normal', - baseline: 'middle', padding: { top: 5, bottom: 5, left: 5, right: 5 }, }, grid: { diff --git a/packages/charts/src/utils/themes/light_theme.ts b/packages/charts/src/utils/themes/light_theme.ts index 471fc026c37..13a66f1d6a2 100644 --- a/packages/charts/src/utils/themes/light_theme.ts +++ b/packages/charts/src/utils/themes/light_theme.ts @@ -327,16 +327,14 @@ export const LIGHT_THEME: Theme = { }, xAxisLabel: { visible: true, - width: 'auto', fontSize: 12, fontFamily: 'Sans-Serif', fontStyle: 'normal', textColor: Colors.Black.keyword, fontVariant: 'normal', fontWeight: 'normal', - align: 'center', - baseline: 'middle', padding: { top: 5, bottom: 5, left: 5, right: 5 }, + rotation: 0, }, yAxisLabel: { visible: true, @@ -347,7 +345,6 @@ export const LIGHT_THEME: Theme = { textColor: Colors.Black.keyword, fontVariant: 'normal', fontWeight: 'normal', - baseline: 'middle', padding: { top: 5, bottom: 5, left: 5, right: 5 }, }, grid: { diff --git a/packages/charts/src/utils/themes/theme.ts b/packages/charts/src/utils/themes/theme.ts index e66c074a20f..475babdaa38 100644 --- a/packages/charts/src/utils/themes/theme.ts +++ b/packages/charts/src/utils/themes/theme.ts @@ -10,7 +10,7 @@ import { $Values } from 'utility-types'; import { Color } from '../../common/colors'; import { Pixels, Ratio } from '../../common/geometry'; -import { Font, FontStyle, TextAlign, TextBaseline } from '../../common/text_utils'; +import { Font, FontStyle } from '../../common/text_utils'; import { ColorVariant, HorizontalAlignment, RecursivePartial, VerticalAlignment } from '../common'; import { Margins, Padding, SimplePadding } from '../dimensions'; import { Point } from '../point'; @@ -213,16 +213,17 @@ export interface HeatmapStyle { }; xAxisLabel: Font & { fontSize: Pixels; - width: Pixels | 'auto'; - align: TextAlign; - baseline: TextBaseline; visible: boolean; padding: Pixels | Padding; + /** + * Positive 0 - 90 degree angle + * @defaultValue 0 + */ + rotation: number; }; yAxisLabel: Font & { fontSize: Pixels; width: Pixels | 'auto' | { max: Pixels }; - baseline: TextBaseline; visible: boolean; padding: Pixels | Padding; }; diff --git a/storybook/stories/heatmap/2_categorical.story.tsx b/storybook/stories/heatmap/2_categorical.story.tsx index 9d43c5ebe51..bd57a304f8d 100644 --- a/storybook/stories/heatmap/2_categorical.story.tsx +++ b/storybook/stories/heatmap/2_categorical.story.tsx @@ -28,8 +28,8 @@ export const Example = () => { const minCellHeight = number('min cell height', 10, { step: 1, min: 3, max: 8, range: true }, 'grid'); const maxCellHeight = number('max cell height', 30, { step: 1, min: 8, max: 45, range: true }, 'grid'); - const showXAxisTitle = boolean('Show x axis title', false); - const showYAxisTitle = boolean('Show y axis title', false); + const showXAxisTitle = boolean('Show x axis title', true); + const showYAxisTitle = boolean('Show y axis title', true); return ( @@ -88,8 +88,8 @@ export const Example = () => { valueAccessor={(d) => d[3]} valueFormatter={(value) => value.toFixed(0.2)} xSortPredicate="alphaAsc" - xAxisTitle={showXAxisTitle ? 'Bottom axis' : undefined} - yAxisTitle={showYAxisTitle ? 'Left axis' : undefined} + xAxisTitle={showXAxisTitle ? 'Popular baby names' : undefined} + yAxisTitle={showYAxisTitle ? 'Years' : undefined} /> ); diff --git a/storybook/stories/heatmap/6_label_rotation.story.tsx b/storybook/stories/heatmap/6_label_rotation.story.tsx new file mode 100644 index 00000000000..939f427fe6f --- /dev/null +++ b/storybook/stories/heatmap/6_label_rotation.story.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { action } from '@storybook/addon-actions'; +import { boolean, number } from '@storybook/addon-knobs'; +import React from 'react'; + +import { Chart, Heatmap, HeatmapStyle, RecursivePartial, Settings } from '@elastic/charts'; + +import { ScaleType } from '../../../packages/charts/src/scales/constants'; +import { DATA_1, ECOMMERCE_DATA } from '../../../packages/charts/src/utils/data_samples/test_dataset_heatmap'; +import { useBaseTheme } from '../../use_base_theme'; + +export const Example = () => { + const yAxisAutoWidth = boolean('Y-axis auto width', true); + const yAxisWidth = number('Y-axis width', 50, { range: true, min: 0, max: 100 }); + const heatmap: RecursivePartial = { + xAxisLabel: { + visible: boolean('X-Axis visible', true), + fontSize: number('X-Axis label fontSize', 12, { range: true, min: 5, max: 20 }), + padding: number('X-Axis label padding', 6, { range: true, min: 0, max: 15 }), + rotation: number('X-Axis label rotation', 0, { step: 1, min: 0, max: 90, range: true }), + }, + yAxisLabel: { + width: yAxisAutoWidth ? 'auto' : yAxisWidth, + }, + }; + const useCategoricalDataset = boolean('Use categorical data', false); + const dataset = useCategoricalDataset ? ECOMMERCE_DATA : DATA_1.data; + return ( + + + + id="heatmap2" + colorScale={{ + type: 'bands', + bands: [ + { start: -Infinity, end: 100, color: '#AADC32' }, + { start: 100, end: 200, color: '#35B779' }, + { start: 200, end: 300, color: '#24868E' }, + { start: 300, end: 400, color: '#3B528B' }, + { start: 400, end: Infinity, color: '#471164' }, + ], + }} + xScale={ + useCategoricalDataset + ? { + type: ScaleType.Ordinal, + } + : { + type: ScaleType.Time, + interval: DATA_1.interval, + } + } + data={dataset} + timeZone={DATA_1.timeZone} + xAxisLabelFormatter={useCategoricalDataset ? (d) => `${d}` : DATA_1.xFormatter} + xAccessor={(d) => d.x} + yAccessor={(d) => d.y} + valueAccessor={(d) => d.value} + valueFormatter={(value) => (Number.isFinite(value) ? value.toFixed(0.2) : '')} + xSortPredicate="dataIndex" + /> + + ); +}; diff --git a/storybook/stories/heatmap/heatmap.stories.tsx b/storybook/stories/heatmap/heatmap.stories.tsx index 8f3f95a0f34..0b6d89c0c0e 100644 --- a/storybook/stories/heatmap/heatmap.stories.tsx +++ b/storybook/stories/heatmap/heatmap.stories.tsx @@ -15,3 +15,4 @@ export { Example as time } from './3_time.story'; export { Example as categorical } from './2_categorical.story'; export { Example as timeSnap } from './4_test_time_snap.story'; export { Example as theming } from './5_theming.story'; +export { Example as labelRotation } from './6_label_rotation.story'; diff --git a/storybook/stories/test_cases/9_heatmap_axis_labels.story.tsx b/storybook/stories/test_cases/9_heatmap_axis_labels.story.tsx deleted file mode 100644 index d1d86bc4538..00000000000 --- a/storybook/stories/test_cases/9_heatmap_axis_labels.story.tsx +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import numeral from 'numeral'; -import React from 'react'; - -import { Chart, Heatmap, niceTimeFormatter, ScaleType, Settings } from '@elastic/charts'; - -import { useBaseTheme } from '../../use_base_theme'; - -export const Example = () => { - return ( - - - { - return niceTimeFormatter([1572825600000, 1572912000000])(value, { - timeZone: 'UTC', - }); - }} - yAxisLabelFormatter={(value) => numeral(value).format()} - /> - - ); -}; - -export const data = [ - { x: 1642521600000, y: 4000000000, count: 1 }, - { x: 1642521600000, y: 5000000000, count: 1 }, - { x: 1642521600000, y: 11000000000, count: 1 }, - { x: 1642521600000, y: 15000000000, count: 1 }, - { x: 1642521600000, y: 17000000000, count: 1 }, - { x: 1642521600000, y: 20000000000, count: 1 }, - { x: 1642523400000, y: 8000000000, count: 1 }, - { x: 1642523400000, y: 10000000000, count: 1 }, - { x: 1642523400000, y: 12000000000, count: 1 }, - { x: 1642523400000, y: 20000000000, count: 1 }, - { x: 1642525200000, y: 13000000000, count: 1 }, - { x: 1642527000000, y: 15000000000, count: 1 }, - { x: 1642527000000, y: 16000000000, count: 2 }, - { x: 1642528800000, y: 16000000000, count: 1 }, - { x: 1642530600000, y: 5000000000, count: 1 }, - { x: 1642530600000, y: 21000000000, count: 1 }, - { x: 1642532400000, y: 7000000000, count: 1 }, - { x: 1642534200000, y: 11000000000, count: 1 }, - { x: 1642534200000, y: 16000000000, count: 1 }, - { x: 1642536000000, y: 19000000000, count: 2 }, - { x: 1642537800000, y: 20000000000, count: 1 }, - { x: 1642546800000, y: 32000000000, count: 1 }, - { x: 1642550400000, y: 16000000000, count: 1 }, - { x: 1642550400000, y: 21000000000, count: 1 }, - { x: 1642561200000, y: 8000000000, count: 1 }, - { x: 1642561200000, y: 11000000000, count: 1 }, - { x: 1642561200000, y: 20000000000, count: 1 }, - { x: 1642564800000, y: 5000000000, count: 1 }, - { x: 1642564800000, y: 6000000000, count: 1 }, - { x: 1642564800000, y: 15000000000, count: 1 }, - { x: 1642568400000, y: 15000000000, count: 1 }, - { x: 1642568400000, y: 19000000000, count: 1 }, - { x: 1642568400000, y: 32000000000, count: 1 }, - { x: 1642570200000, y: 3000000000, count: 1 }, - { x: 1642570200000, y: 10000000000, count: 1 }, - { x: 1642570200000, y: 15000000000, count: 1 }, - { x: 1642570200000, y: 16000000000, count: 1 }, - { x: 1642570200000, y: 20000000000, count: 1 }, - { x: 1642572000000, y: 21000000000, count: 1 }, - { x: 1642573800000, y: 7000000000, count: 1 }, - { x: 1642573800000, y: 9000000000, count: 1 }, - { x: 1642573800000, y: 18000000000, count: 1 }, - { x: 1642573800000, y: 32000000000, count: 1 }, - { x: 1642575600000, y: 4000000000, count: 1 }, - { x: 1642575600000, y: 11000000000, count: 1 }, - { x: 1642575600000, y: 19000000000, count: 1 }, - { x: 1642577400000, y: 4000000000, count: 1 }, - { x: 1642577400000, y: 8000000000, count: 1 }, - { x: 1642577400000, y: 10000000000, count: 1 }, - { x: 1642577400000, y: 13000000000, count: 1 }, - { x: 1642577400000, y: 15000000000, count: 1 }, - { x: 1642577400000, y: 17000000000, count: 1 }, - { x: 1642577400000, y: 21000000000, count: 3 }, - { x: 1642577400000, y: 32000000000, count: 1 }, - { x: 1642579200000, y: 15000000000, count: 1 }, - { x: 1642579200000, y: 20000000000, count: 1 }, - { x: 1642581000000, y: 2000000000, count: 2 }, - { x: 1642581000000, y: 4000000000, count: 1 }, - { x: 1642581000000, y: 7000000000, count: 1 }, - { x: 1642581000000, y: 9000000000, count: 1 }, - { x: 1642581000000, y: 12000000000, count: 1 }, - { x: 1642581000000, y: 13000000000, count: 1 }, - { x: 1642581000000, y: 15000000000, count: 1 }, - { x: 1642581000000, y: 17000000000, count: 3 }, - { x: 1642581000000, y: 19000000000, count: 1 }, - { x: 1642581000000, y: 21000000000, count: 1 }, - { x: 1642582800000, y: 4000000000, count: 2 }, - { x: 1642582800000, y: 13000000000, count: 3 }, - { x: 1642582800000, y: 18000000000, count: 1 }, - { x: 1642582800000, y: 21000000000, count: 1 }, - { x: 1642584600000, y: 8000000000, count: 2 }, - { x: 1642584600000, y: 10000000000, count: 1 }, - { x: 1642584600000, y: 12000000000, count: 1 }, - { x: 1642584600000, y: 16000000000, count: 1 }, - { x: 1642584600000, y: 17000000000, count: 1 }, - { x: 1642584600000, y: 19000000000, count: 1 }, - { x: 1642584600000, y: 20000000000, count: 2 }, - { x: 1642586400000, y: 2000000000, count: 1 }, - { x: 1642586400000, y: 4000000000, count: 1 }, - { x: 1642586400000, y: 5000000000, count: 2 }, - { x: 1642586400000, y: 7000000000, count: 1 }, - { x: 1642586400000, y: 8000000000, count: 2 }, - { x: 1642586400000, y: 9000000000, count: 1 }, - { x: 1642586400000, y: 15000000000, count: 3 }, - { x: 1642586400000, y: 16000000000, count: 1 }, - { x: 1642586400000, y: 17000000000, count: 3 }, - { x: 1642586400000, y: 21000000000, count: 1 }, - { x: 1642588200000, y: 4000000000, count: 1 }, - { x: 1642588200000, y: 7000000000, count: 1 }, - { x: 1642588200000, y: 9000000000, count: 1 }, - { x: 1642588200000, y: 10000000000, count: 3 }, - { x: 1642588200000, y: 12000000000, count: 2 }, - { x: 1642588200000, y: 13000000000, count: 1 }, - { x: 1642588200000, y: 19000000000, count: 1 }, - { x: 1642588200000, y: 32000000000, count: 1 }, - { x: 1642590000000, y: 3000000000, count: 1 }, - { x: 1642590000000, y: 6000000000, count: 1 }, - { x: 1642590000000, y: 8000000000, count: 1 }, - { x: 1642590000000, y: 9000000000, count: 1 }, - { x: 1642590000000, y: 13000000000, count: 1 }, - { x: 1642590000000, y: 17000000000, count: 2 }, - { x: 1642590000000, y: 19000000000, count: 1 }, - { x: 1642591800000, y: 2000000000, count: 1 }, - { x: 1642591800000, y: 3000000000, count: 1 }, - { x: 1642591800000, y: 8000000000, count: 1 }, - { x: 1642591800000, y: 10000000000, count: 1 }, - { x: 1642591800000, y: 11000000000, count: 1 }, - { x: 1642591800000, y: 12000000000, count: 1 }, - { x: 1642591800000, y: 13000000000, count: 1 }, - { x: 1642591800000, y: 15000000000, count: 1 }, - { x: 1642591800000, y: 16000000000, count: 1 }, - { x: 1642591800000, y: 17000000000, count: 1 }, - { x: 1642591800000, y: 18000000000, count: 1 }, - { x: 1642591800000, y: 32000000000, count: 1 }, - { x: 1642593600000, y: 2000000000, count: 1 }, - { x: 1642593600000, y: 3000000000, count: 1 }, - { x: 1642593600000, y: 7000000000, count: 1 }, - { x: 1642593600000, y: 13000000000, count: 1 }, - { x: 1642593600000, y: 17000000000, count: 2 }, - { x: 1642593600000, y: 18000000000, count: 1 }, - { x: 1642593600000, y: 21000000000, count: 1 }, - { x: 1642593600000, y: 32000000000, count: 1 }, - { x: 1642595400000, y: 2000000000, count: 2 }, - { x: 1642595400000, y: 3000000000, count: 1 }, - { x: 1642595400000, y: 5000000000, count: 1 }, - { x: 1642595400000, y: 10000000000, count: 1 }, - { x: 1642595400000, y: 12000000000, count: 1 }, - { x: 1642595400000, y: 15000000000, count: 1 }, - { x: 1642595400000, y: 19000000000, count: 1 }, - { x: 1642595400000, y: 21000000000, count: 1 }, - { x: 1642597200000, y: 3000000000, count: 1 }, - { x: 1642597200000, y: 6000000000, count: 1 }, - { x: 1642597200000, y: 7000000000, count: 1 }, - { x: 1642597200000, y: 8000000000, count: 2 }, - { x: 1642597200000, y: 11000000000, count: 1 }, - { x: 1642597200000, y: 13000000000, count: 1 }, - { x: 1642597200000, y: 15000000000, count: 1 }, - { x: 1642597200000, y: 17000000000, count: 1 }, - { x: 1642597200000, y: 18000000000, count: 1 }, - { x: 1642597200000, y: 20000000000, count: 1 }, - { x: 1642599000000, y: 4000000000, count: 2 }, - { x: 1642599000000, y: 6000000000, count: 1 }, - { x: 1642599000000, y: 7000000000, count: 1 }, - { x: 1642599000000, y: 11000000000, count: 1 }, - { x: 1642599000000, y: 12000000000, count: 2 }, - { x: 1642599000000, y: 17000000000, count: 2 }, - { x: 1642599000000, y: 18000000000, count: 1 }, - { x: 1642600800000, y: 4000000000, count: 2 }, - { x: 1642600800000, y: 5000000000, count: 1 }, - { x: 1642600800000, y: 6000000000, count: 1 }, - { x: 1642600800000, y: 11000000000, count: 2 }, - { x: 1642600800000, y: 13000000000, count: 2 }, - { x: 1642600800000, y: 16000000000, count: 1 }, - { x: 1642600800000, y: 18000000000, count: 1 }, - { x: 1642600800000, y: 20000000000, count: 1 }, - { x: 1642600800000, y: 21000000000, count: 1 }, - { x: 1642602600000, y: 2000000000, count: 1 }, - { x: 1642602600000, y: 3000000000, count: 1 }, - { x: 1642602600000, y: 4000000000, count: 2 }, - { x: 1642602600000, y: 5000000000, count: 1 }, - { x: 1642602600000, y: 6000000000, count: 1 }, - { x: 1642602600000, y: 8000000000, count: 1 }, - { x: 1642602600000, y: 9000000000, count: 1 }, - { x: 1642602600000, y: 11000000000, count: 1 }, - { x: 1642602600000, y: 12000000000, count: 2 }, - { x: 1642604400000, y: 2000000000, count: 1 }, - { x: 1642604400000, y: 10000000000, count: 1 }, - { x: 1642604400000, y: 11000000000, count: 1 }, - { x: 1642604400000, y: 17000000000, count: 1 }, - { x: 1642604400000, y: 20000000000, count: 1 }, - { x: 1642604400000, y: 21000000000, count: 1 }, - { x: 1642606200000, y: 2000000000, count: 1 }, - { x: 1642606200000, y: 3000000000, count: 1 }, - { x: 1642606200000, y: 4000000000, count: 2 }, - { x: 1642606200000, y: 8000000000, count: 2 }, - { x: 1642606200000, y: 9000000000, count: 2 }, - { x: 1642606200000, y: 10000000000, count: 1 }, - { x: 1642606200000, y: 12000000000, count: 1 }, - { x: 1642606200000, y: 16000000000, count: 1 }, - { x: 1642606200000, y: 18000000000, count: 1 }, - { x: 1642606200000, y: 20000000000, count: 3 }, - { x: 1642608000000, y: 3000000000, count: 1 }, - { x: 1642608000000, y: 8000000000, count: 1 }, - { x: 1642608000000, y: 9000000000, count: 1 }, - { x: 1642608000000, y: 11000000000, count: 1 }, - { x: 1642608000000, y: 16000000000, count: 1 }, - { x: 1642608000000, y: 17000000000, count: 1 }, - { x: 1642608000000, y: 21000000000, count: 1 }, - { x: 1642609800000, y: 7000000000, count: 1 }, -]; diff --git a/storybook/stories/test_cases/test_cases.stories.tsx b/storybook/stories/test_cases/test_cases.stories.tsx index f3679ea73ee..3f841650055 100644 --- a/storybook/stories/test_cases/test_cases.stories.tsx +++ b/storybook/stories/test_cases/test_cases.stories.tsx @@ -18,4 +18,3 @@ export { Example as legendScrollBarSizing } from './5_legend_scroll_bar_sizing.s export { Example as accessibilityCustomizations } from './6_a11y_custom_description.story'; export { Example as rtlText } from './7_rtl_text.story'; export { Example as testPointsOutsideOfDomain } from './8_test_points_outside_of_domain.story'; -export { Example as heatmapAxisLabels } from './9_heatmap_axis_labels.story';