diff --git a/.eslintrc.js b/.eslintrc.js index 97e6070d956..82c477226a6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -68,7 +68,7 @@ module.exports = { '@typescript-eslint/no-unsafe-return': 0, '@typescript-eslint/explicit-module-boundary-types': 0, '@typescript-eslint/restrict-plus-operands': 0, // rule is broken - '@typescript-eslint/no-unsafe-call': 1, + '@typescript-eslint/no-unsafe-call': 0, // seems to have issues with default import types '@typescript-eslint/unbound-method': 1, '@typescript-eslint/no-redeclare': 'off', // we use to declare enum type and object with the same name '@typescript-eslint/no-shadow': 'off', // we use shadow mostly within the canvas renderer function when we need a new context 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 new file mode 100644 index 00000000000..4591d57f62d Binary files /dev/null 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-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 b09461c6a23..c54cbb35cd3 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__/all-test-ts-baseline-visual-tests-for-all-stories-wordcloud-alpha-simple-wordcloud-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-wordcloud-alpha-simple-wordcloud-visually-looks-correct-1-snap.png index beac770b2fd..162850349fd 100644 Binary files a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-wordcloud-alpha-simple-wordcloud-visually-looks-correct-1-snap.png and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-wordcloud-alpha-simple-wordcloud-visually-looks-correct-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 new file mode 100644 index 00000000000..1f1d4de6cf1 Binary files /dev/null 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 new file mode 100644 index 00000000000..f0f683ea7fd Binary files /dev/null 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 new file mode 100644 index 00000000000..515a27f52b8 Binary files /dev/null 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 new file mode 100644 index 00000000000..d5c5c8d86b9 Binary files /dev/null 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 new file mode 100644 index 00000000000..db266ec5c7b Binary files /dev/null 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 new file mode 100644 index 00000000000..3d828725f74 Binary files /dev/null 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 new file mode 100644 index 00000000000..5529bde4dcb Binary files /dev/null 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 new file mode 100644 index 00000000000..55ce2ab79a3 Binary files /dev/null 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__/wordcloud-stories-test-ts-scales-stories-should-render-multiple-wordcloud-template-1-snap.png b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-multiple-wordcloud-template-1-snap.png new file mode 100644 index 00000000000..c3c66703d58 Binary files /dev/null and b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-multiple-wordcloud-template-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-right-angled-wordcloud-template-1-snap.png b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-right-angled-wordcloud-template-1-snap.png new file mode 100644 index 00000000000..a67c9695865 Binary files /dev/null and b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-right-angled-wordcloud-template-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-single-wordcloud-template-1-snap.png b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-single-wordcloud-template-1-snap.png new file mode 100644 index 00000000000..fa21e5635d5 Binary files /dev/null and b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-single-wordcloud-template-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-small-waves-wordcloud-template-1-snap.png b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-small-waves-wordcloud-template-1-snap.png new file mode 100644 index 00000000000..991c02b9cc4 Binary files /dev/null and b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-small-waves-wordcloud-template-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-sparse-wordcloud-template-1-snap.png b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-sparse-wordcloud-template-1-snap.png new file mode 100644 index 00000000000..85541139f75 Binary files /dev/null and b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-sparse-wordcloud-template-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-square-words-wordcloud-template-1-snap.png b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-square-words-wordcloud-template-1-snap.png new file mode 100644 index 00000000000..2efb2e1cac1 Binary files /dev/null and b/integration/tests/__image_snapshots__/wordcloud-stories-test-ts-scales-stories-should-render-square-words-wordcloud-template-1-snap.png differ diff --git a/integration/tests/heatmap_stories.test.ts b/integration/tests/heatmap_stories.test.ts index 16ac0add18b..87404fbec3b 100644 --- a/integration/tests/heatmap_stories.test.ts +++ b/integration/tests/heatmap_stories.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { eachTheme } from '../helpers'; import { common } from '../page_objects'; describe('Heatmap stories', () => { @@ -16,10 +17,28 @@ describe('Heatmap stories', () => { { left: 300, top: 300 }, ); }); + + eachTheme.describe((_, themeParams) => { + it('should render basic heatmap', async () => { + await common.expectChartAtUrlToMatchScreenshot( + `http://localhost:9001/?path=/story/heatmap-alpha--basic&${themeParams}`, + ); + }); + + it('should render correct brush area', async () => { + await common.expectChartWithDragAtUrlToMatchScreenshot( + `http://localhost:9001/?path=/story/heatmap-alpha--basic&${themeParams}`, + { left: 200, top: 100 }, + { left: 400, top: 250 }, + ); + }); + }); + it('should maximize the label with an unique fontSize', async () => { await page.setViewport({ width: 450, height: 600 }); await common.expectChartAtUrlToMatchScreenshot('http://localhost:9001/?path=/story/heatmap-alpha--categorical'); }); + it('should maximize the label fontSize', async () => { await page.setViewport({ width: 420, height: 600 }); await common.expectChartAtUrlToMatchScreenshot( diff --git a/integration/tests/wordcloud_stories.test.ts b/integration/tests/wordcloud_stories.test.ts new file mode 100644 index 00000000000..8f4ae9df3ea --- /dev/null +++ b/integration/tests/wordcloud_stories.test.ts @@ -0,0 +1,18 @@ +/* + * 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 { TEMPLATES } from '../../storybook/stories/wordcloud/1_wordcloud.story'; +import { common } from '../page_objects'; + +describe('Scales stories', () => { + it.each(TEMPLATES.filter((t) => t !== 'edit'))('should render %s wordcloud template', async (template) => { + await common.expectChartAtUrlToMatchScreenshot( + `http://localhost:9001/?path=/story/wordcloud-alpha--simple-wordcloud&knob-template=${template}`, + ); + }); +}); diff --git a/package.json b/package.json index d8feec43ed1..5e09c472ecb 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@types/color": "^3.0.1", "@types/core-js": "^2.5.2", "@types/d3-array": "^1.2.6", + "@types/d3-cloud": "^1.2.5", "@types/d3-collection": "^1.0.8", "@types/d3-interpolate": "^1.3.1", "@types/d3-scale": "^2.1.1", diff --git a/packages/charts/api/charts.api.md b/packages/charts/api/charts.api.md index 78049a7ea24..48f8459d2bf 100644 --- a/packages/charts/api/charts.api.md +++ b/packages/charts/api/charts.api.md @@ -937,7 +937,7 @@ export function getNodeName(node: ArrayNode): string; // Warning: (ae-forgotten-export) The symbol "SpecOptionalProps" needs to be exported by the entry point index.d.ts // // @alpha (undocumented) -export const Goal: React_2.FunctionComponent; +export const Goal: React_2.FunctionComponent; // @alpha (undocumented) export type GoalLabelAccessor = LabelAccessor; @@ -1061,8 +1061,11 @@ export type GroupId = string; // @public (undocumented) export type GroupKeysOrKeyFn = Array | GroupByKeyFn; +// Warning: (ae-forgotten-export) The symbol "SpecRequiredProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "SpecOptionalProps" needs to be exported by the entry point index.d.ts +// // @alpha (undocumented) -export const Heatmap: React_2.FunctionComponent & Partial>>; +export const Heatmap: React_2.FunctionComponent; // @alpha (undocumented) export interface HeatmapBandsColorScale { @@ -1081,10 +1084,60 @@ export type HeatmapBrushEvent = { }; // @public (undocumented) -export interface HeatmapConfig { +export type HeatmapElementEvent = [Cell, SeriesIdentifier]; + +// @alpha (undocumented) +export interface HeatmapSpec extends Spec { + // (undocumented) + chartType: typeof ChartType.Heatmap; + // (undocumented) + colorScale: HeatmapBandsColorScale; + // (undocumented) + data: Datum[]; + // (undocumented) + highlightedData?: { + x: Array; + y: Array; + }; + // (undocumented) + name?: string; + // Warning: (ae-forgotten-export) The symbol "HeatmapBrushEvent" needs to be exported by the entry point index.d.ts + // + // (undocumented) + onBrushEnd?: (brushArea: HeatmapBrushEvent_2) => void; + // (undocumented) + specType: typeof SpecType.Series; + // (undocumented) + timeZone: string; + // (undocumented) + valueAccessor: Accessor | AccessorFn; + // (undocumented) + valueFormatter: (value: number) => string; + // (undocumented) + xAccessor: Accessor | AccessorFn; + // (undocumented) + xAxisLabelFormatter: (value: string | number) => string; + // (undocumented) + xAxisLabelName: string; + // (undocumented) + xScale: RasterTimeScale | OrdinalScale | LinearScale; + // (undocumented) + xSortPredicate: Predicate; + // (undocumented) + yAccessor: Accessor | AccessorFn; + // (undocumented) + yAxisLabelFormatter: (value: string | number) => string; + // (undocumented) + yAxisLabelName: string; + // (undocumented) + ySortPredicate: Predicate; +} + +// @public (undocumented) +export interface HeatmapStyle { brushArea: { visible: boolean; - fill: Color; + fill?: Color; stroke: Color; strokeWidth: number; }; @@ -1113,10 +1166,6 @@ export interface HeatmapConfig { stroke: Color; }; }; - // Warning: (ae-forgotten-export) The symbol "FontFamily" needs to be exported by the entry point index.d.ts - // - // (undocumented) - fontFamily: FontFamily; // (undocumented) grid: { cellWidth: { @@ -1133,40 +1182,24 @@ export interface HeatmapConfig { }; }; // (undocumented) - height: Pixels; - // (undocumented) - margin: { - left: SizeRatio; - right: SizeRatio; - top: SizeRatio; - bottom: SizeRatio; - }; - // (undocumented) maxColumnWidth: Pixels; // (undocumented) maxLegendHeight?: number; // (undocumented) maxRowHeight: Pixels; - // (undocumented) - timeZone: string; - // (undocumented) - width: Pixels; // Warning: (ae-forgotten-export) The symbol "Font" needs to be exported by the entry point index.d.ts // // (undocumented) xAxisLabel: Font & { - name: string; fontSize: Pixels; width: Pixels | 'auto'; align: TextAlign; baseline: TextBaseline; visible: boolean; padding: number; - formatter: (value: string | number) => string; }; // (undocumented) yAxisLabel: Font & { - name: string; fontSize: Pixels; width: Pixels | 'auto' | { max: Pixels; @@ -1179,46 +1212,7 @@ export interface HeatmapConfig { top?: number; bottom?: number; }; - formatter: (value: string | number) => string; - }; -} - -// @public (undocumented) -export type HeatmapElementEvent = [Cell, SeriesIdentifier]; - -// @alpha (undocumented) -export interface HeatmapSpec extends Spec { - // (undocumented) - chartType: typeof ChartType.Heatmap; - // (undocumented) - colorScale: HeatmapBandsColorScale; - // (undocumented) - config: RecursivePartial; - // (undocumented) - data: Datum[]; - // (undocumented) - highlightedData?: { - x: Array; - y: Array; }; - // (undocumented) - name?: string; - // (undocumented) - specType: typeof SpecType.Series; - // (undocumented) - valueAccessor: Accessor | AccessorFn; - // (undocumented) - valueFormatter: (value: number) => string; - // (undocumented) - xAccessor: Accessor | AccessorFn; - // (undocumented) - xScale: RasterTimeScale | OrdinalScale | LinearScale; - // (undocumented) - xSortPredicate: Predicate; - // (undocumented) - yAccessor: Accessor | AccessorFn; - // (undocumented) - ySortPredicate: Predicate; } // @public @@ -2119,9 +2113,6 @@ export interface SimplePadding { outer: number; } -// @public (undocumented) -export type SizeRatio = Ratio; - // @alpha (undocumented) export const SmallMultiples: React_2.FunctionComponent; @@ -2213,11 +2204,6 @@ export interface StrokeStyle { strokeWidth: number; } -// Warning: (ae-forgotten-export) The symbol "TEXT_ALIGNS" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export type TextAlign = typeof TEXT_ALIGNS[number]; - // @public export interface TextAlignment { // (undocumented) @@ -2226,11 +2212,6 @@ export interface TextAlignment { vertical: VerticalAlignment; } -// Warning: (ae-forgotten-export) The symbol "TEXT_BASELINE" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export type TextBaseline = typeof TEXT_BASELINE[number]; - // @public export interface TextOffset { reference: 'global' | 'local'; @@ -2315,6 +2296,8 @@ export interface Theme { // (undocumented) goal: GoalStyles; // (undocumented) + heatmap: HeatmapStyle; + // (undocumented) legend: LegendStyle; lineSeriesStyle: LineSeriesStyle; markSizeRatio?: number; @@ -2341,9 +2324,6 @@ export type TickStyle = StrokeStyle & Visible & { // @public (undocumented) export function timeFormatter(format: string): TickFormatter; -// @public (undocumented) -export type TimeMs = number; - // @public (undocumented) export interface TimeScale { // (undocumented) @@ -2499,52 +2479,24 @@ export type WeightFn = $Values; // Warning: (ae-forgotten-export) The symbol "SpecOptionalProps" needs to be exported by the entry point index.d.ts // // @alpha (undocumented) -export const Wordcloud: React_2.FunctionComponent; +export const Wordcloud: React_2.FunctionComponent; // @public (undocumented) -export interface WordcloudConfigs { - // (undocumented) - count: number; - // (undocumented) - endAngle: number; - // (undocumented) - exponent: number; - // (undocumented) - fontFamily: string; - // (undocumented) - fontStyle: FontStyle; - // (undocumented) - fontWeight: number; - // (undocumented) - height: number; - // (undocumented) - maxFontSize: number; - // (undocumented) - minFontSize: number; - // (undocumented) - padding: number; - // (undocumented) - spiral: string; - // (undocumented) - startAngle: number; +export type WordCloudElementEvent = [WordModel, SeriesIdentifier]; + +// @alpha (undocumented) +export interface WordcloudSpec extends Spec, WordcloudViewModel { // (undocumented) - weightFn: WeightFn; + chartType: typeof ChartType.Wordcloud; // (undocumented) - width: number; + specType: typeof SpecType.Series; } // @public (undocumented) -export type WordCloudElementEvent = [WordModel, SeriesIdentifier]; - -// @alpha (undocumented) -export interface WordcloudSpec extends Spec { +export interface WordcloudViewModel { // (undocumented) angleCount: number; // (undocumented) - chartType: typeof ChartType.Wordcloud; - // (undocumented) - config: RecursivePartial; - // (undocumented) data: WordModel[]; // (undocumented) endAngle: number; @@ -2565,8 +2517,6 @@ export interface WordcloudSpec extends Spec { // (undocumented) padding: number; // (undocumented) - specType: typeof SpecType.Series; - // (undocumented) spiral: string; // (undocumented) startAngle: number; @@ -2623,6 +2573,12 @@ export interface YDomainBase { // @public (undocumented) export type YDomainRange = YDomainBase & DomainRange & LogScaleOptions; +// Warnings were encountered during analysis: +// +// src/chart_types/partition_chart/layout/types/config_types.ts:137:5 - (ae-forgotten-export) The symbol "TimeMs" needs to be exported by the entry point index.d.ts +// src/utils/themes/theme.ts:216:5 - (ae-forgotten-export) The symbol "TextAlign" needs to be exported by the entry point index.d.ts +// src/utils/themes/theme.ts:217: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/config/config.ts b/packages/charts/src/chart_types/heatmap/layout/config/config.ts deleted file mode 100644 index 0ef5278d7cf..00000000000 --- a/packages/charts/src/chart_types/heatmap/layout/config/config.ts +++ /dev/null @@ -1,102 +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 { Colors } from '../../../../common/colors'; -import { Config } from '../types/config_types'; - -/** @internal */ -export const config: Config = { - width: 500, - height: 500, - margin: { left: 0.01, right: 0.01, top: 0.01, bottom: 0.01 }, - maxRowHeight: 30, - maxColumnWidth: 30, - fontFamily: 'Sans-Serif', - - brushArea: { - visible: true, - fill: Colors.Black.keyword, // black === transparent - stroke: '#69707D', // euiColorDarkShade, - strokeWidth: 2, - }, - brushMask: { - visible: true, - fill: 'rgb(115 115 115 / 50%)', - }, - brushTool: { - visible: false, - fill: 'gray', - }, - - timeZone: 'UTC', - - xAxisLabel: { - name: 'X Value', - visible: true, - width: 'auto', - fontSize: 12, - fontFamily: 'Sans-Serif', - fontStyle: 'normal', - textColor: Colors.Black.keyword, - fontVariant: 'normal', - fontWeight: 'normal', - align: 'center' as CanvasTextAlign, - baseline: 'verticalAlign' as CanvasTextBaseline, - padding: 6, - formatter: String, - }, - yAxisLabel: { - name: 'Y Value', - visible: true, - width: 'auto', - fontSize: 12, - fontFamily: 'Sans-Serif', - fontStyle: 'normal', - textColor: Colors.Black.keyword, - fontVariant: 'normal', - fontWeight: 'normal', - baseline: 'verticalAlign' as CanvasTextBaseline, - padding: 5, - formatter: String, - }, - grid: { - cellWidth: { - min: 0, - max: 30, - }, - cellHeight: { - min: 12, - max: 30, - }, - stroke: { - width: 1, - color: 'gray', - }, - }, - cell: { - maxWidth: 'fill', - maxHeight: 'fill', - align: 'center', - label: { - visible: true, - maxWidth: 'fill', - minFontSize: 8, - maxFontSize: 12, - fontFamily: 'Sans-Serif', - fontStyle: 'normal', - textColor: Colors.Black.keyword, - fontVariant: 'normal', - fontWeight: 'normal', - useGlobalMinFontSize: true, - }, - border: { - strokeWidth: 1, - stroke: 'gray', - }, - }, -}; diff --git a/packages/charts/src/chart_types/heatmap/layout/types/config_types.ts b/packages/charts/src/chart_types/heatmap/layout/types/config_types.ts deleted file mode 100644 index 7fdf528a30b..00000000000 --- a/packages/charts/src/chart_types/heatmap/layout/types/config_types.ts +++ /dev/null @@ -1,93 +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 { Color } from '../../../../common/colors'; -import { Pixels, SizeRatio } from '../../../../common/geometry'; -import { Font, FontFamily, TextAlign, TextBaseline } from '../../../../common/text_utils'; - -/** - * @public - */ -export interface Config { - width: Pixels; - height: Pixels; - margin: { left: SizeRatio; right: SizeRatio; top: SizeRatio; bottom: SizeRatio }; - maxRowHeight: Pixels; - maxColumnWidth: Pixels; - // general text config - fontFamily: FontFamily; - timeZone: string; - - /** - * Config of the mask over the area outside of the selected cells - */ - brushMask: { visible: boolean; fill: Color }; - /** - * Config of the mask over the selected cells - */ - brushArea: { visible: boolean; fill: Color; stroke: Color; strokeWidth: number }; - /** - * Config of the brushing tool - */ - brushTool: { - visible: boolean; - // TODO add support for changing the brush tool color - fill: Color; - }; - - xAxisLabel: Font & { - name: string; - fontSize: Pixels; - width: Pixels | 'auto'; - align: TextAlign; - baseline: TextBaseline; - visible: boolean; - padding: number; - formatter: (value: string | number) => string; - }; - yAxisLabel: Font & { - name: string; - fontSize: Pixels; - width: Pixels | 'auto' | { max: Pixels }; - baseline: TextBaseline; - visible: boolean; - padding: number | { left?: number; right?: number; top?: number; bottom?: number }; - formatter: (value: string | number) => string; - }; - grid: { - cellWidth: { - min: Pixels; - max: Pixels | 'fill'; - }; - cellHeight: { - min: Pixels; - max: Pixels | 'fill'; - }; - stroke: { - color: string; - width: number; - }; - }; - cell: { - maxWidth: Pixels | 'fill'; - maxHeight: Pixels | 'fill'; - align: 'center'; - label: Font & { - minFontSize: Pixels; - maxFontSize: Pixels; - useGlobalMinFontSize: boolean; - maxWidth: Pixels | 'fill'; - visible: boolean; - }; - border: { - strokeWidth: Pixels; - stroke: Color; - }; - }; - maxLegendHeight?: number; -} 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 5d4b8990807..3e1db5290ec 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 @@ -13,10 +13,10 @@ import { Box } from '../../../../common/text_utils'; import { Fill, Line, Rect, Stroke } from '../../../../geoms/types'; import { HeatmapBrushEvent } from '../../../../specs/settings'; import { Point } from '../../../../utils/point'; +import { LIGHT_THEME } from '../../../../utils/themes/light_theme'; +import { HeatmapStyle } from '../../../../utils/themes/theme'; import { PrimitiveValue } from '../../../partition_chart/layout/utils/group_by_rollup'; -import { config } from '../config/config'; import { HeatmapCellDatum } from '../viewmodel/viewmodel'; -import { Config } from './config_types'; /** @internal */ export interface Value { @@ -98,7 +98,7 @@ export type DragShape = ReturnType; /** @internal */ export type ShapeViewModel = { - config: Config; + theme: HeatmapStyle; heatmapViewModel: HeatmapViewModel; pickQuads: PickFunction; pickDragArea: PickDragFunction; @@ -125,8 +125,8 @@ export const nullHeatmapViewModel: HeatmapViewModel = { }; /** @internal */ -export const nullShapeViewModel = (specifiedConfig?: Config): ShapeViewModel => ({ - config: specifiedConfig || config, +export const nullShapeViewModel = (): ShapeViewModel => ({ + theme: LIGHT_THEME.heatmap, heatmapViewModel: nullHeatmapViewModel, pickQuads: () => [], pickDragArea: () => ({ cells: [], x: [], y: [], chartType: ChartType.Heatmap }), 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 d5f689744e8..42496fd5051 100644 --- a/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts +++ b/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts @@ -15,19 +15,18 @@ import { Pixels } from '../../../../common/geometry'; import { Box, maximiseFontSize, TextMeasure } from '../../../../common/text_utils'; import { ScaleContinuous } from '../../../../scales'; import { ScaleType } from '../../../../scales/constants'; -import { LinearScale, OrdinalScale, RasterTimeScale, SettingsSpec } from '../../../../specs'; +import { LinearScale, OrdinalScale, RasterTimeScale } from '../../../../specs'; import { withTextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator'; import { addIntervalToTime } from '../../../../utils/chrono/elasticsearch'; import { clamp } from '../../../../utils/common'; import { Dimensions } from '../../../../utils/dimensions'; import { Logger } from '../../../../utils/logger'; -import { Theme } from '../../../../utils/themes/theme'; +import { HeatmapStyle, Theme } from '../../../../utils/themes/theme'; import { PrimitiveValue } from '../../../partition_chart/layout/utils/group_by_rollup'; import { HeatmapSpec } from '../../specs'; import { HeatmapTable } from '../../state/selectors/compute_chart_dimensions'; import { ColorScale } from '../../state/selectors/get_color_scale'; import { GridHeightParams } from '../../state/selectors/get_grid_full_height'; -import { Config } from '../types/config_types'; import { Cell, PickDragFunction, @@ -57,7 +56,8 @@ function getValuesInRange( function estimatedNonOverlappingTickCount( chartWidth: number, - { formatter, padding, fontSize, fontFamily }: Config['xAxisLabel'], + formatter: HeatmapSpec['xAxisLabelFormatter'], + { padding, fontSize, fontFamily }: HeatmapStyle['xAxisLabel'], ): number { return withTextMeasure((textMeasure) => { const labelSample = formatter(Date.now()); @@ -69,30 +69,27 @@ function estimatedNonOverlappingTickCount( return Math.floor(maxTicks / 2); }); } - /** @internal */ export function shapeViewModel( textMeasure: TextMeasure, spec: HeatmapSpec, - config: Config, - settingsSpec: SettingsSpec, + { heatmap: heatmapTheme, background }: Theme, chartDimensions: Dimensions, heatmapTable: HeatmapTable, colorScale: ColorScale, bandsToHide: Array<[number, number]>, { height, pageSize }: GridHeightParams, - theme: Theme, ): ShapeViewModel { - const gridStrokeWidth = config.grid.stroke.width ?? 1; + const gridStrokeWidth = heatmapTheme.grid.stroke.width ?? 1; const { table, yValues, xValues } = heatmapTable; // measure the text width of all rows values to get the grid area width const boxedYValues = yValues.map }>((value) => ({ - text: config.yAxisLabel.formatter(value), + text: spec.yAxisLabelFormatter(value), value, isValue: false, - ...config.yAxisLabel, + ...heatmapTheme.yAxisLabel, })); // compute the scale for the rows positions @@ -107,8 +104,8 @@ export function shapeViewModel( // compute the cell width (can be smaller then the available size depending on config const cellWidth = - config.cell.maxWidth !== 'fill' && xScale.bandwidth() > config.cell.maxWidth - ? config.cell.maxWidth + heatmapTheme.cell.maxWidth !== 'fill' && xScale.bandwidth() > heatmapTheme.cell.maxWidth + ? heatmapTheme.cell.maxWidth : xScale.bandwidth(); // compute the cell height (we already computed the max size for that) @@ -117,9 +114,9 @@ export function shapeViewModel( const currentGridHeight = cellHeight * pageSize; // compute the position of each column label - const textXValues = getXTicks(spec, config, chartDimensions, xScale, heatmapTable, currentGridHeight); + const textXValues = getXTicks(spec, heatmapTheme, chartDimensions, xScale, heatmapTable, currentGridHeight); - const { padding } = config.yAxisLabel; + const { padding } = heatmapTheme.yAxisLabel; const rightPadding = typeof padding === 'number' ? padding : padding.right ?? 0; // compute the position of each row label @@ -135,11 +132,11 @@ export function shapeViewModel( const cellWidthInner = cellWidth - gridStrokeWidth * 2; const cellHeightInner = cellHeight - gridStrokeWidth * 2; - if (colorToRgba(theme.background.color)[3] < 1) { + if (colorToRgba(background.color)[3] < 1) { Logger.expected( `Text contrast requires a opaque background color, using white as fallback`, 'an opaque color', - theme.background.color, + background.color, ); } @@ -160,9 +157,9 @@ export function shapeViewModel( const fontSize = maximiseFontSize( textMeasure, formattedValue, - config.cell.label, - config.cell.label.minFontSize, - config.cell.label.maxFontSize, + heatmapTheme.cell.label, + heatmapTheme.cell.label.minFontSize, + heatmapTheme.cell.label.maxFontSize, // adding 3px padding per side to avoid that text touches the edges cellWidthInner - 6, cellHeightInner - 6, @@ -170,7 +167,8 @@ export function shapeViewModel( acc[cellKey] = { x: - (config.cell.maxWidth !== 'fill' ? x + xScale.bandwidth() / 2 - config.cell.maxWidth / 2 : x) + gridStrokeWidth, + (heatmapTheme.cell.maxWidth !== 'fill' ? x + xScale.bandwidth() / 2 - heatmapTheme.cell.maxWidth / 2 : x) + + gridStrokeWidth, y, yIndex, width: cellWidthInner, @@ -180,14 +178,14 @@ export function shapeViewModel( color: colorToRgba(cellBackgroundColor), }, stroke: { - color: colorToRgba(config.cell.border.stroke), - width: config.cell.border.strokeWidth, + color: colorToRgba(heatmapTheme.cell.border.stroke), + width: heatmapTheme.cell.border.strokeWidth, }, value: d.value, visible: !isValueHidden(d.value, bandsToHide), formatted: formattedValue, fontSize, - textColor: fillTextColor(cellBackgroundColor, theme.background.color), + textColor: fillTextColor(cellBackgroundColor, background.color), }; return acc; }, {}); @@ -245,7 +243,7 @@ export function shapeViewModel( const allYValuesInRange: Array> = getValuesInRange(yValues, startY, endY); const invertedXValues: Array> = isRasterTimeScale(spec.xScale) && typeof endX === 'number' - ? [startX, addIntervalToTime(endX, spec.xScale.interval, config.timeZone)] + ? [startX, addIntervalToTime(endX, spec.xScale.interval, spec.timeZone)] : [...allXValuesInRange]; const cells: Cell[] = []; @@ -344,7 +342,7 @@ export function shapeViewModel( const tableMinFontSize = cells.reduce((acc, { fontSize }) => Math.min(acc, fontSize), Infinity); return { - config, + theme: heatmapTheme, heatmapViewModel: { gridOrigin: { x: chartDimensions.left, @@ -354,13 +352,13 @@ export function shapeViewModel( x: xLines, y: yLines, stroke: { - color: colorToRgba(config.grid.stroke.color), + color: colorToRgba(heatmapTheme.grid.stroke.color), width: gridStrokeWidth, }, }, pageSize, cells, - cellFontSize: (cell: Cell) => (config.cell.label.useGlobalMinFontSize ? tableMinFontSize : cell.fontSize), + cellFontSize: (cell: Cell) => (heatmapTheme.cell.label.useGlobalMinFontSize ? tableMinFontSize : cell.fontSize), xValues: textXValues, yValues: textYValues, }, @@ -386,23 +384,23 @@ export function isRasterTimeScale(scale: RasterTimeScale | OrdinalScale | Linear function getXTicks( spec: HeatmapSpec, - config: Config, + style: HeatmapStyle, chartDimensions: Dimensions, xScale: ScaleBand, { xValues, xNumericExtent }: HeatmapTable, gridHeight: number, ): Array { const getTextValue = ( - formatter: Config['xAxisLabel']['formatter'], + formatter: HeatmapSpec['xAxisLabelFormatter'], scaleCallback: (x: string | number) => number | undefined | null, ) => (value: string | number): TextBox => { return { text: formatter(value), value, isValue: false, - ...config.xAxisLabel, + ...style.xAxisLabel, x: chartDimensions.left + (scaleCallback(value) ?? 0), - y: gridHeight + config.xAxisLabel.fontSize / 2 + config.xAxisLabel.padding, + y: gridHeight + style.xAxisLabel.fontSize / 2 + style.xAxisLabel.padding, }; }; if (isRasterTimeScale(spec.xScale)) { @@ -414,16 +412,20 @@ function getXTicks( nice: false, }, { - desiredTickCount: estimatedNonOverlappingTickCount(chartDimensions.width, config.xAxisLabel), - timeZone: config.timeZone, + desiredTickCount: estimatedNonOverlappingTickCount( + chartDimensions.width, + spec.xAxisLabelFormatter, + style.xAxisLabel, + ), + timeZone: spec.timeZone, }, ); - return timeScale.ticks().map(getTextValue(config.xAxisLabel.formatter, (x) => timeScale.scale(x))); + return timeScale.ticks().map(getTextValue(spec.xAxisLabelFormatter, (x) => timeScale.scale(x))); } return xValues.map((textBox: string | number) => { return { - ...getTextValue(config.xAxisLabel.formatter, xScale)(textBox), + ...getTextValue(spec.xAxisLabelFormatter, xScale)(textBox), x: chartDimensions.left + (xScale(textBox) || 0) + xScale.bandwidth() / 2, }; }); 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 2a609cad6a8..8ec146a4834 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 @@ -18,11 +18,9 @@ import { ShapeViewModel } from '../../layout/types/viewmodel_types'; export function renderCanvas2d( ctx: CanvasRenderingContext2D, dpr: number, - { config, heatmapViewModel }: ShapeViewModel, + { theme, heatmapViewModel }: ShapeViewModel, background: Color, ) { - // eslint-disable-next-line no-empty-pattern - const {} = config; withContext(ctx, () => { // set some defaults for the overall rendering @@ -49,16 +47,16 @@ export function renderCanvas2d( renderLayers(ctx, [ () => clearCanvas(ctx, background), () => { + // Grid withContext(ctx, () => { - // render grid renderMultiLine(ctx, heatmapViewModel.gridLines.x, heatmapViewModel.gridLines.stroke); renderMultiLine(ctx, heatmapViewModel.gridLines.y, heatmapViewModel.gridLines.stroke); }); }, () => + // Cells withContext(ctx, () => { - // render cells const { x, y } = heatmapViewModel.gridOrigin; ctx.translate(x, y); filteredCells.forEach((cell) => { @@ -67,16 +65,15 @@ export function renderCanvas2d( }), () => - config.cell.label.visible && withContext(ctx, () => { - // render text on cells + // Text on cells const { x, y } = heatmapViewModel.gridOrigin; ctx.translate(x, y); filteredCells.forEach((cell) => { const fontSize = heatmapViewModel.cellFontSize(cell); if (cell.visible && Number.isFinite(fontSize)) renderText(ctx, { x: cell.x + cell.width / 2, y: cell.y + cell.height / 2 }, cell.formatted, { - ...config.cell.label, + ...theme.cell.label, fontSize, align: 'center', baseline: 'middle', @@ -86,25 +83,25 @@ export function renderCanvas2d( }), () => - // render text on Y axis - config.yAxisLabel.visible && + // Y Axis + theme.yAxisLabel.visible && withContext(ctx, () => filteredYValues.forEach((yValue) => { const font: Font = { - fontFamily: config.yAxisLabel.fontFamily, - fontStyle: config.yAxisLabel.fontStyle ? config.yAxisLabel.fontStyle : 'normal', + fontFamily: theme.yAxisLabel.fontFamily, + fontStyle: theme.yAxisLabel.fontStyle ? theme.yAxisLabel.fontStyle : 'normal', fontVariant: 'normal', fontWeight: 'normal', textColor: Colors.Black.keyword, }; - const { padding } = config.yAxisLabel; + const { padding } = theme.yAxisLabel; const horizontalPadding = typeof padding === 'number' ? padding * 2 : (padding.left ?? 0) + (padding.right ?? 0); const [resultText] = wrapLines( ctx, yValue.text, font, - config.yAxisLabel.fontSize, + theme.yAxisLabel.fontSize, heatmapViewModel.gridOrigin.x - horizontalPadding, 16, { shouldAddEllipsis: true, wrapAtWord: false }, @@ -114,17 +111,17 @@ export function renderCanvas2d( { x: yValue.x, y: yValue.y }, resultText, // the alignment for y axis labels is fixed to the right - { ...config.yAxisLabel, align: 'right' }, + { ...theme.yAxisLabel, align: 'right' }, ); }), ), () => - // render text on X axis - config.xAxisLabel.visible && + // Text on X axis + theme.xAxisLabel.visible && withContext(ctx, () => heatmapViewModel.xValues.forEach((xValue) => - renderText(ctx, { x: xValue.x, y: xValue.y }, xValue.text, config.xAxisLabel), + renderText(ctx, { x: xValue.x, y: xValue.y }, xValue.text, theme.xAxisLabel), ), ), ]); 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 52c815a5dcd..24706e2acf1 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 @@ -23,7 +23,7 @@ import { getChartThemeSelector } from '../../../../state/selectors/get_chart_the import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; import { Dimensions } from '../../../../utils/dimensions'; import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; -import { geometries } from '../../state/selectors/geometries'; +import { getHeatmapGeometries } from '../../state/selectors/geometries'; import { getHeatmapContainerSizeSelector } from '../../state/selectors/get_heatmap_container_size'; import { renderCanvas2d } from './canvas_renderers'; @@ -88,13 +88,12 @@ class Component extends React.Component { private drawCanvas() { if (this.ctx) { - const { width, height }: Dimensions = this.props.chartContainerDimensions; renderCanvas2d( this.ctx, this.devicePixelRatio, { ...this.props.geometries, - config: { ...this.props.geometries.config, width, height }, + theme: this.props.geometries.theme, }, this.props.background, ); @@ -160,7 +159,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { } return { initialized: true, - geometries: geometries(state), + geometries: getHeatmapGeometries(state), chartContainerDimensions: getHeatmapContainerSizeSelector(state), a11ySettings: getA11ySettingsSelector(state), background: getChartThemeSelector(state).background.color, diff --git a/packages/charts/src/chart_types/heatmap/renderer/dom/highlighter.tsx b/packages/charts/src/chart_types/heatmap/renderer/dom/highlighter.tsx index 6a29b8c725e..9fb625d7fa3 100644 --- a/packages/charts/src/chart_types/heatmap/renderer/dom/highlighter.tsx +++ b/packages/charts/src/chart_types/heatmap/renderer/dom/highlighter.tsx @@ -9,8 +9,8 @@ import React, { FC } from 'react'; import { Dimensions } from '../../../../utils/dimensions'; -import { config } from '../../layout/config/config'; -import { Config } from '../../layout/types/config_types'; +import { LIGHT_THEME } from '../../../../utils/themes/light_theme'; +import { HeatmapStyle } from '../../../../utils/themes/theme'; import { DragShape, nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; /** @internal */ @@ -20,8 +20,8 @@ export interface HighlighterCellsProps { canvasDimension: Dimensions; geometries: ShapeViewModel; dragShape: DragShape | null; - brushMask: Config['brushMask']; - brushArea: Config['brushArea']; + brushMask: HeatmapStyle['brushMask']; + brushArea: HeatmapStyle['brushArea']; } /** @@ -144,6 +144,6 @@ export const DEFAULT_PROPS: HighlighterCellsProps = { }, geometries: nullShapeViewModel(), dragShape: { x: 0, y: 0, height: 0, width: 0 }, - brushArea: config.brushArea, - brushMask: config.brushMask, + brushArea: LIGHT_THEME.heatmap.brushArea, + brushMask: LIGHT_THEME.heatmap.brushMask, }; diff --git a/packages/charts/src/chart_types/heatmap/renderer/dom/highlighter_brush.tsx b/packages/charts/src/chart_types/heatmap/renderer/dom/highlighter_brush.tsx index 4c5d5b5bd43..f9043b28eb4 100644 --- a/packages/charts/src/chart_types/heatmap/renderer/dom/highlighter_brush.tsx +++ b/packages/charts/src/chart_types/heatmap/renderer/dom/highlighter_brush.tsx @@ -9,11 +9,11 @@ import { connect } from 'react-redux'; import { GlobalChartState } from '../../../../state/chart_state'; +import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; import { computeChartDimensionsSelector } from '../../state/selectors/compute_chart_dimensions'; -import { geometries } from '../../state/selectors/geometries'; +import { getHeatmapGeometries } from '../../state/selectors/geometries'; import { getBrushedHighlightedShapesSelector } from '../../state/selectors/get_brushed_highlighted_shapes'; -import { getHeatmapConfigSelector } from '../../state/selectors/get_heatmap_config'; import { getHighlightedAreaSelector } from '../../state/selectors/get_highlighted_area'; import { DEFAULT_PROPS, HighlighterCellsComponent, HighlighterCellsProps } from './highlighter'; @@ -24,7 +24,7 @@ const brushMapStateToProps = (state: GlobalChartState): HighlighterCellsProps => const { chartId } = state; - const geoms = geometries(state); + const geoms = getHeatmapGeometries(state); const canvasDimension = computeChartDimensionsSelector(state); let dragShape = getBrushedHighlightedShapesSelector(state); @@ -32,7 +32,7 @@ const brushMapStateToProps = (state: GlobalChartState): HighlighterCellsProps => if (highlightedArea) { dragShape = highlightedArea; } - const { brushMask, brushArea } = getHeatmapConfigSelector(state); + const { brushMask, brushArea } = getChartThemeSelector(state).heatmap; return { chartId, diff --git a/packages/charts/src/chart_types/heatmap/specs/heatmap.ts b/packages/charts/src/chart_types/heatmap/specs/heatmap.ts index 108cad94b92..073f319eb82 100644 --- a/packages/charts/src/chart_types/heatmap/specs/heatmap.ts +++ b/packages/charts/src/chart_types/heatmap/specs/heatmap.ts @@ -17,9 +17,8 @@ import { SpecType } from '../../../specs/constants'; import { getConnect, specComponentFactory } from '../../../state/spec_factory'; import { Accessor, AccessorFn } from '../../../utils/accessor'; import { ESCalendarInterval, ESFixedInterval } from '../../../utils/chrono/elasticsearch'; -import { Datum, RecursivePartial } from '../../../utils/common'; -import { config } from '../layout/config/config'; -import { Config } from '../layout/types/config_types'; +import { Datum } from '../../../utils/common'; +import { Cell } from '../layout/types/viewmodel_types'; import { X_SCALE_DEFAULT } from './scale_defaults'; const defaultProps = { @@ -33,7 +32,11 @@ const defaultProps = { valueFormatter: (value: number) => `${value}`, xSortPredicate: Predicate.AlphaAsc, ySortPredicate: Predicate.AlphaAsc, - config, + timeZone: 'UTC', + xAxisLabelName: 'X Value', + xAxisLabelFormatter: String, + yAxisLabelName: 'Y Value', + yAxisLabelFormatter: String, }; /** @public */ @@ -59,6 +62,12 @@ export interface HeatmapBandsColorScale { labelFormatter?: (start: number, end: number) => string; } +/** @public */ +export type HeatmapBrushEvent = { + cells: Cell[]; + x: (string | number)[]; + y: (string | number)[]; +}; /** @public */ export interface TimeScale { type: typeof ScaleType.Time; @@ -92,15 +101,21 @@ export interface HeatmapSpec extends Spec { xSortPredicate: Predicate; ySortPredicate: Predicate; xScale: RasterTimeScale | OrdinalScale | LinearScale; - config: RecursivePartial; highlightedData?: { x: Array; y: Array }; name?: string; + timeZone: string; + onBrushEnd?: (brushArea: HeatmapBrushEvent) => void; + xAxisLabelName: string; + xAxisLabelFormatter: (value: string | number) => string; + yAxisLabelName: string; + yAxisLabelFormatter: (value: string | number) => string; } +type SpecRequiredProps = Pick; +type SpecOptionalProps = Partial>; + /** @alpha */ -export const Heatmap: React.FunctionComponent< - Pick & Partial> -> = getConnect()( +export const Heatmap: React.FunctionComponent = getConnect()( specComponentFactory< HeatmapSpec, | 'xAccessor' @@ -110,7 +125,11 @@ export const Heatmap: React.FunctionComponent< | 'ySortPredicate' | 'xSortPredicate' | 'valueFormatter' - | 'config' | 'xScale' + | 'timeZone' + | 'xAxisLabelName' + | 'xAxisLabelFormatter' + | 'yAxisLabelName' + | 'yAxisLabelFormatter' >(defaultProps), ); 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 4da0ffb4c29..ca59652cf30 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 @@ -11,13 +11,13 @@ import { max as d3Max } from 'd3-array'; import { Box, measureText } from '../../../../common/text_utils'; 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 { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { Position } from '../../../../utils/common'; import { Dimensions } from '../../../../utils/dimensions'; import { HeatmapCellDatum } from '../../layout/viewmodel/viewmodel'; import { getGridHeightParamsSelector } from './get_grid_full_height'; -import { getHeatmapConfigSelector } from './get_heatmap_config'; import { getHeatmapTableSelector } from './get_heatmap_table'; import { getXAxisRightOverflow } from './get_x_axis_right_overflow'; @@ -41,7 +41,7 @@ export const computeChartDimensionsSelector = createCustomCachedSelector( getParentDimension, getLegendSizeSelector, getHeatmapTableSelector, - getHeatmapConfigSelector, + getChartThemeSelector, getXAxisRightOverflow, getGridHeightParamsSelector, getSettingsSpecSelector, @@ -50,14 +50,14 @@ export const computeChartDimensionsSelector = createCustomCachedSelector( chartContainerDimensions, legendSize, heatmapTable, - config, + { heatmap }, rightOverflow, { height }, { showLegend, legendPosition }, ): Dimensions => { let { width, left } = chartContainerDimensions; const { top } = chartContainerDimensions; - const { padding } = config.yAxisLabel; + const { padding } = heatmap.yAxisLabel; const textMeasurer = document.createElement('canvas'); const textMeasurerCtx = textMeasurer.getContext('2d'); @@ -66,23 +66,23 @@ export const computeChartDimensionsSelector = createCustomCachedSelector( const totalHorizontalPadding = typeof padding === 'number' ? padding * 2 : (padding.left ?? 0) + (padding.right ?? 0); - if (config.yAxisLabel.visible) { + if (heatmap.yAxisLabel.visible) { // measure the text width of all rows values to get the grid area width const boxedYValues = heatmapTable.yValues.map((value) => { return { text: String(value), value, isValue: false, - ...config.yAxisLabel, + ...heatmap.yAxisLabel, }; }); - const measuredYValues = textMeasure(config.yAxisLabel.fontSize, boxedYValues); + const measuredYValues = textMeasure(heatmap.yAxisLabel.fontSize, boxedYValues); let yColumnWidth: number = d3Max(measuredYValues, ({ width }) => width) ?? 0; - if (typeof config.yAxisLabel.width === 'number') { - yColumnWidth = config.yAxisLabel.width; - } else if (typeof config.yAxisLabel.width === 'object' && yColumnWidth > config.yAxisLabel.width.max) { - yColumnWidth = config.yAxisLabel.width.max; + if (typeof heatmap.yAxisLabel.width === 'number') { + yColumnWidth = heatmap.yAxisLabel.width; + } else if (typeof heatmap.yAxisLabel.width === 'object' && yColumnWidth > heatmap.yAxisLabel.width.max) { + yColumnWidth = heatmap.yAxisLabel.width.max; } width -= yColumnWidth + rightOverflow + totalHorizontalPadding; diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/geometries.ts b/packages/charts/src/chart_types/heatmap/state/selectors/geometries.ts index 39e3191b6dc..ab1e31bc71a 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/geometries.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/geometries.ts @@ -9,7 +9,6 @@ import { GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; -import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; import { getColorScale } from './get_color_scale'; @@ -21,11 +20,10 @@ import { render } from './scenegraph'; const getDeselectedSeriesSelector = (state: GlobalChartState) => state.interactions.deselectedDataSeries; /** @internal */ -export const geometries = createCustomCachedSelector( +export const getHeatmapGeometries = createCustomCachedSelector( [ getHeatmapSpecSelector, computeChartDimensionsSelector, - getSettingsSpecSelector, getHeatmapTableSelector, getColorScale, getDeselectedSeriesSelector, @@ -35,7 +33,6 @@ export const geometries = createCustomCachedSelector( ( heatmapSpec, chartDimensions, - settingSpec, heatmapTable, { bands, scale: colorScale }, deselectedSeries, @@ -56,16 +53,7 @@ export const geometries = createCustomCachedSelector( .map(({ start, end }) => [start, end]); return heatmapSpec - ? render( - heatmapSpec, - settingSpec, - chartDimensions, - heatmapTable, - colorScale, - bandsToHide, - gridHeightParams, - theme, - ) + ? render(heatmapSpec, chartDimensions, heatmapTable, colorScale, bandsToHide, gridHeightParams, theme) : nullShapeViewModel(); }, ); diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_brushed_highlighted_shapes.test.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_brushed_highlighted_shapes.test.ts index 9e4f4f2103e..3ce3148db90 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_brushed_highlighted_shapes.test.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_brushed_highlighted_shapes.test.ts @@ -18,14 +18,31 @@ import { createOnBrushEndCaller } from './on_brush_end_caller'; describe('Categorical heatmap brush', () => { let store: Store; - let onBrushEndMock = jest.fn(); + const onBrushEndMock = jest.fn(); beforeEach(() => { store = MockStore.default({ width: 300, height: 300, top: 0, left: 0 }, 'chartId'); - onBrushEndMock = jest.fn(); MockStore.addSpecs( [ MockGlobalSpec.settingsNoMargins({ + theme: { + heatmap: { + grid: { + cellHeight: { + max: 'fill', + }, + cellWidth: { + max: 'fill', + }, + }, + xAxisLabel: { + visible: false, + }, + yAxisLabel: { + visible: false, + }, + }, + }, onBrushEnd: onBrushEndMock, }), MockSeriesSpec.heatmap({ @@ -41,23 +58,6 @@ describe('Categorical heatmap brush', () => { { x: 'b', y: 'yc', value: 8 }, { x: 'c', y: 'yc', value: 9 }, ], - config: { - grid: { - cellHeight: { - max: 'fill', - }, - cellWidth: { - max: 'fill', - }, - }, - xAxisLabel: { - visible: false, - }, - yAxisLabel: { - visible: false, - }, - margin: { top: 0, bottom: 0, left: 0, right: 0 }, - }, }), ], store, @@ -88,6 +88,24 @@ describe('Temporal heatmap brush', () => { MockStore.addSpecs( [ MockGlobalSpec.settingsNoMargins({ + theme: { + heatmap: { + grid: { + cellHeight: { + max: 'fill', + }, + cellWidth: { + max: 'fill', + }, + }, + xAxisLabel: { + visible: false, + }, + yAxisLabel: { + visible: false, + }, + }, + }, onBrushEnd: onBrushEndMock, }), MockSeriesSpec.heatmap({ @@ -103,24 +121,7 @@ describe('Temporal heatmap brush', () => { { x: start.plus({ days: 1 }).toMillis(), y: 'yc', value: 8 }, { x: start.plus({ days: 2 }).toMillis(), y: 'yc', value: 9 }, ], - config: { - grid: { - cellHeight: { - max: 'fill', - }, - cellWidth: { - max: 'fill', - }, - }, - xAxisLabel: { - visible: false, - }, - yAxisLabel: { - visible: false, - }, - margin: { top: 0, bottom: 0, left: 0, right: 0 }, - timeZone: 'Europe/Rome', - }, + timeZone: 'Europe/Rome', }), ], store, diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_brushed_highlighted_shapes.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_brushed_highlighted_shapes.ts index f814ab03d12..8b58e01aa4a 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_brushed_highlighted_shapes.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_brushed_highlighted_shapes.ts @@ -9,7 +9,7 @@ import { GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { DragShape } from '../../layout/types/viewmodel_types'; -import { geometries } from './geometries'; +import { getHeatmapGeometries } from './geometries'; function getCurrentPointerStates(state: GlobalChartState) { return state.interactions.pointer; @@ -17,7 +17,7 @@ function getCurrentPointerStates(state: GlobalChartState) { /** @internal */ export const getBrushedHighlightedShapesSelector = createCustomCachedSelector( - [geometries, getCurrentPointerStates], + [getHeatmapGeometries, getCurrentPointerStates], (geoms, pointerStates): DragShape => { if (!pointerStates.dragging || !pointerStates.down) { return null; diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_debug_state.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_debug_state.ts index 1a2a9225ac4..861d40625b3 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_debug_state.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_debug_state.ts @@ -6,13 +6,22 @@ * Side Public License, v 1. */ +/* + * 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 { RGBATupleToString } from '../../../../common/color_library_wrappers'; import { LegendItem } from '../../../../common/legend'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { DebugState, DebugStateLegend } from '../../../../state/types'; import { Position } from '../../../../utils/common'; +import { getChartThemeSelector } from './../../../../state/selectors/get_chart_theme'; import { computeLegendSelector } from './compute_legend'; -import { geometries } from './geometries'; +import { getHeatmapGeometries } from './geometries'; import { getHighlightedAreaSelector, getHighlightedDataSelector } from './get_highlighted_area'; /** @@ -20,8 +29,14 @@ import { getHighlightedAreaSelector, getHighlightedDataSelector } from './get_hi * @internal */ export const getDebugStateSelector = createCustomCachedSelector( - [geometries, computeLegendSelector, getHighlightedAreaSelector, getHighlightedDataSelector], - (geoms, legend, pickedArea, highlightedData): DebugState => { + [ + getHeatmapGeometries, + computeLegendSelector, + getHighlightedAreaSelector, + getHighlightedDataSelector, + getChartThemeSelector, + ], + (geoms, legend, pickedArea, highlightedData, { heatmap }): DebugState => { return { // Common debug state legend: getLegendState(legend), @@ -55,7 +70,7 @@ export const getDebugStateSelector = createCustomCachedSelector( fill: RGBATupleToString(cell.fill.color), formatted: cell.formatted, value: cell.value, - valueShown: geoms.config.cell.label.visible && Number.isFinite(geoms.heatmapViewModel.cellFontSize(cell)), + valueShown: heatmap.cell.label.visible && Number.isFinite(geoms.heatmapViewModel.cellFontSize(cell)), })), selection: { area: pickedArea, diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_grid_full_height.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_grid_full_height.ts index 7e629ed6fc6..a5e7fd947ce 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_grid_full_height.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_grid_full_height.ts @@ -8,11 +8,11 @@ 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 { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { isHorizontalLegend } from '../../../../utils/legend'; -import { Config } from '../../layout/types/config_types'; -import { getHeatmapConfigSelector } from './get_heatmap_config'; +import { HeatmapStyle } from '../../../../utils/themes/theme'; import { getHeatmapTableSelector } from './get_heatmap_table'; /** @internal */ @@ -25,18 +25,18 @@ const getParentDimension = (state: GlobalChartState) => state.parentDimensions; /** @internal */ export const getGridHeightParamsSelector = createCustomCachedSelector( - [ - getLegendSizeSelector, - getSettingsSpecSelector, - getParentDimension, - getHeatmapConfigSelector, - getHeatmapTableSelector, - ], + [getLegendSizeSelector, getSettingsSpecSelector, getParentDimension, getChartThemeSelector, getHeatmapTableSelector], ( legendSize, { showLegend }, { height: containerHeight }, - { xAxisLabel: { padding, visible, fontSize }, grid, maxLegendHeight }, + { + heatmap: { + xAxisLabel: { padding, visible, fontSize }, + grid, + maxLegendHeight, + }, + }, { yValues }, ): GridHeightParams => { const xAxisHeight = visible ? fontSize : 0; @@ -63,7 +63,7 @@ export const getGridHeightParamsSelector = createCustomCachedSelector( }, ); -function getGridCellHeight(yValues: Array, grid: Config['grid'], height: number): number { +function getGridCellHeight(yValues: Array, grid: HeatmapStyle['grid'], height: number): number { if (yValues.length === 0) { return height; } diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_config.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_config.ts deleted file mode 100644 index e1f4be590ca..00000000000 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_config.ts +++ /dev/null @@ -1,19 +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 { createCustomCachedSelector } from '../../../../state/create_selector'; -import { mergePartial } from '../../../../utils/common'; -import { config as defaultConfig } from '../../layout/config/config'; -import { Config } from '../../layout/types/config_types'; -import { getHeatmapSpecSelector } from './get_heatmap_spec'; - -/** @internal */ -export const getHeatmapConfigSelector = createCustomCachedSelector( - [getHeatmapSpecSelector], - (spec): Config => mergePartial(defaultConfig, spec.config), -); diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_container_size.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_container_size.ts index dfb79372720..5a9d3af837f 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_container_size.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_container_size.ts @@ -8,11 +8,11 @@ import { GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; +import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { getLegendConfigSelector } from '../../../../state/selectors/get_legend_config_selector'; import { getLegendSizeSelector } from '../../../../state/selectors/get_legend_size'; import { LayoutDirection } from '../../../../utils/common'; import { Dimensions } from '../../../../utils/dimensions'; -import { getHeatmapConfigSelector } from './get_heatmap_config'; const getParentDimension = (state: GlobalChartState) => state.parentDimensions; @@ -21,8 +21,8 @@ const getParentDimension = (state: GlobalChartState) => state.parentDimensions; * @internal */ export const getHeatmapContainerSizeSelector = createCustomCachedSelector( - [getParentDimension, getLegendSizeSelector, getHeatmapConfigSelector, getLegendConfigSelector], - (parentDimensions, legendSize, { maxLegendHeight }, { showLegend, legendPosition }): Dimensions => { + [getParentDimension, getLegendSizeSelector, getChartThemeSelector, getLegendConfigSelector], + (parentDimensions, legendSize, { heatmap: { maxLegendHeight } }, { showLegend, legendPosition }): Dimensions => { if (!showLegend || legendPosition.floating) { return parentDimensions; } diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_spec.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_spec.ts index 84885eed169..205c3701f87 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_spec.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_spec.ts @@ -14,7 +14,10 @@ import { getSpecsFromStore } from '../../../../state/utils'; import { HeatmapSpec } from '../../specs'; /** @internal */ -export const getHeatmapSpecSelector = createCustomCachedSelector([getSpecs], (specs) => { - const spec = getSpecsFromStore(specs, ChartType.Heatmap, SpecType.Series); - return spec[0]; -}); +export const getHeatmapSpecSelector = createCustomCachedSelector( + [getSpecs], + (specs): HeatmapSpec => { + const spec = getSpecsFromStore(specs, ChartType.Heatmap, SpecType.Series); + return spec[0]; + }, +); diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts index a22d5a1d976..7a1a01b1970 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_heatmap_table.ts @@ -16,7 +16,6 @@ import { getAccessorValue } from '../../../../utils/accessor'; import { addIntervalToTime, timeRange } from '../../../../utils/chrono/elasticsearch'; import { isFiniteNumber } from '../../../../utils/common'; import { HeatmapTable } from './compute_chart_dimensions'; -import { getHeatmapConfigSelector } from './get_heatmap_config'; import { getHeatmapSpecSelector } from './get_heatmap_spec'; /** @@ -24,11 +23,10 @@ import { getHeatmapSpecSelector } from './get_heatmap_spec'; * @internal */ export const getHeatmapTableSelector = createCustomCachedSelector( - [getHeatmapSpecSelector, getSettingsSpecSelector, getHeatmapConfigSelector], + [getHeatmapSpecSelector, getSettingsSpecSelector], ( - { data, valueAccessor, xAccessor, yAccessor, xSortPredicate, ySortPredicate, xScale }, + { data, valueAccessor, xAccessor, yAccessor, xSortPredicate, ySortPredicate, xScale, timeZone }, { xDomain }, - { timeZone }, ): HeatmapTable => { const resultData = data.reduce( (acc, curr, index) => { diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_highlighted_area.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_highlighted_area.ts index dd168175643..db154964b47 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_highlighted_area.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_highlighted_area.ts @@ -7,7 +7,7 @@ */ import { createCustomCachedSelector } from '../../../../state/create_selector'; -import { geometries } from './geometries'; +import { getHeatmapGeometries } from './geometries'; import { getHeatmapSpecSelector } from './get_heatmap_spec'; import { isBrushingSelector } from './is_brushing'; @@ -29,7 +29,7 @@ export const getHighlightedDataSelector = createCustomCachedSelector( * @internal */ export const getHighlightedAreaSelector = createCustomCachedSelector( - [geometries, getHeatmapSpecSelector, isBrushingSelector], + [getHeatmapGeometries, getHeatmapSpecSelector, isBrushingSelector], (geoms, spec, isBrushing) => { if (!spec.highlightedData || isBrushing) { return null; diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_picked_cells.test.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_picked_cells.test.ts index e70a0d2756d..ede6dfcef69 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_picked_cells.test.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_picked_cells.test.ts @@ -24,7 +24,28 @@ describe('Heatmap picked cells', () => { onBrushEndMock = jest.fn(); MockStore.addSpecs( [ - MockGlobalSpec.settingsNoMargins({ onBrushEnd: onBrushEndMock }), + MockGlobalSpec.settingsNoMargins({ + onBrushEnd: onBrushEndMock, + theme: { + heatmap: { + brushTool: { visible: true }, + grid: { + cellHeight: { + max: 'fill', + }, + cellWidth: { + max: 'fill', + }, + }, + xAxisLabel: { + visible: true, + }, + yAxisLabel: { + visible: true, + }, + }, + }, + }), MockSeriesSpec.heatmap({ xScale: { type: ScaleType.Ordinal }, data: [ @@ -38,24 +59,6 @@ describe('Heatmap picked cells', () => { { x: 'b', y: 'yc', value: 8 }, { x: 'c', y: 'yc', value: 9 }, ], - config: { - brushTool: { visible: true }, - grid: { - cellHeight: { - max: 'fill', - }, - cellWidth: { - max: 'fill', - }, - }, - xAxisLabel: { - visible: true, - }, - yAxisLabel: { - visible: true, - }, - margin: { top: 0, bottom: 0, left: 0, right: 0 }, - }, }), ], store, diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_picked_cells.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_picked_cells.ts index 7013ff2bcc3..b0dc57bcf87 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_picked_cells.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_picked_cells.ts @@ -10,12 +10,12 @@ import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getLastDragSelector } from '../../../../state/selectors/get_last_drag'; import { PickDragFunction } from '../../layout/types/viewmodel_types'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; -import { geometries } from './geometries'; +import { getHeatmapGeometries } from './geometries'; import { getGridHeightParamsSelector } from './get_grid_full_height'; /** @internal */ export const getPickedCells = createCustomCachedSelector( - [geometries, getLastDragSelector, computeChartDimensionsSelector, getGridHeightParamsSelector], + [getHeatmapGeometries, getLastDragSelector, computeChartDimensionsSelector, getGridHeightParamsSelector], (geoms, dragState, canvasDimensions, gridParams): ReturnType | null => { if (!dragState) { return null; 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 index b214694c7e6..8502da52339 100644 --- 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 @@ -9,8 +9,8 @@ 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 { getHeatmapConfigSelector } from './get_heatmap_config'; import { getHeatmapSpecSelector } from './get_heatmap_spec'; import { getHeatmapTableSelector } from './get_heatmap_table'; @@ -19,8 +19,16 @@ import { getHeatmapTableSelector } from './get_heatmap_table'; * Gets color scale based on specification and values range. */ export const getXAxisRightOverflow = createCustomCachedSelector( - [getHeatmapSpecSelector, getHeatmapConfigSelector, getHeatmapTableSelector], - ({ xScale }, { timeZone, xAxisLabel: { fontSize, fontFamily, padding, formatter, width } }, { xNumericExtent }) => { + [getHeatmapSpecSelector, getChartThemeSelector, getHeatmapTableSelector], + ( + { xScale, timeZone, xAxisLabelFormatter }, + { + heatmap: { + xAxisLabel: { fontSize, fontFamily, padding, width }, + }, + }, + { xNumericExtent }, + ) => { return xScale.type !== ScaleType.Time ? 0 : typeof width === 'number' @@ -31,7 +39,10 @@ export const getXAxisRightOverflow = createCustomCachedSelector( { timeZone: xScale.type === ScaleType.Time ? timeZone : undefined }, ) .ticks() - .reduce((max, n) => Math.max(max, measure(formatter(n), padding, fontSize, fontFamily).width + padding), 0); + .reduce( + (max, n) => Math.max(max, measure(xAxisLabelFormatter(n), padding, fontSize, fontFamily).width + padding), + 0, + ); }) / 2; }, ); diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/is_brush_available.ts b/packages/charts/src/chart_types/heatmap/state/selectors/is_brush_available.ts index 8f1d13dd178..9d73912e836 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/is_brush_available.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/is_brush_available.ts @@ -7,21 +7,19 @@ */ import { createCustomCachedSelector } from '../../../../state/create_selector'; +import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { getHeatmapConfigSelector } from './get_heatmap_config'; /** * The brush is available only if a onBrushEnd listener is configured * @internal */ export const isBrushAvailableSelector = createCustomCachedSelector( - [getHeatmapConfigSelector, getSettingsSpecSelector], - (config, { onBrushEnd }): boolean => { - return Boolean(onBrushEnd) && config.brushTool.visible; - }, + [getChartThemeSelector, getSettingsSpecSelector], + ({ heatmap: { brushTool } }, { onBrushEnd }): boolean => Boolean(onBrushEnd) && brushTool.visible, ); /** @internal */ -export const isBrushEndProvided = createCustomCachedSelector([getSettingsSpecSelector], ({ onBrushEnd }): boolean => { - return Boolean(onBrushEnd); -}); +export const isBrushEndProvided = createCustomCachedSelector([getSettingsSpecSelector], ({ onBrushEnd }): boolean => + Boolean(onBrushEnd), +); diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/on_brush_end_caller.ts b/packages/charts/src/chart_types/heatmap/state/selectors/on_brush_end_caller.ts index 2c56fdbad8c..31b8d5a2992 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/on_brush_end_caller.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/on_brush_end_caller.ts @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -import { Selector } from 'reselect'; +import { OutputSelector } from 'reselect'; import { ChartType } from '../../..'; -import { GlobalChartState } from '../../../../state/chart_state'; +import { HeatmapBrushEvent, SettingsSpec } from '../../../../specs/settings'; +import { DragState, GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getLastDragSelector } from '../../../../state/selectors/get_last_drag'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { DragCheckProps, hasDragged } from '../../../../utils/events'; +import { HeatmapSpec } from '../../specs'; import { getPickedCells } from './get_picked_cells'; import { getSpecOrNull } from './heatmap_spec'; import { isBrushEndProvided } from './is_brush_available'; @@ -26,8 +28,13 @@ import { isBrushEndProvided } from './is_brush_available'; */ export function createOnBrushEndCaller(): (state: GlobalChartState) => void { let prevProps: DragCheckProps | null = null; - let selector: Selector | null = null; - return (state: GlobalChartState) => { + let selector: OutputSelector< + GlobalChartState, + void, + (res1: DragState | null, res2: HeatmapSpec | null, res3: SettingsSpec, res4: HeatmapBrushEvent | null) => void + > | null = null; + + return (state) => { if (selector === null && state.chartType === ChartType.Heatmap) { if (!isBrushEndProvided(state)) { selector = null; diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/picked_shapes.ts b/packages/charts/src/chart_types/heatmap/state/selectors/picked_shapes.ts index cc64cf3d9ee..f4fb69a9a55 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/picked_shapes.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/picked_shapes.ts @@ -9,7 +9,7 @@ import { GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { Cell, TextBox } from '../../layout/types/viewmodel_types'; -import { geometries } from './geometries'; +import { getHeatmapGeometries } from './geometries'; import { getGridHeightParamsSelector } from './get_grid_full_height'; function getCurrentPointerPosition(state: GlobalChartState) { @@ -18,7 +18,7 @@ function getCurrentPointerPosition(state: GlobalChartState) { /** @internal */ export const getPickedShapes = createCustomCachedSelector( - [geometries, getCurrentPointerPosition, getGridHeightParamsSelector], + [getHeatmapGeometries, getCurrentPointerPosition, getGridHeightParamsSelector], (geoms, pointerPosition, gridParams): Cell[] | TextBox => { const picker = geoms.pickQuads; const { x, y } = pointerPosition; diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/scenegraph.ts b/packages/charts/src/chart_types/heatmap/state/selectors/scenegraph.ts index 5ff7825f382..3a2e3cf682c 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/scenegraph.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/scenegraph.ts @@ -7,12 +7,8 @@ */ import { measureText } from '../../../../common/text_utils'; -import { SettingsSpec } from '../../../../specs'; -import { RecursivePartial, mergePartial } from '../../../../utils/common'; import { Dimensions } from '../../../../utils/dimensions'; import { Theme } from '../../../../utils/themes/theme'; -import { config as defaultConfig } from '../../layout/config/config'; -import { Config } from '../../layout/types/config_types'; import { ShapeViewModel, nullShapeViewModel } from '../../layout/types/viewmodel_types'; import { shapeViewModel } from '../../layout/viewmodel/viewmodel'; import { HeatmapSpec } from '../../specs'; @@ -23,7 +19,6 @@ import { GridHeightParams } from './get_grid_full_height'; /** @internal */ export function render( spec: HeatmapSpec, - settingsSpec: SettingsSpec, chartDimensions: Dimensions, heatmapTable: HeatmapTable, colorScale: ColorScale, @@ -36,20 +31,14 @@ export function render( if (!textMeasurerCtx) { return nullShapeViewModel(); } - const { width, height } = chartDimensions; - const { config: specConfig } = spec; - const partialConfig: RecursivePartial = { ...specConfig, width, height }; - const config = mergePartial(defaultConfig, partialConfig); return shapeViewModel( measureText(textMeasurerCtx), spec, - config, - settingsSpec, + theme, chartDimensions, heatmapTable, colorScale, bandsToHide, gridHeightParams, - theme, ); } diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/tooltip.ts b/packages/charts/src/chart_types/heatmap/state/selectors/tooltip.ts index d32930169e9..3c33f700304 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/tooltip.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/tooltip.ts @@ -10,7 +10,6 @@ import { RGBATupleToString } from '../../../../common/color_library_wrappers'; import { Colors } from '../../../../common/colors'; import { TooltipInfo } from '../../../../components/tooltip/types'; import { createCustomCachedSelector } from '../../../../state/create_selector'; -import { getHeatmapConfigSelector } from './get_heatmap_config'; import { getSpecOrNull } from './heatmap_spec'; import { getPickedShapes } from './picked_shapes'; @@ -21,8 +20,8 @@ const EMPTY_TOOLTIP = Object.freeze({ /** @internal */ export const getTooltipInfoSelector = createCustomCachedSelector( - [getSpecOrNull, getHeatmapConfigSelector, getPickedShapes], - (spec, config, pickedShapes): TooltipInfo => { + [getSpecOrNull, getPickedShapes], + (spec, pickedShapes): TooltipInfo => { if (!spec) { return EMPTY_TOOLTIP; } @@ -38,7 +37,7 @@ export const getTooltipInfoSelector = createCustomCachedSelector( .forEach((shape) => { // X-axis value tooltipInfo.values.push({ - label: config.xAxisLabel.name, + label: spec.xAxisLabelName, color: Colors.Transparent.keyword, isHighlighted: false, isVisible: true, @@ -47,13 +46,13 @@ export const getTooltipInfoSelector = createCustomCachedSelector( key: spec.id, }, value: `${shape.datum.x}`, - formattedValue: config.xAxisLabel.formatter(shape.datum.x), + formattedValue: spec.xAxisLabelFormatter(shape.datum.x), datum: shape.datum, }); // Y-axis value tooltipInfo.values.push({ - label: config.yAxisLabel.name, + label: spec.yAxisLabelName, color: Colors.Transparent.keyword, isHighlighted: false, isVisible: true, @@ -62,7 +61,7 @@ export const getTooltipInfoSelector = createCustomCachedSelector( key: spec.id, }, value: `${shape.datum.y}`, - formattedValue: config.yAxisLabel.formatter(shape.datum.y), + formattedValue: spec.yAxisLabelFormatter(shape.datum.y), datum: shape.datum, }); diff --git a/packages/charts/src/chart_types/wordcloud/layout/config/config.ts b/packages/charts/src/chart_types/wordcloud/layout/config/config.ts deleted file mode 100644 index a039866bae4..00000000000 --- a/packages/charts/src/chart_types/wordcloud/layout/config/config.ts +++ /dev/null @@ -1,42 +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 { ConfigItem, configMap } from '../../../../common/config_objects'; -import { Config } from '../types/config_types'; - -/** @internal */ -export const configMetadata: Record = { - // shape geometry - width: { dflt: 300, min: 0, max: 1024, type: 'number', reconfigurable: false }, - height: { dflt: 150, min: 0, max: 1024, type: 'number', reconfigurable: false }, - margin: { - type: 'group', - values: { - left: { dflt: 0, min: -0.25, max: 0.25, type: 'number' }, - right: { dflt: 0, min: -0.25, max: 0.25, type: 'number' }, - top: { dflt: 0, min: -0.25, max: 0.25, type: 'number' }, - bottom: { dflt: 0, min: -0.25, max: 0.25, type: 'number' }, - }, - }, - - // general text config - fontFamily: { - dflt: 'Impact', - type: 'string', - }, - - // fill text config - minFontSize: { dflt: 10, min: 10, max: 50, type: 'number', reconfigurable: true }, - maxFontSize: { dflt: 70, min: 15, max: 150, type: 'number', reconfigurable: true }, - - backgroundColor: { dflt: '#ffffff', type: 'color' }, - sectorLineWidth: { dflt: 1, min: 0, max: 4, type: 'number' }, -}; - -/** @internal */ -export const config: Config = configMap((item: ConfigItem) => item.dflt, configMetadata); diff --git a/packages/charts/src/chart_types/wordcloud/layout/types/config_types.ts b/packages/charts/src/chart_types/wordcloud/layout/types/config_types.ts deleted file mode 100644 index 5092db51871..00000000000 --- a/packages/charts/src/chart_types/wordcloud/layout/types/config_types.ts +++ /dev/null @@ -1,26 +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 { Pixels, SizeRatio } from '../../../../common/geometry'; -import { FontFamily } from '../../../../common/text_utils'; - -// todo switch to `io-ts` style, generic way of combining static and runtime type info -/** @public */ -export interface Config { - // shape geometry - width: number; - height: number; - margin: { left: SizeRatio; right: SizeRatio; top: SizeRatio; bottom: SizeRatio }; - - // general text config - fontFamily: FontFamily; - - // fill text config - minFontSize: Pixels; - maxFontSize: Pixels; -} diff --git a/packages/charts/src/chart_types/wordcloud/layout/types/viewmodel_types.ts b/packages/charts/src/chart_types/wordcloud/layout/types/viewmodel_types.ts index d6f49ce4880..ab2f9825494 100644 --- a/packages/charts/src/chart_types/wordcloud/layout/types/viewmodel_types.ts +++ b/packages/charts/src/chart_types/wordcloud/layout/types/viewmodel_types.ts @@ -6,13 +6,12 @@ * Side Public License, v 1. */ +import { Word as D3Word } from 'd3-cloud'; import { $Values as Values } from 'utility-types'; import { Color } from '../../../../common/colors'; -import { Pixels, PointObject, Rectangle } from '../../../../common/geometry'; +import { Pixels, PointObject } from '../../../../common/geometry'; import { FontStyle } from '../../../../common/text_utils'; -import { config } from '../config/config'; -import { Config } from './config_types'; /** @public */ export interface WordModel { @@ -32,49 +31,26 @@ export const WeightFn = Object.freeze({ /** @public */ export type WeightFn = Values; -/** @internal */ -export interface Word extends Rectangle { +/** + * Word properties extends `D3Word` except for explicitly defined values. + * + * `D3Word` values are added datum via d3TagCloud, but may be undefined + * @internal + */ +export interface Word extends D3Word { + datum: WordModel; + text: string; color: string; - font: string; fontFamily: string; - fontWeight: number; - hasText: boolean; - height: number; - padding: number; - rotate: number; - size: number; style: string; - text: string; - weight: number; - x: number; - xoff: number; - y: number; - yoff: number; - datum: WordModel; -} - -/** @public */ -export interface Configs { - count: number; - endAngle: number; - exponent: number; - fontFamily: string; - fontStyle: FontStyle; fontWeight: number; - height: number; - maxFontSize: number; - minFontSize: number; - padding: number; - spiral: string; - startAngle: number; - weightFn: WeightFn; - width: number; + size: number; } /** @public */ export type OutOfRoomCallback = (wordCount: number, renderedWordCount: number, renderedWords: string[]) => void; -/** @internal */ +/** @public */ export interface WordcloudViewModel { startAngle: number; endAngle: number; @@ -90,7 +66,6 @@ export interface WordcloudViewModel { data: WordModel[]; weightFn: WeightFn; outOfRoomCallback: OutOfRoomCallback; - // specType: string; } /** @internal */ @@ -105,7 +80,6 @@ export type PickFunction = (x: Pixels, y: Pixels) => Array; /** @internal */ export type ShapeViewModel = { - config: Config; wordcloudViewModel: WordcloudViewModel; chartCenter: PointObject; pickQuads: PickFunction; @@ -141,10 +115,9 @@ export const nullWordcloudViewModel: WordcloudViewModel = { }; /** @internal */ -export const nullShapeViewModel = (specifiedConfig?: Config, chartCenter?: PointObject): ShapeViewModel => ({ - config: specifiedConfig || config, +export const nullShapeViewModel = (): ShapeViewModel => ({ wordcloudViewModel: nullWordcloudViewModel, - chartCenter: chartCenter || { x: 0, y: 0 }, + chartCenter: { x: 0, y: 0 }, pickQuads: () => [], specId: 'empty', }); diff --git a/packages/charts/src/chart_types/wordcloud/layout/viewmodel/viewmodel.ts b/packages/charts/src/chart_types/wordcloud/layout/viewmodel/viewmodel.ts index a22047095d2..1dffefb454a 100644 --- a/packages/charts/src/chart_types/wordcloud/layout/viewmodel/viewmodel.ts +++ b/packages/charts/src/chart_types/wordcloud/layout/viewmodel/viewmodel.ts @@ -6,16 +6,17 @@ * Side Public License, v 1. */ +import { Dimensions } from '../../../../utils/dimensions'; +import { Theme } from '../../../../utils/themes/theme'; import { WordcloudSpec } from '../../specs'; -import { Config } from '../types/config_types'; import { WordcloudViewModel, PickFunction, ShapeViewModel } from '../types/viewmodel_types'; /** @internal */ -export function shapeViewModel(spec: WordcloudSpec, config: Config): ShapeViewModel { - const { width, height, margin } = config; - - const innerWidth = width * (1 - Math.min(1, margin.left + margin.right)); - const innerHeight = height * (1 - Math.min(1, margin.top + margin.bottom)); +export function shapeViewModel(spec: WordcloudSpec, theme: Theme, chartDimensions: Dimensions): ShapeViewModel { + const { width, height } = chartDimensions; + const { chartMargins: margin } = theme; + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; const chartCenter = { x: width * margin.left + innerWidth / 2, @@ -64,7 +65,6 @@ export function shapeViewModel(spec: WordcloudSpec, config: Config): ShapeViewMo // combined viewModel return { - config, chartCenter, wordcloudViewModel, pickQuads, diff --git a/packages/charts/src/chart_types/wordcloud/renderer/svg/connected_component.tsx b/packages/charts/src/chart_types/wordcloud/renderer/svg/connected_component.tsx index 742fbe2985e..2eec29fa655 100644 --- a/packages/charts/src/chart_types/wordcloud/renderer/svg/connected_component.tsx +++ b/packages/charts/src/chart_types/wordcloud/renderer/svg/connected_component.tsx @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -// @ts-ignore - remove in workcloud refactor import d3TagCloud from 'd3-cloud'; import React from 'react'; import { connect } from 'react-redux'; @@ -23,14 +22,10 @@ import { } from '../../../../state/selectors/get_accessibility_config'; import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { Dimensions } from '../../../../utils/dimensions'; -import { Configs, Datum, nullShapeViewModel, ShapeViewModel, Word } from '../../layout/types/viewmodel_types'; +import { Size } from '../../../../utils/dimensions'; +import { nullShapeViewModel, ShapeViewModel, Word, WordcloudViewModel } from '../../layout/types/viewmodel_types'; import { geometries } from '../../state/selectors/geometries'; -function seed() { - return 0.5; -} - function getFont(d: Word) { return d.fontFamily; } @@ -43,14 +38,6 @@ function getFontWeight(d: Word) { return d.fontWeight; } -function getWidth(conf: Configs) { - return conf.width ?? 500; -} - -function getHeight(conf: Configs) { - return conf.height ?? 500; -} - function getFontSize(d: Word) { return d.size; } @@ -91,40 +78,42 @@ function log(minFontSize: number, maxFontSize: number, _exponent: number, weight const weightFnLookup = { linear, exponential, log, squareRoot }; -function layoutMaker(config: Configs, data: Datum[]) { - const words = data.map((d) => { - const weightFn = weightFnLookup[config.weightFn]; +function layoutMaker({ data, ...viewModel }: WordcloudViewModel, chartSize: Size) { + const { height, width } = chartSize; + const words = data.map((d) => { + const weightFn = weightFnLookup[viewModel.weightFn]; return { datum: d, text: d.text, color: d.color, - fontFamily: config.fontFamily, - style: config.fontStyle, - fontWeight: config.fontWeight, - size: weightFn(config.minFontSize, config.maxFontSize, config.exponent, d.weight), + fontFamily: viewModel.fontFamily, + style: viewModel.fontStyle, + fontWeight: viewModel.fontWeight, + size: weightFn(viewModel.minFontSize, viewModel.maxFontSize, viewModel.exponent, d.weight), }; }); - return d3TagCloud() - .random(seed) - .size([getWidth(config), getHeight(config)]) + + return d3TagCloud() + .random(() => 0.5) + .size([width, height]) .words(words) - .spiral(config.spiral ?? 'archimedean') - .padding(config.padding ?? 5) - .rotate((d: Word) => getRotation(config.startAngle, config.endAngle, config.count, d.text)) + .spiral(viewModel.spiral ?? 'archimedean') + .padding(viewModel.padding ?? 5) + .rotate((d) => getRotation(viewModel.startAngle, viewModel.endAngle, viewModel.angleCount, d.text)) .font(getFont) .fontStyle(getFontStyle) .fontWeight(getFontWeight) - .fontSize((d: Word) => getFontSize(d)); + .fontSize(getFontSize); } const View = ({ words, - conf, + size: { height, width }, actions: { onElementClick, onElementOver, onElementOut }, specId, }: { words: Word[]; - conf: Configs; + size: Size; actions: { onElementClick?: SettingsSpec['onElementClick']; onElementOver?: SettingsSpec['onElementOver']; @@ -133,8 +122,8 @@ const View = ({ specId: string; }) => { return ( - - + + {words.map((d, i) => { const elements: WordCloudElementEvent[] = [[d.datum, { specId, key: specId }]]; const actions = { @@ -180,7 +169,7 @@ const View = ({ interface ReactiveChartStateProps { initialized: boolean; geometries: ShapeViewModel; - chartContainerDimensions: Dimensions; + chartSize: Size; a11ySettings: A11ySettings; onElementClick?: SettingsSpec['onElementClick']; onElementOver?: SettingsSpec['onElementOver']; @@ -211,40 +200,24 @@ class Component extends React.Component { render() { const { initialized, - chartContainerDimensions: { width, height }, + chartSize, geometries: { wordcloudViewModel, specId }, a11ySettings, onElementClick, onElementOver, onElementOut, } = this.props; - if (!initialized || width === 0 || height === 0) { + + if (!initialized || chartSize.width === 0 || chartSize.height === 0) { return null; } - const conf1: Configs = { - width, - height, - startAngle: wordcloudViewModel.startAngle, - endAngle: wordcloudViewModel.endAngle, - count: wordcloudViewModel.angleCount, - padding: wordcloudViewModel.padding, - fontWeight: wordcloudViewModel.fontWeight, - fontFamily: wordcloudViewModel.fontFamily, - fontStyle: wordcloudViewModel.fontStyle, - minFontSize: wordcloudViewModel.minFontSize, - maxFontSize: wordcloudViewModel.maxFontSize, - spiral: wordcloudViewModel.spiral, - exponent: wordcloudViewModel.exponent, - weightFn: wordcloudViewModel.weightFn, - }; - const layout = layoutMaker(conf1, wordcloudViewModel.data); + const layout = layoutMaker(wordcloudViewModel, chartSize); - let ww; - layout.on('end', (w: Word[]) => (ww = w)).start(); + let renderedWordObjects: Word[] = []; + layout.on('end', (w) => (renderedWordObjects = w)).start(); const wordCount = wordcloudViewModel.data.length; - const renderedWordObjects = (ww as unknown) as Word[]; const renderedWordCount: number = renderedWordObjects.length; const notAllWordsFit = wordCount !== renderedWordCount; if (notAllWordsFit && wordcloudViewModel.outOfRoomCallback instanceof Function) { @@ -259,7 +232,7 @@ class Component extends React.Component {
@@ -280,11 +253,9 @@ const mapDispatchToProps = (dispatch: Dispatch): ReactiveChartDispatchProps => const DEFAULT_PROPS: ReactiveChartStateProps = { initialized: false, geometries: nullShapeViewModel(), - chartContainerDimensions: { + chartSize: { width: 0, height: 0, - left: 0, - top: 0, }, a11ySettings: DEFAULT_A11Y_SETTINGS, }; @@ -296,7 +267,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { return { initialized: true, geometries: geometries(state), - chartContainerDimensions: state.parentDimensions, + chartSize: state.parentDimensions, a11ySettings: getA11ySettingsSelector(state), onElementClick: getSettingsSpecSelector(state).onElementClick, onElementOver: getSettingsSpecSelector(state).onElementOver, diff --git a/packages/charts/src/chart_types/wordcloud/specs/index.ts b/packages/charts/src/chart_types/wordcloud/specs/index.ts index beeb51028d6..42e286c526a 100644 --- a/packages/charts/src/chart_types/wordcloud/specs/index.ts +++ b/packages/charts/src/chart_types/wordcloud/specs/index.ts @@ -9,48 +9,29 @@ import React from 'react'; import { ChartType } from '../..'; -import { FontStyle } from '../../../common/text_utils'; import { Spec } from '../../../specs'; import { SpecType } from '../../../specs/constants'; import { getConnect, specComponentFactory } from '../../../state/spec_factory'; -import { RecursivePartial } from '../../../utils/common'; -import { config } from '../layout/config/config'; import { WordModel, defaultWordcloudSpec, WeightFn, OutOfRoomCallback, - Configs as WordcloudConfigs, + WordcloudViewModel, } from '../layout/types/viewmodel_types'; const defaultProps = { chartType: ChartType.Wordcloud, specType: SpecType.Series, ...defaultWordcloudSpec, - config, }; -export { WordModel, WeightFn, OutOfRoomCallback, WordcloudConfigs }; +export { WordModel, WeightFn, OutOfRoomCallback }; /** @alpha */ -export interface WordcloudSpec extends Spec { +export interface WordcloudSpec extends Spec, WordcloudViewModel { specType: typeof SpecType.Series; chartType: typeof ChartType.Wordcloud; - config: RecursivePartial; - startAngle: number; - endAngle: number; - angleCount: number; - padding: number; - fontWeight: number; - fontFamily: string; - fontStyle: FontStyle; - minFontSize: number; - maxFontSize: number; - spiral: string; - exponent: number; - data: WordModel[]; - weightFn: WeightFn; - outOfRoomCallback: OutOfRoomCallback; } type SpecRequiredProps = Pick; @@ -62,7 +43,6 @@ export const Wordcloud: React.FunctionComponent state.parentDimensions; /** @internal */ export const geometries = createCustomCachedSelector( - [getSpecs, getParentDimensions], - (specs, parentDimensions): ShapeViewModel => { + [getSpecs, getChartThemeSelector, getParentDimensions], + (specs, theme, parentDimensions): ShapeViewModel => { const wordcloudSpecs = getSpecsFromStore(specs, ChartType.Wordcloud, SpecType.Series); - return wordcloudSpecs.length === 1 ? render(wordcloudSpecs[0], parentDimensions) : nullShapeViewModel(); + return wordcloudSpecs.length === 1 ? render(wordcloudSpecs[0], theme, parentDimensions) : nullShapeViewModel(); }, ); diff --git a/packages/charts/src/chart_types/wordcloud/state/selectors/scenegraph.ts b/packages/charts/src/chart_types/wordcloud/state/selectors/scenegraph.ts index e8d50ed6e13..3618b418d75 100644 --- a/packages/charts/src/chart_types/wordcloud/state/selectors/scenegraph.ts +++ b/packages/charts/src/chart_types/wordcloud/state/selectors/scenegraph.ts @@ -6,15 +6,13 @@ * Side Public License, v 1. */ -import { mergePartial } from '../../../../utils/common'; import { Dimensions } from '../../../../utils/dimensions'; -import { config as defaultConfig } from '../../layout/config/config'; +import { Theme } from '../../../../utils/themes/theme'; import { ShapeViewModel } from '../../layout/types/viewmodel_types'; import { shapeViewModel } from '../../layout/viewmodel/viewmodel'; import { WordcloudSpec } from '../../specs'; /** @internal */ -export function render(spec: WordcloudSpec, parentDimensions: Dimensions): ShapeViewModel { - const { width, height } = parentDimensions; - return shapeViewModel(spec, mergePartial(defaultConfig, { ...spec.config, width, height })); +export function render(spec: WordcloudSpec, theme: Theme, parentDimensions: Dimensions): ShapeViewModel { + return shapeViewModel(spec, theme, parentDimensions); } diff --git a/packages/charts/src/common/colors.tsx b/packages/charts/src/common/colors.tsx index 70735a0bc7f..81faf66cf4e 100644 --- a/packages/charts/src/common/colors.tsx +++ b/packages/charts/src/common/colors.tsx @@ -15,7 +15,7 @@ import { RgbaTuple } from './color_library_wrappers'; export type Color = string; // todo static/runtime type it this for proper color string content; several places in the code, and ultimate use, dictate it not be an empty string /** @internal */ -export const Colors: Record = { +export const Colors: Record<'Red' | 'White' | 'Black' | 'Transparent', { keyword: Color; rgba: RgbaTuple }> = { Red: { keyword: 'red', rgba: [255, 0, 0, 1], diff --git a/packages/charts/src/index.ts b/packages/charts/src/index.ts index 439979ac41b..94f7d8c7ad4 100644 --- a/packages/charts/src/index.ts +++ b/packages/charts/src/index.ts @@ -72,6 +72,9 @@ export * from './utils/themes/theme_common'; export { LIGHT_THEME } from './utils/themes/light_theme'; export { DARK_THEME } from './utils/themes/dark_theme'; +// wordcloud +export { WordcloudViewModel } from './chart_types/wordcloud/layout/types/viewmodel_types'; + // partition export * from './chart_types/partition_chart/layout/types/viewmodel_types'; export * from './chart_types/partition_chart/layout/utils/group_by_rollup'; @@ -79,9 +82,6 @@ export { AnimKeyframe } from './chart_types/partition_chart/layout/types/config_ // heatmap export { Cell } from './chart_types/heatmap/layout/types/viewmodel_types'; -export { SizeRatio, TimeMs } from './common/geometry'; -export { TextAlign, TextBaseline } from './common/text_utils'; -export { Config as HeatmapConfig } from './chart_types/heatmap/layout/types/config_types'; export { ColorBand, HeatmapBandsColorScale } from './chart_types/heatmap/specs/heatmap'; // utilities diff --git a/packages/charts/src/mocks/specs/specs.ts b/packages/charts/src/mocks/specs/specs.ts index f0e9aa3d9aa..a59fecca2f2 100644 --- a/packages/charts/src/mocks/specs/specs.ts +++ b/packages/charts/src/mocks/specs/specs.ts @@ -190,7 +190,11 @@ export class MockSeriesSpec { valueFormatter: (value: number) => `${value}`, xSortPredicate: Predicate.AlphaAsc, ySortPredicate: Predicate.AlphaAsc, - config: {}, + timeZone: 'UTC', + xAxisLabelName: 'X Value', + xAxisLabelFormatter: String, + yAxisLabelName: 'Y Value', + yAxisLabelFormatter: String, }; static bar(partial?: Partial): BarSeriesSpec { 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 9aeeb021ffb..c68d6fd46ae 100644 --- a/packages/charts/src/utils/data_samples/test_dataset_heatmap.ts +++ b/packages/charts/src/utils/data_samples/test_dataset_heatmap.ts @@ -8,7 +8,7 @@ import { DateTime } from 'luxon'; -import { Config } from '../../chart_types/heatmap/layout/types/config_types'; +import { HeatmapSpec } from '../../chart_types/heatmap/specs'; import { ESCalendarInterval, ESFixedInterval } from '../chrono/elasticsearch'; type HeatmapDataSets = { @@ -16,7 +16,7 @@ type HeatmapDataSets = { timeZone?: string; data: Array<{ x: number; y: string; value: number }>; interval: ESFixedInterval | ESCalendarInterval; - xFormatter?: Config['xAxisLabel']['formatter']; + xFormatter?: HeatmapSpec['xAxisLabelFormatter']; }; /** @internal */ diff --git a/packages/charts/src/utils/themes/dark_theme.ts b/packages/charts/src/utils/themes/dark_theme.ts index fb4e2576320..64df26b235a 100644 --- a/packages/charts/src/utils/themes/dark_theme.ts +++ b/packages/charts/src/utils/themes/dark_theme.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { Colors } from '../../common/colors'; import { ColorVariant } from '../common'; import { palettes } from './colors'; import { Theme } from './theme'; @@ -253,4 +254,81 @@ export const DARK_THEME: Theme = { stroke: 'white', }, }, + heatmap: { + maxRowHeight: 30, + maxColumnWidth: 30, + brushArea: { + visible: true, + stroke: '#D3DAE6', // euiColorLightShade, + strokeWidth: 2, + }, + brushMask: { + visible: true, + fill: '#8c8c8c80', + }, + brushTool: { + visible: false, + fill: 'snow', + }, + 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: 6, + }, + yAxisLabel: { + visible: true, + width: 'auto', + fontSize: 12, + fontFamily: 'Sans-Serif', + fontStyle: 'normal', + textColor: Colors.White.keyword, + fontVariant: 'normal', + fontWeight: 'normal', + baseline: 'middle', + padding: 5, + }, + grid: { + cellWidth: { + min: 0, + max: 30, + }, + cellHeight: { + min: 12, + max: 30, + }, + stroke: { + width: 1, + color: 'snow', + }, + }, + cell: { + maxWidth: 'fill', + maxHeight: 'fill', + align: 'center', + label: { + visible: true, + maxWidth: 'fill', + minFontSize: 8, + maxFontSize: 12, + fontFamily: 'Sans-Serif', + fontStyle: 'normal', + textColor: Colors.White.keyword, + fontVariant: 'normal', + fontWeight: 'normal', + useGlobalMinFontSize: true, + }, + border: { + strokeWidth: 1, + stroke: 'snow', + }, + }, + }, }; diff --git a/packages/charts/src/utils/themes/light_theme.ts b/packages/charts/src/utils/themes/light_theme.ts index 04d395fa393..637bb891efb 100644 --- a/packages/charts/src/utils/themes/light_theme.ts +++ b/packages/charts/src/utils/themes/light_theme.ts @@ -254,4 +254,81 @@ export const LIGHT_THEME: Theme = { stroke: Colors.Black.keyword, }, }, + heatmap: { + maxRowHeight: 30, + maxColumnWidth: 30, + brushArea: { + visible: true, + stroke: '#69707D', // euiColorDarkShade, + strokeWidth: 2, + }, + brushMask: { + visible: true, + fill: '#73737380', + }, + brushTool: { + visible: false, + fill: 'gray', + }, + 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: 6, + }, + yAxisLabel: { + visible: true, + width: 'auto', + fontSize: 12, + fontFamily: 'Sans-Serif', + fontStyle: 'normal', + textColor: Colors.Black.keyword, + fontVariant: 'normal', + fontWeight: 'normal', + baseline: 'middle', + padding: 5, + }, + grid: { + cellWidth: { + min: 0, + max: 30, + }, + cellHeight: { + min: 12, + max: 30, + }, + stroke: { + width: 1, + color: 'gray', + }, + }, + cell: { + maxWidth: 'fill', + maxHeight: 'fill', + align: 'center', + label: { + visible: true, + maxWidth: 'fill', + minFontSize: 8, + maxFontSize: 12, + fontFamily: 'Sans-Serif', + fontStyle: 'normal', + textColor: Colors.Black.keyword, + fontVariant: 'normal', + fontWeight: 'normal', + useGlobalMinFontSize: true, + }, + border: { + strokeWidth: 1, + stroke: 'gray', + }, + }, + }, }; diff --git a/packages/charts/src/utils/themes/theme.ts b/packages/charts/src/utils/themes/theme.ts index 4f129c8b2a7..daaa5bf6e61 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 { FontStyle } from '../../common/text_utils'; +import { Font, FontStyle, TextAlign, TextBaseline } from '../../common/text_utils'; import { ColorVariant, HorizontalAlignment, RecursivePartial, VerticalAlignment } from '../common'; import { Margins, SimplePadding } from '../dimensions'; import { Point } from '../point'; @@ -188,6 +188,76 @@ export interface GoalStyles { maxFontSize: number; } +/** + * @public + */ +export interface HeatmapStyle { + maxRowHeight: Pixels; + maxColumnWidth: Pixels; + /** + * Config of the mask over the area outside of the selected cells + */ + brushMask: { visible: boolean; fill: Color }; + /** + * Config of the mask over the selected cells + */ + brushArea: { visible: boolean; fill?: Color; stroke: Color; strokeWidth: number }; + /** + * Config of the brushing tool + */ + brushTool: { + visible: boolean; + // TODO add support for changing the brush tool color + fill: Color; + }; + xAxisLabel: Font & { + fontSize: Pixels; + width: Pixels | 'auto'; + align: TextAlign; + baseline: TextBaseline; + visible: boolean; + padding: number; + }; + yAxisLabel: Font & { + fontSize: Pixels; + width: Pixels | 'auto' | { max: Pixels }; + baseline: TextBaseline; + visible: boolean; + padding: number | { left?: number; right?: number; top?: number; bottom?: number }; + }; + grid: { + cellWidth: { + min: Pixels; + max: Pixels | 'fill'; + }; + cellHeight: { + min: Pixels; + max: Pixels | 'fill'; + }; + stroke: { + color: string; + width: number; + }; + }; + cell: { + maxWidth: Pixels | 'fill'; + maxHeight: Pixels | 'fill'; + align: 'center'; + label: Font & { + minFontSize: Pixels; + maxFontSize: Pixels; + useGlobalMinFontSize: boolean; + maxWidth: Pixels | 'fill'; + visible: boolean; + }; + border: { + strokeWidth: Pixels; + stroke: Color; + }; + }; + maxLegendHeight?: number; +} + /** @public */ export interface ScalesConfig { /** @@ -324,6 +394,7 @@ export interface Theme { */ background: BackgroundStyle; goal: GoalStyles; + heatmap: HeatmapStyle; } /** @public */ diff --git a/storybook/stories/heatmap/1_basic.story.tsx b/storybook/stories/heatmap/1_basic.story.tsx index b20cc36bb38..4af50ea5bd1 100644 --- a/storybook/stories/heatmap/1_basic.story.tsx +++ b/storybook/stories/heatmap/1_basic.story.tsx @@ -16,6 +16,7 @@ import { DebugState, Heatmap, HeatmapElementEvent, + HeatmapStyle, niceTimeFormatter, RecursivePartial, ScaleType, @@ -23,7 +24,6 @@ import { HeatmapBrushEvent, ElementClickListener, } from '@elastic/charts'; -import { Config } from '@elastic/charts/src/chart_types/heatmap/layout/types/config_types'; import { DATA_6 } from '../../../packages/charts/src/utils/data_samples/test_dataset_heatmap'; import { useBaseTheme } from '../../use_base_theme'; @@ -41,8 +41,8 @@ export const Example = () => { button('Clear cells selection', handler); - const config: RecursivePartial = useMemo( - () => ({ + const heatmap = useMemo(() => { + const styles: RecursivePartial = { brushTool: { visible: true, }, @@ -71,15 +71,10 @@ export const Example = () => { width: 'auto', padding: { left: 10, right: 10 }, }, - xAxisLabel: { - formatter: (value: string | number) => { - return niceTimeFormatter([1572825600000, 1572912000000])(value, { timeZone: 'UTC' }); - }, - }, - timeZone: DATA_6.timeZone, - }), - [], - ); + }; + + return styles; + }, []); const logDebugState = debounce(() => { if (!debugState) return; @@ -112,6 +107,7 @@ export const Example = () => { brushAxis="both" xDomain={{ min: 1572825600000, max: 1572912000000 }} debugState={debugState} + theme={{ heatmap }} baseTheme={useBaseTheme()} onBrushEnd={(e) => { onBrushEnd(e); @@ -138,7 +134,13 @@ export const Example = () => { valueFormatter={(d) => `${Number(d.toFixed(2))}℃`} ySortPredicate="numAsc" xScale={{ type: ScaleType.Time, interval: DATA_6.interval }} - config={config} + xAxisLabelFormatter={(value) => { + return niceTimeFormatter([1572825600000, 1572912000000])(value, { timeZone: 'UTC' }); + }} + timeZone={DATA_6.timeZone} + onBrushEnd={(e) => { + setSelection({ x: e.x, y: e.y }); + }} highlightedData={persistCellsSelection ? selection : undefined} /> diff --git a/storybook/stories/heatmap/2_categorical.story.tsx b/storybook/stories/heatmap/2_categorical.story.tsx index 64428613b6c..c2eade6e942 100644 --- a/storybook/stories/heatmap/2_categorical.story.tsx +++ b/storybook/stories/heatmap/2_categorical.story.tsx @@ -36,6 +36,35 @@ export const Example = () => { legendPosition="right" brushAxis="both" baseTheme={useBaseTheme()} + theme={{ + heatmap: { + grid: { + stroke: { + width: 0, + }, + cellHeight: { + min: minCellHeight, + max: maxCellHeight, + }, + }, + cell: { + maxWidth: 'fill', + label: { + minFontSize, + maxFontSize, + visible: showLabels, + useGlobalMinFontSize, + }, + border: { + stroke: 'transparent', + strokeWidth: 1, + }, + }, + yAxisLabel: { + visible: true, + }, + }, + }} onBrushEnd={action('onBrushEnd')} /> { valueAccessor={(d) => d[3]} valueFormatter={(value) => value.toFixed(0.2)} xSortPredicate="alphaAsc" - config={{ - grid: { - stroke: { - width: 0, - }, - cellHeight: { - min: minCellHeight, - max: maxCellHeight, - }, - }, - cell: { - maxWidth: 'fill', - label: { - minFontSize, - maxFontSize, - visible: showLabels, - useGlobalMinFontSize, - }, - border: { - stroke: 'transparent', - strokeWidth: 1, - }, - }, - yAxisLabel: { - visible: true, - }, - }} /> ); diff --git a/storybook/stories/heatmap/3_time.story.tsx b/storybook/stories/heatmap/3_time.story.tsx index 7923756a907..6e282a83862 100644 --- a/storybook/stories/heatmap/3_time.story.tsx +++ b/storybook/stories/heatmap/3_time.story.tsx @@ -10,9 +10,11 @@ import { number } from '@storybook/addon-knobs'; import { DateTime } from 'luxon'; import React, { useMemo } from 'react'; -import { Chart, Heatmap, RecursivePartial, ScaleType, Settings, HeatmapConfig } from '@elastic/charts'; +import { Chart, Heatmap, RecursivePartial, ScaleType, Settings, HeatmapStyle } from '@elastic/charts'; import { getRandomNumberGenerator } from '@elastic/charts/src/mocks/utils'; +import { useBaseTheme } from '../../use_base_theme'; + const rng = getRandomNumberGenerator(); const start = DateTime.fromISO('2021-03-27T20:00:00', { zone: 'UTC' }); const end = DateTime.fromISO('2021-03-28T11:00:00', { zone: 'UTC' }); @@ -27,8 +29,8 @@ const data = [...new Array(14)].flatMap((d, i) => { }); export const Example = () => { - const config: RecursivePartial = useMemo( - () => ({ + const heatmap = useMemo(() => { + const styles: RecursivePartial = { grid: { cellHeight: { min: 20, @@ -54,17 +56,10 @@ export const Example = () => { width: 'auto', padding: { left: 10, right: 10 }, }, - xAxisLabel: { - formatter: (value: string | number) => { - return DateTime.fromMillis(value as number) - .setZone('UTC') - .toFormat('HH:mm:ss'); - }, - }, - timeZone: 'UTC', - }), - [], - ); + }; + + return styles; + }, []); const startTimeOffset = number('start time offset', 0, { min: -1000 * 60 * 60 * 24, @@ -92,6 +87,8 @@ export const Example = () => { min: start.toMillis() + startTimeOffset, max: end.toMillis() + endTimeOffset, }} + theme={{ heatmap }} + baseTheme={useBaseTheme()} /> { valueAccessor={(d) => d[2]} valueFormatter={(d) => d.toFixed(2)} ySortPredicate="numAsc" + xAxisLabelFormatter={(value) => { + return DateTime.fromMillis(value as number) + .setZone('UTC') + .toFormat('HH:mm:ss'); + }} + timeZone="UTC" xScale={{ type: ScaleType.Time, interval: { @@ -120,7 +123,6 @@ export const Example = () => { value: 1, }, }} - config={config} /> diff --git a/storybook/stories/heatmap/4_test_time_snap.story.tsx b/storybook/stories/heatmap/4_test_time_snap.story.tsx index 8860f4c47af..31acc3711ce 100644 --- a/storybook/stories/heatmap/4_test_time_snap.story.tsx +++ b/storybook/stories/heatmap/4_test_time_snap.story.tsx @@ -11,7 +11,7 @@ import { extent } from 'd3-array'; import { DateTime } from 'luxon'; import React from 'react'; -import { Chart, Heatmap, ScaleType, Settings } from '@elastic/charts'; +import { Chart, Heatmap, PartialTheme, ScaleType, Settings } from '@elastic/charts'; import { ColorBand } from '../../../packages/charts/src/chart_types/heatmap/specs/heatmap'; import { @@ -25,9 +25,40 @@ import { DATA_8, DATA_9, } from '../../../packages/charts/src/utils/data_samples/test_dataset_heatmap'; +import { useBaseTheme } from '../../use_base_theme'; const datasets = [DATA_1, DATA_2, DATA_3, DATA_4, DATA_5, DATA_6, DATA_7, DATA_8, DATA_9]; +const theme: PartialTheme = { + heatmap: { + grid: { + cellHeight: { + min: 20, + }, + stroke: { + width: 1, + color: 'black', + }, + }, + cell: { + maxWidth: 'fill', + maxHeight: 3, + label: { + visible: true, + }, + border: { + stroke: 'transparent', + strokeWidth: 0, + }, + }, + yAxisLabel: { + visible: true, + width: 'auto', + padding: { left: 10, right: 10 }, + }, + }, +}; + export const Example = () => { const datasetIndex = select('dataset', [1, 2, 3, 4, 5, 6, 7, 8, 9], 1) - 1; const dataset = datasets[datasetIndex]; @@ -49,7 +80,7 @@ export const Example = () => { {`${dataset.interval.type}: ${dataset.interval.value}${dataset.interval.unit} points:${dataset.data.length}`} - + { type: ScaleType.Time, interval: dataset.interval, }} - config={{ - grid: { - cellHeight: { - min: 20, - }, - stroke: { - width: 1, - color: 'black', - }, - }, - cell: { - maxWidth: 'fill', - maxHeight: 3, - label: { - visible: true, - }, - border: { - stroke: 'transparent', - strokeWidth: 0, - }, - }, - yAxisLabel: { - visible: true, - width: 'auto', - padding: { left: 10, right: 10 }, - }, - xAxisLabel: { - formatter: dataset.xFormatter, - }, - timeZone: dataset.timeZone ?? 'Europe/Rome', - }} + timeZone={dataset.timeZone ?? 'Europe/Rome'} + xAxisLabelFormatter={dataset.xFormatter} /> diff --git a/storybook/stories/heatmap/5_theming.story.tsx b/storybook/stories/heatmap/5_theming.story.tsx new file mode 100644 index 00000000000..b05c2397827 --- /dev/null +++ b/storybook/stories/heatmap/5_theming.story.tsx @@ -0,0 +1,137 @@ +/* + * 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, color, number } from '@storybook/addon-knobs'; +import React from 'react'; +import { debounce } from 'ts-debounce'; + +import { + Chart, + DebugState, + Heatmap, + HeatmapStyle, + niceTimeFormatter, + RecursivePartial, + ScaleType, + Settings, +} from '@elastic/charts'; +import { DATA_6 } from '@elastic/charts/src/utils/data_samples/test_dataset_heatmap'; + +import { useBaseTheme } from '../../use_base_theme'; + +export const Example = () => { + const debugState = boolean('Enable debug state', true); + const dataStateAction = action('DataState'); + + const heatmap: RecursivePartial = { + brushArea: { + visible: boolean('brushArea visible', true, 'Theme'), + fill: color('brushArea fill', 'black', 'Theme'), + stroke: color('brushArea stroke', '#69707D', 'Theme'), + strokeWidth: number('brushArea strokeWidth', 2, { range: true, min: 1, max: 10 }, 'Theme'), + }, + brushMask: { + visible: boolean('brushMask visible', true, 'Theme'), + fill: color('brushMask fill', 'rgb(115 115 115 / 50%)', 'Theme'), + }, + brushTool: { + visible: boolean('brushTool visible', false, 'Theme'), + fill: color('brushTool fill color', 'gray', 'Theme'), + }, + xAxisLabel: { + visible: boolean('xAxisLabel visible', true, 'Theme'), + fontSize: number('xAxisLabel fontSize', 12, { range: true, min: 5, max: 20 }, 'Theme'), + textColor: color('xAxisLabel textColor', 'black', 'Theme'), + padding: number('xAxisLabel padding', 6, { range: true, min: 0, max: 15 }, 'Theme'), + }, + yAxisLabel: { + visible: boolean('yAxisLabel visible', true, 'Theme'), + fontSize: number('yAxisLabel fontSize', 12, { range: true, min: 5, max: 20 }, 'Theme'), + textColor: color('yAxisLabel textColor', 'black', 'Theme'), + padding: number('yAxisLabel padding', 5, { range: true, min: 0, max: 15 }, 'Theme'), + }, + grid: { + stroke: { + color: color('grid stroke color', 'gray', 'Theme'), + }, + }, + cell: { + label: { + visible: boolean('cell label visible', false, 'Theme'), + textColor: color('cell label textColor', 'black', 'Theme'), + useGlobalMinFontSize: boolean('cell label use global min fontSize', true, 'Theme'), + minFontSize: number('cell label min fontSize', 6, { step: 1, min: 4, max: 10, range: true }, 'Theme'), + maxFontSize: number('cell label max fontSize', 12, { step: 1, min: 10, max: 64, range: true }, 'Theme'), + }, + border: { + strokeWidth: number('border strokeWidth', 1, { range: true, min: 1, max: 5 }, 'Theme'), + stroke: color('border stroke color', 'gray', 'Theme'), + }, + }, + }; + + const logDebugState = debounce(() => { + if (!debugState) return; + + const statusEl = document.querySelector('.echChartStatus'); + + if (statusEl) { + const dataState = statusEl.dataset.echDebugState + ? (JSON.parse(statusEl.dataset.echDebugState) as DebugState) + : null; + dataStateAction(dataState); + } + }, 100); + + return ( + + + `${Number(d.toFixed(2))}℃`} + ySortPredicate="numAsc" + xScale={{ type: ScaleType.Time, interval: DATA_6.interval }} + xAxisLabelFormatter={(value) => { + return niceTimeFormatter([1572825600000, 1572912000000])(value, { timeZone: 'UTC' }); + }} + timeZone={DATA_6.timeZone} + /> + + ); +}; + +Example.parameters = { + markdown: ` +> __Warning:__ ⚠️ default \`Theme\` styles are overrided by knob controls. Toggling between themes may show incorrect styles. + `, +}; diff --git a/storybook/stories/heatmap/heatmap.stories.tsx b/storybook/stories/heatmap/heatmap.stories.tsx index 3b942b137dd..8f3f95a0f34 100644 --- a/storybook/stories/heatmap/heatmap.stories.tsx +++ b/storybook/stories/heatmap/heatmap.stories.tsx @@ -14,3 +14,4 @@ export { Example as basic } from './1_basic.story'; 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'; diff --git a/storybook/stories/wordcloud/1_wordcloud.story.tsx b/storybook/stories/wordcloud/1_wordcloud.story.tsx index 68168d94de3..83958a339f7 100644 --- a/storybook/stories/wordcloud/1_wordcloud.story.tsx +++ b/storybook/stories/wordcloud/1_wordcloud.story.tsx @@ -10,7 +10,7 @@ import { action } from '@storybook/addon-actions'; import { color, number, select } from '@storybook/addon-knobs'; import React from 'react'; -import { Chart, Settings, Wordcloud, FontStyle } from '@elastic/charts'; +import { Chart, Settings, Wordcloud, FontStyle, WordcloudSpec, Color } from '@elastic/charts'; import { WeightFn, WordModel } from '@elastic/charts/src/chart_types/wordcloud/layout/types/viewmodel_types'; import { getRandomNumberGenerator } from '@elastic/charts/src/mocks/utils'; import { palettes as euiPalettes } from '@elastic/charts/src/utils/themes/colors'; @@ -54,119 +54,159 @@ const palettes = { }, }; -const configs = { - edit: { - startAngle: -90, - endAngle: 90, - angleCount: 16, - padding: 0.5, - exponent: 15, - fontWeight: 900, - minFontSize: 15, - maxFontSize: 80, - fontFamily: 'Arial', - fontStyle: 'italic', - shape: 'archimedean', - palette: 'turquoise', - backgroundColor: '#1c1c24', - weightFn: WeightFn.exponential, - }, - single: { - startAngle: 0, - endAngle: 0, - angleCount: 1, - padding: 1, - exponent: 4, - fontWeight: 900, - minFontSize: 14, - maxFontSize: 92, - fontFamily: 'Arial', - fontStyle: 'normal', - shape: 'rectangular', - palette: 'greyScale', - backgroundColor: '#9fa714', - weightFn: WeightFn.exponential, - }, - rightAngled: { - startAngle: 0, - endAngle: 90, - angleCount: 2, - padding: 1, - exponent: 4, - fontWeight: 600, - minFontSize: 14, - maxFontSize: 92, - fontFamily: 'Arial Narrow', - fontStyle: 'normal', - shape: 'rectangular', - palette: 'euiLight', - backgroundColor: '#ffffff', - weightFn: WeightFn.exponential, - }, - multiple: { - startAngle: -90, - endAngle: 90, - angleCount: 16, - padding: 1, - exponent: 15, - fontWeight: 100, - minFontSize: 16, - maxFontSize: 50, - fontFamily: 'Luminari', - fontStyle: 'italic', - shape: 'archimedean', - palette: 'redBlue', - backgroundColor: '#1c1c24', - weightFn: WeightFn.exponential, - }, - squareWords: { - startAngle: -45, - endAngle: 45, - angleCount: 2, - padding: 0, - exponent: 3, - fontWeight: 100, - minFontSize: 10, - maxFontSize: 90, - fontFamily: 'Arial Narrow', - fontStyle: 'normal', - shape: 'archimedean', - palette: 'weight', - backgroundColor: '#4a6960', - weightFn: WeightFn.exponential, - }, - smallWaves: { - startAngle: -15, - endAngle: 15, - angleCount: 7, - padding: 0.5, - exponent: 5, - fontWeight: 600, - minFontSize: 17, - maxFontSize: 79, - fontFamily: 'Impact', - fontStyle: 'normal', - shape: 'rectangular', - palette: 'euiColorBlind', - backgroundColor: '#ffffff', - weightFn: WeightFn.exponential, - }, - sparse: { - startAngle: 0, - endAngle: 0, - angleCount: 1, - padding: getRandomNumber(2, 22), - exponent: 15, - fontWeight: 600, - minFontSize: 12, - maxFontSize: 60, - fontFamily: 'Courier', - fontStyle: 'normal', - shape: 'rectangular', - palette: 'vivid', - backgroundColor: '#1c1c24', - weightFn: WeightFn.exponential, - }, +type WordcloudKnobs = Omit & { + palette: keyof typeof palettes; + backgroundColor: Color; +}; + +// Used in integration testing +export const TEMPLATES = ['edit', 'single', 'rightAngled', 'multiple', 'squareWords', 'smallWaves', 'sparse']; +const getTemplate = (name: string): WordcloudKnobs => { + switch (name) { + case 'single': + return { + spiral: 'rectangular', + startAngle: 0, + endAngle: 0, + angleCount: 1, + padding: 1, + exponent: 4, + fontWeight: 900, + minFontSize: 14, + maxFontSize: 92, + fontFamily: 'Arial', + fontStyle: 'normal', + palette: 'greyScale', + weightFn: WeightFn.exponential, + backgroundColor: '#9fa714', + }; + case 'rightAngled': + return { + spiral: 'rectangular', + startAngle: 0, + endAngle: 90, + angleCount: 2, + padding: 1, + exponent: 4, + fontWeight: 600, + minFontSize: 14, + maxFontSize: 92, + fontFamily: 'Arial Narrow', + fontStyle: 'normal', + palette: 'euiLight', + weightFn: WeightFn.exponential, + backgroundColor: '#ffffff', + }; + case 'multiple': + return { + spiral: 'archimedean', + startAngle: -90, + endAngle: 90, + angleCount: 16, + padding: 1, + exponent: 15, + fontWeight: 100, + minFontSize: 16, + maxFontSize: 50, + fontFamily: 'Luminari', + fontStyle: 'italic', + palette: 'redBlue', + weightFn: WeightFn.exponential, + backgroundColor: '#1c1c24', + }; + case 'squareWords': + return { + spiral: 'archimedean', + startAngle: -45, + endAngle: 45, + angleCount: 2, + padding: 0, + exponent: 3, + fontWeight: 100, + minFontSize: 10, + maxFontSize: 90, + fontFamily: 'Arial Narrow', + fontStyle: 'normal', + palette: 'weight', + weightFn: WeightFn.exponential, + backgroundColor: '#4a6960', + }; + case 'smallWaves': + return { + spiral: 'rectangular', + startAngle: -15, + endAngle: 15, + angleCount: 7, + padding: 0.5, + exponent: 5, + fontWeight: 600, + minFontSize: 17, + maxFontSize: 79, + fontFamily: 'Impact', + fontStyle: 'normal', + palette: 'euiColorBlind', + weightFn: WeightFn.exponential, + backgroundColor: '#ffffff', + }; + case 'sparse': + return { + spiral: 'rectangular', + startAngle: 0, + endAngle: 0, + angleCount: 1, + padding: getRandomNumber(2, 22), + exponent: 15, + fontWeight: 600, + minFontSize: 12, + maxFontSize: 60, + fontFamily: 'Courier', + fontStyle: 'normal', + palette: 'vivid', + weightFn: WeightFn.exponential, + backgroundColor: '#1c1c24', + }; + case 'edit': + default: + return { + spiral: select('shape', { oval: 'archimedean', rectangular: 'rectangular' }, 'archimedean'), + startAngle: number('startAngle', -90, { range: true, min: -360, max: 360, step: 1 }), + endAngle: number('endAngle', 90, { range: true, min: -360, max: 360, step: 1 }), + angleCount: number('angleCount', 16, { range: true, min: 2, max: 360, step: 1 }), + padding: number('padding', 0.5, { range: true, min: 0, max: 10, step: 0.5 }), + exponent: number('exponent', 15, { range: true, min: 0, max: 15, step: 1 }), + fontWeight: number('fontWeight', 900, { range: true, min: 100, max: 900, step: 100 }), + minFontSize: number('minFontSize', 15, { range: true, min: 6, max: 85, step: 1 }), + maxFontSize: number('maxFontSize', 80, { range: true, min: 15, max: 150, step: 1 }), + fontFamily: select( + 'fontFamily', + { + Arial: 'Arial', + 'Arial Narrow': 'Arial Narrow', + Courier: 'Courier', + Impact: 'Impact', + Luminari: 'Luminari', + }, + 'Arial', + ), + fontStyle: select('fontStyle', { normal: 'normal', italic: 'italic' }, 'italic'), + palette: select( + 'palette', + Object.keys(palettes).reduce((p, k) => ({ ...p, [k]: k }), {}), + 'turquoise', + ), + weightFn: select( + 'weightFn', + { + linear: WeightFn.linear, + exponential: WeightFn.exponential, + squareRoot: WeightFn.squareRoot, + log: WeightFn.log, + }, + WeightFn.exponential, + ), + backgroundColor: color('background', '#1c1c24'), + }; + } }; const rawData = text @@ -186,7 +226,7 @@ interface RawDatum { weight: number; } -function sampleData(txt: string, paletteName: keyof typeof palettes): WordModel[] { +function sampleData(paletteName: keyof typeof palettes): WordModel[] { return rawData.map(function rawMapper(d, i) { return { ...d, @@ -197,76 +237,11 @@ function sampleData(txt: string, paletteName: keyof typeof palettes): WordModel[ export const Example = () => { const configName = select( - 'config', - Object.keys(configs).reduce((p, k) => ({ ...p, [k]: k }), {}), + 'template', + TEMPLATES.reduce((p, k) => ({ ...p, [k]: k }), {}), 'edit', ); - const startConfig = configs[configName]; - const template = configName !== 'edit'; - const spiral = template - ? startConfig.shape - : select('shape', { oval: 'archimedean', rectangular: 'rectangular' }, startConfig.shape); - const backgroundColor = template ? startConfig.backgroundColor : color('background', startConfig.backgroundColor); - const startAngle = template - ? startConfig.startAngle - : number('startAngle', startConfig.startAngle, { range: true, min: -360, max: 360, step: 1 }); - const endAngle = template - ? startConfig.endAngle - : number('endAngle', startConfig.endAngle, { range: true, min: -360, max: 360, step: 1 }); - const angleCount = template - ? startConfig.angleCount - : number('angleCount', startConfig.angleCount, { range: true, min: 2, max: 360, step: 1 }); - const padding = template - ? startConfig.padding - : number('padding', startConfig.padding, { range: true, min: 0, max: 10, step: 0.5 }); - const exponent = template - ? startConfig.exponent - : number('exponent', startConfig.exponent, { range: true, min: 0, max: 15, step: 1 }); - const fontWeight = template - ? startConfig.fontWeight - : number('fontWeight', startConfig.fontWeight, { range: true, min: 100, max: 900, step: 100 }); - const minFontSize = template - ? startConfig.minFontSize - : number('minFontSize', startConfig.minFontSize, { range: true, min: 6, max: 85, step: 1 }); - const maxFontSize = template - ? startConfig.maxFontSize - : number('maxFontSize', startConfig.maxFontSize, { range: true, min: 15, max: 150, step: 1 }); - const fontFamily = template - ? startConfig.fontFamily - : select( - 'fontFamily', - { - Arial: 'Arial', - 'Arial Narrow': 'Arial Narrow', - Courier: 'Courier', - Impact: 'Impact', - Luminari: 'Luminari', - }, - startConfig.fontFamily, - ); - const fontStyle = template - ? (startConfig.fontStyle as FontStyle) - : select('fontStyle', { normal: 'normal', italic: 'italic' }, startConfig.fontStyle as FontStyle); - - const palette = template - ? startConfig.palette - : select( - 'palette', - Object.keys(palettes).reduce((p, k) => ({ ...p, [k]: k }), {}), - startConfig.palette, - ); - const weightFn = template - ? startConfig.weightFn - : select( - 'weightFn', - { - linear: WeightFn.linear, - exponential: WeightFn.exponential, - squareRoot: WeightFn.squareRoot, - log: WeightFn.log, - }, - startConfig.weightFn, - ); + const { backgroundColor, palette, ...knobs } = getTemplate(configName); return ( @@ -284,19 +259,8 @@ export const Example = () => { /> { action('outOfRoomCallback')( `Managed to render ${renderedWordCount} words out of ${wordCount} words: ${renderedWords.join(', ')}`, @@ -308,5 +272,6 @@ export const Example = () => { }; Example.parameters = { - backgrounds: { disable: true }, + background: { disable: true }, + theme: { disable: true }, }; diff --git a/yarn.lock b/yarn.lock index a1a570e8097..165a77396d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5592,6 +5592,13 @@ resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.8.tgz#b852381cb68e31e46bfa23ee70a383cbc6d62146" integrity sha512-wWV0wT6oLUGprrOR5LMK7Dh8EBiondhnqINsvazv6UucYfTdb2oaFF4knlqzZV2RKB9ZC9G7G1Iojt8b/wolsw== +"@types/d3-cloud@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/d3-cloud/-/d3-cloud-1.2.5.tgz#0300bedc826aacd505ae6c41c5f8c4ab75c45135" + integrity sha512-vEIER9DsEBUOdpRiwCh3n1qE+cV6h4e1LhxhY2sLt+m8LPNAIkOOhTlqk0JDiBwD+ZPM8ynFAOU3AuPuVYBFBA== + dependencies: + "@types/d3" "^3" + "@types/d3-collection@^1.0.8": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/d3-collection/-/d3-collection-1.0.8.tgz#aa9552c570a96e33c132e0fd20e331f64baa9dd5" @@ -5633,6 +5640,11 @@ resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-1.0.10.tgz#d338c7feac93a98a32aac875d1100f92c7b61f4f" integrity sha512-aKf62rRQafDQmSiv1NylKhIMmznsjRN+MnXRXTqHoqm0U/UZzVpdrtRnSIfdiLS616OuC1soYeX1dBg2n1u8Xw== +"@types/d3@^3": + version "3.5.45" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.45.tgz#cceb1cd8f468b0ed1c96546ddefff3408d7463a7" + integrity sha512-wLICfMtjDEoAJie1MF6OuksAzOapRXgJy+l5HQVpyC1yMAlvHz2QKrrasUHru8xD6cbgQNGeO+CeyjOlKtly2A== + "@types/enzyme-adapter-react-16@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.5.tgz#1bf30a166f49be69eeda4b81e3f24113c8b4e9d5"