diff --git a/.buildkite/utils/github.ts b/.buildkite/utils/github.ts index b588d27c6b..0055a47224 100644 --- a/.buildkite/utils/github.ts +++ b/.buildkite/utils/github.ts @@ -229,8 +229,8 @@ export const updateCheckStatus = async ( // revert the completed check run is to create a new check run. This will not show as a duplicate run. const newCheckNeeded = options.status !== 'completed' && checkRun?.status === 'completed'; - console.trace('updateCheckStatus', checkId, title); - console.log(JSON.stringify(options, null, 2)); + // console.trace('updateCheckStatus', checkId, title); + // console.log(JSON.stringify(options, null, 2)); try { const output = diff --git a/e2e/page_objects/common.ts b/e2e/page_objects/common.ts index a649c9e28d..7a4b99e0be 100644 --- a/e2e/page_objects/common.ts +++ b/e2e/page_objects/common.ts @@ -167,7 +167,7 @@ export class CommonPage { static validatePath(path: string | string[]): string | string[] { const fileName = Array.isArray(path) ? path[path.length - 1] : path; - if (/\.png$/.test(fileName)) return path; + if (fileName && /\.png$/.test(fileName)) return path; throw new Error(`Screenshot path or last path segment must contain the .png file extension.`); } diff --git a/e2e/tests/legend_stories.test.ts b/e2e/tests/legend_stories.test.ts index 548813fa6e..29ea9bc32e 100644 --- a/e2e/tests/legend_stories.test.ts +++ b/e2e/tests/legend_stories.test.ts @@ -208,7 +208,7 @@ test.describe('Legend stories', () => { const getPositionalUrl = (p1: string, p2: string, others: string = '') => `http://localhost:9001/?path=/story/legend--inside-chart&knob-vAlign_Legend=${p1}&knob-hAlign_Legend=${p2}${others}`; - pwEach.test([ + pwEach.test<[Position, Position]>([ [Position.Top, Position.Left], [Position.Top, Position.Right], [Position.Bottom, Position.Left], @@ -220,7 +220,7 @@ test.describe('Legend stories', () => { }, ); - pwEach.test([ + pwEach.test<[Position, Position]>([ [Position.Top, Position.Left], [Position.Top, Position.Right], [Position.Bottom, Position.Left], @@ -235,7 +235,7 @@ test.describe('Legend stories', () => { const longLabel = 'Non do aliqua veniam dolore ipsum eu aliquip. Culpa in duis amet non velit qui non ullamco sit adipisicing. Ut sunt Lorem mollit exercitation deserunt officia sunt ipsum eu amet.'; - pwEach.test([ + pwEach.test<[Position, Position]>([ [Position.Top, Position.Left], [Position.Top, Position.Right], [Position.Bottom, Position.Left], diff --git a/github_bot/Dockerfile b/github_bot/Dockerfile index 8e498781c3..b09a3837b7 100644 --- a/github_bot/Dockerfile +++ b/github_bot/Dockerfile @@ -2,7 +2,7 @@ FROM node:16-alpine as builder WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile -COPY tsconfig.json ./ +COPY tsconfig.main.json ./tsconfig.json COPY src src RUN yarn build diff --git a/github_bot/src/github/events/push/trigger_build.ts b/github_bot/src/github/events/push/trigger_build.ts index 4d6f4aae2f..2687c4cca7 100644 --- a/github_bot/src/github/events/push/trigger_build.ts +++ b/github_bot/src/github/events/push/trigger_build.ts @@ -16,10 +16,15 @@ import { checkCommitFn, isBaseRepo, testPatternString, updateAllChecks } from '. * build trigger for pushes to select base branches not pull requests */ export function setupBuildTrigger(app: Probot) { + // @ts-ignore - probot issue https://github.com/probot/probot/issues/1680 app.on('push', async (ctx) => { const [branch] = ctx.payload.ref.split('/').reverse(); - if (!isBaseRepo(ctx.payload.repository) || !getConfig().github.env.branch.base.some(testPatternString(branch))) { + if ( + !branch || + !isBaseRepo(ctx.payload.repository) || + !getConfig().github.env.branch.base.some(testPatternString(branch)) + ) { return; } diff --git a/github_bot/src/github/utils.ts b/github_bot/src/github/utils.ts index f667b87f2b..748dfabb6b 100644 --- a/github_bot/src/github/utils.ts +++ b/github_bot/src/github/utils.ts @@ -259,6 +259,8 @@ export async function syncChecks(ctx: ProbotEventContext<'pull_request'>) { const [previousCommitSha] = await getLatestCommits(ctx); + if (!previousCommitSha) throw new Error('Unable to load previous commit'); + const { data: { check_runs: checks }, } = await ctx.octokit.checks.listForRef({ @@ -348,14 +350,15 @@ export async function updatePreviousDeployments( await Promise.all( deployments.map(async ({ id }) => { const { - data: [{ environment, state: currentState, ...status }], + data: [data], } = await ctx.octokit.repos.listDeploymentStatuses({ ...ctx.repo(), deployment_id: id, per_page: 1, }); - if (['in_progress', 'queued', 'pending'].includes(currentState)) { + if (data && ['in_progress', 'queued', 'pending'].includes(data.state)) { + const { environment, ...status } = data; await ctx.octokit.repos.createDeploymentStatus({ ...ctx.repo(), ...status, diff --git a/github_bot/tsconfig.json b/github_bot/tsconfig.main.json similarity index 100% rename from github_bot/tsconfig.json rename to github_bot/tsconfig.main.json diff --git a/package.json b/package.json index d4c71ef16c..57f4c32cd7 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,10 @@ "test:e2e:generate:page": "./e2e_server/scripts/compile_vrt_page.sh", "test:e2e:server": "sh ./e2e_server/scripts/start.sh", "test:e2e:server:build": "cd e2e_server/server && webpack build", + "typecheck:base": "tsc -p ./tsconfig.base.json --noEmit", "typecheck:src": "lerna run --loglevel=silent --scope @elastic/charts typecheck --stream --no-prefix", - "typecheck:all": "tsc -p ./tsconfig.json --noEmit", + "typecheck:storybook": "lerna run --loglevel=silent --scope charts-storybook typecheck --stream --no-prefix", + "typecheck:all": "yarn typecheck:base && yarn typecheck:src && yarn typecheck:storybook", "ts:prune": "ts-prune" }, "devDependencies": { diff --git a/packages/charts/api-extractor.jsonc b/packages/charts/api-extractor.jsonc index e72bc020b6..4a3dc1c38d 100644 --- a/packages/charts/api-extractor.jsonc +++ b/packages/charts/api-extractor.jsonc @@ -78,7 +78,7 @@ * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/tsconfig.json" */ - "tsconfigFilePath": "/tsconfig.json", + "tsconfigFilePath": "/tsconfig.src.json", /** * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. * The object must conform to the TypeScript tsconfig schema: diff --git a/packages/charts/api/charts.api.md b/packages/charts/api/charts.api.md index 492677e3b2..de3daac1d8 100644 --- a/packages/charts/api/charts.api.md +++ b/packages/charts/api/charts.api.md @@ -2247,7 +2247,7 @@ export type Ratio = number; export type RawTextGetter = (node: ShapeTreeNode) => string; // @public (undocumented) -export const RectAnnotation: FC>; +export const RectAnnotation: FC>; // @public export interface RectAnnotationDatum { diff --git a/packages/charts/package.json b/packages/charts/package.json index 47fcd9856f..cefd786ad7 100644 --- a/packages/charts/package.json +++ b/packages/charts/package.json @@ -23,13 +23,13 @@ "build:ts": "yarn build:clean && yarn build:compile && yarn build:check", "build:css": "yarn build:sass && yarn autoprefix:css && yarn concat:sass", "build:clean": "echo 'Cleaning dist...' && rm -rf ./dist", - "build:compile": "echo 'Compiling...' && tsc -p ./tsconfig.json && tsc -p ./tsconfig.nocomments.json", + "build:compile": "echo 'Compiling...' && tsc -p ./tsconfig.src.json && tsc -p ./tsconfig.nocomments.json", "build:sass": "echo 'Building sass...' && sass src:dist --style compressed --quiet --color", "build:check": "echo 'Type checking dist...' && tsc -p ./tsconfig.check.json", - "build:watch": "echo 'Watching build...' && yarn build:clean && yarn build:css && tsc -p ./tsconfig.json -w", + "build:watch": "echo 'Watching build...' && yarn build:clean && yarn build:css && tsc -p ./tsconfig.src.json -w", "concat:sass": "echo 'Concat SASS...' && node scripts/concat_sass.js", "semantic-release": "semantic-release", - "typecheck": "tsc -p ./tsconfig.json --noEmit && tsc -p ./tsconfig.nocomments.json --noEmit" + "typecheck": "tsc -p ./tsconfig.src.json --noEmit && tsc -p ./tsconfig.nocomments.json --noEmit" }, "dependencies": { "@popperjs/core": "^2.4.0", diff --git a/packages/charts/src/chart_types/flame_chart/flame_chart.tsx b/packages/charts/src/chart_types/flame_chart/flame_chart.tsx index 03b8a001f8..618231c19f 100644 --- a/packages/charts/src/chart_types/flame_chart/flame_chart.tsx +++ b/packages/charts/src/chart_types/flame_chart/flame_chart.tsx @@ -33,7 +33,7 @@ import { getChartThemeSelector } from '../../state/selectors/get_chart_theme'; import { getSettingsSpecSelector } from '../../state/selectors/get_settings_spec'; import { getTooltipSpecSelector } from '../../state/selectors/get_tooltip_spec'; import { getSpecsFromStore } from '../../state/utils'; -import { clamp, isFiniteNumber } from '../../utils/common'; +import { clamp, isFiniteNumber, isNil } from '../../utils/common'; import { Size } from '../../utils/dimensions'; import { FlamegraphStyle } from '../../utils/themes/theme'; @@ -60,7 +60,7 @@ const WOBBLE_REPEAT_COUNT = 2; const WOBBLE_FREQUENCY = SHOULD_DISABLE_WOBBLE ? 0 : 2 * Math.PI * (WOBBLE_REPEAT_COUNT / WOBBLE_DURATION); // e.g. 1/30 means a cycle of every 30ms const NODE_TWEEN_DURATION_MS = 500; -const unitRowPitch = (position: Float32Array) => (position.length >= 4 ? position[1] - position[3] : 1); +const unitRowPitch = (position: Float32Array) => (position.length >= 4 ? (position[1] ?? 0) - (position[3] ?? 0) : 1); const initialPixelRowPitch = () => 16; const specValueFormatter = (d: number) => d; // fixme use the formatter from the spec const browserRootWindow = () => { @@ -70,10 +70,10 @@ const browserRootWindow = () => { }; const columnToRowPositions = ({ position1, size1 }: FlameSpec['columnarData'], i: number) => ({ - x0: position1[i * 2], - x1: position1[i * 2] + size1[i], - y0: position1[i * 2 + 1], - y1: position1[i * 2 + 1] + unitRowPitch(position1), + x0: position1[i * 2] ?? 0, + x1: (position1[i * 2] ?? 0) + (size1[i] ?? 0), + y0: position1[i * 2 + 1] ?? 0, + y1: (position1[i * 2 + 1] ?? 0) + unitRowPitch(position1), }); /** @internal */ @@ -111,17 +111,18 @@ const focusRect = ( ): FocusRect => focusForArea(chartHeight, columnToRowPositions(columnarViewModel, drilldownDatumIndex || 0)); const getColor = (c: Float32Array, i: number) => { - const r = Math.round(255 * c[4 * i]); - const g = Math.round(255 * c[4 * i + 1]); - const b = Math.round(255 * c[4 * i + 2]); + const r = Math.round(255 * (c[4 * i] ?? 0)); + const g = Math.round(255 * (c[4 * i + 1] ?? 0)); + const b = Math.round(255 * (c[4 * i + 2] ?? 0)); const a = c[4 * i + 3]; return `rgba(${r}, ${g}, ${b}, ${a})`; }; const colorToDatumIndex = (pixel: Uint8Array) => { // this is the inverse of what's done via BIT_SHIFTERS in shader code (bijective color/index mapping) - const isEmptyArea = pixel[0] + pixel[1] + pixel[2] + pixel[3] < GEOM_INDEX_OFFSET; // ie. zero - return isEmptyArea ? NaN : pixel[3] + 256 * (pixel[2] + 256 * (pixel[1] + 256 * pixel[0])) - GEOM_INDEX_OFFSET; + const [p0 = 0, p1 = 0, p2 = 0, p3 = 0] = pixel; + const isEmptyArea = p0 + p1 + p2 + p3 < GEOM_INDEX_OFFSET; // ie. zero + return isEmptyArea ? NaN : p3 + 256 * (p2 + 256 * (p1 + 256 * p0)) - GEOM_INDEX_OFFSET; }; const getRegExp = (searchString: string): RegExp => { @@ -473,21 +474,21 @@ class FlameComponent extends React.Component { if (prevHoverIndex !== this.hoverIndex) { const columns = this.props.columnarViewModel; - this.tooltipValues = - this.hoverIndex >= 0 - ? [ - { - label: columns.label[this.hoverIndex], - color: getColor(columns.color, this.hoverIndex), - isHighlighted: false, - isVisible: true, - seriesIdentifier: { specId: '', key: '' }, - value: columns.value[this.hoverIndex], - formattedValue: `${specValueFormatter(columns.value[this.hoverIndex])}`, - valueAccessor: this.hoverIndex, - }, - ] - : []; + const hoverValue = this.hoverIndex >= 0 ? columns.value[this.hoverIndex] : null; + this.tooltipValues = !isNil(hoverValue) + ? [ + { + label: columns.label[this.hoverIndex] ?? '', + color: getColor(columns.color, this.hoverIndex), + isHighlighted: false, + isVisible: true, + seriesIdentifier: { specId: '', key: '' }, + value: hoverValue, + formattedValue: `${specValueFormatter(hoverValue)}`, + valueAccessor: this.hoverIndex, + }, + ] + : []; } this.setState({}); // exact tooltip location needs an update } @@ -707,13 +708,13 @@ class FlameComponent extends React.Component { let y1 = -Infinity; // todo unify with matcher loop and setup in focusOnHit for (let i = 0; i < datumCount; i++) { - const label = this.caseSensitive ? labels[i] : labels[i].toLowerCase(); - if (regex ? label.match(regex) : label.includes(customizedSearchString)) { + const label = this.caseSensitive ? labels[i] : labels[i]?.toLowerCase(); + if (regex ? label?.match(regex) : label?.includes(customizedSearchString)) { this.currentSearchHitCount++; - x0 = Math.min(x0, position[2 * i]); - x1 = Math.max(x1, position[2 * i] + size[i]); - y0 = Math.min(y0, position[2 * i + 1]); - y1 = Math.max(y1, position[2 * i + 1] + rowHeight); + x0 = Math.min(x0, position[2 * i] ?? 0); + x1 = Math.max(x1, (position[2 * i] ?? 0) + (size[i] ?? 0)); + y0 = Math.min(y0, position[2 * i + 1] ?? 0); + y1 = Math.max(y1, (position[2 * i + 1] ?? 0) + rowHeight); } else { this.currentColor[4 * i + 3] *= 0.25; // multiply alpha } @@ -794,8 +795,8 @@ class FlameComponent extends React.Component { const labels = this.props.columnarViewModel.label; // todo unify with matcher loop and setup in focusOnAllMatches for (let i = 0; i < labels.length; i++) { - const label = this.caseSensitive ? labels[i] : labels[i].toLowerCase(); - if (regex ? label.match(regex) : label.includes(customizedSearchString)) { + const label = this.caseSensitive ? labels[i] : labels[i]?.toLowerCase(); + if (regex ? label?.match(regex) : label?.includes(customizedSearchString)) { datumIndex = i; hitEnumerator++; if (hitEnumerator === this.focusedMatchIndex) break; diff --git a/packages/charts/src/chart_types/flame_chart/render/draw_canvas.ts b/packages/charts/src/chart_types/flame_chart/render/draw_canvas.ts index 05cbb45106..609b75b568 100644 --- a/packages/charts/src/chart_types/flame_chart/render/draw_canvas.ts +++ b/packages/charts/src/chart_types/flame_chart/render/draw_canvas.ts @@ -22,7 +22,7 @@ const ROW_OFFSET_Y = 0.45; // approx. middle line (text is middle anchored so ta const MAX_FONT_HEIGHT_RATIO = 1; // relative to the row height const MAX_FONT_SIZE = 12; -const mix = (a: number, b: number, x: number) => (1 - x) * a + x * b; // like the GLSL `mix` +const mix = (a: number = 1, b: number = 1, x: number = 1) => (1 - x) * a + x * b; // like the GLSL `mix` /** @internal */ export const drawCanvas2d = ( @@ -90,7 +90,7 @@ export const drawCanvas2d = ( ctx.fillStyle = textColor; lastTextColor = textColor; } - const textAlpha = color[i * 4 + 3]; + const textAlpha = color[i * 4 + 3] ?? 1; if (textAlpha !== lastTextAlpha) { // as we're sorting the iteration, the number of color changes (API calls) is minimized ctx.globalAlpha = textAlpha; diff --git a/packages/charts/src/chart_types/goal_chart/layout/viewmodel/geoms.ts b/packages/charts/src/chart_types/goal_chart/layout/viewmodel/geoms.ts index bb43af3ab7..6f705ff43d 100644 --- a/packages/charts/src/chart_types/goal_chart/layout/viewmodel/geoms.ts +++ b/packages/charts/src/chart_types/goal_chart/layout/viewmodel/geoms.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { getSagitta, getMinSagitta, getTranformDirection } from './utils'; +import { getSagitta, getMinSagitta, getTransformDirection } from './utils'; import { GOLDEN_RATIO, TAU } from '../../../../common/constants'; import { PointObject, Radian, Rectangle } from '../../../../common/geometry'; import { cssFontShorthand, Font } from '../../../../common/text_utils'; @@ -261,7 +261,7 @@ export function geoms( const circular = subtype === GoalSubtype.Goal; const vertical = subtype === GoalSubtype.VerticalBullet; - const domain = [lowestValue, highestValue]; + const domain: [number, number] = [lowestValue, highestValue]; const data = { base: { value: base }, ...Object.fromEntries(bands.map(({ value }, index) => [`qualitative_${index}`, { value }])), @@ -402,7 +402,7 @@ export function geoms( if (circular) { const sagitta = getMinSagitta(angleStart, angleEnd, r); const maxSagitta = getSagitta((3 / 2) * Math.PI, r); - const direction = getTranformDirection(angleStart, angleEnd); + const direction = getTransformDirection(angleStart, angleEnd); data.yOffset.value = Math.abs(sagitta) >= maxSagitta ? 0 : (direction * (maxSagitta - sagitta)) / 2; } diff --git a/packages/charts/src/chart_types/goal_chart/layout/viewmodel/utils.ts b/packages/charts/src/chart_types/goal_chart/layout/viewmodel/utils.ts index 21c82f211b..8d44dc976c 100644 --- a/packages/charts/src/chart_types/goal_chart/layout/viewmodel/utils.ts +++ b/packages/charts/src/chart_types/goal_chart/layout/viewmodel/utils.ts @@ -20,7 +20,7 @@ const LIMITING_ANGLE = Math.PI / 2; * Angles are relative to mathematical angles of a unit circle from -2π > θ > 2π */ const hasTopGap = (angleStart: Radian, angleEnd: Radian): boolean => { - const [a, b] = [angleStart, angleEnd].sort(); + const [a, b] = ([angleStart, angleEnd] as [number, number]).sort(); return a <= -Math.PI / 2 && a >= (-Math.PI * 3) / 2 && b >= -Math.PI / 2 && b <= Math.PI / 2; }; @@ -28,7 +28,7 @@ const hasTopGap = (angleStart: Radian, angleEnd: Radian): boolean => { * Angles are relative to mathematical angles of a unit circle from -2π > θ > 2π */ const hasBottomGap = (angleStart: Radian, angleEnd: Radian): boolean => { - const [a, b] = [angleStart, angleEnd].sort(); + const [a, b] = ([angleStart, angleEnd] as [number, number]).sort(); return a >= -Math.PI / 2 && a <= Math.PI / 2 && b < (Math.PI * 3) / 2 && b >= Math.PI / 2; }; @@ -36,7 +36,7 @@ const hasBottomGap = (angleStart: Radian, angleEnd: Radian): boolean => { * Angles are relative to mathematical angles of a unit circle from -2π > θ > 2π */ const isOnlyTopHalf = (angleStart: Radian, angleEnd: Radian): boolean => { - const [a, b] = [angleStart, angleEnd].sort(); + const [a, b] = ([angleStart, angleEnd] as [number, number]).sort(); return a >= 0 && b <= Math.PI; }; @@ -44,7 +44,7 @@ const isOnlyTopHalf = (angleStart: Radian, angleEnd: Radian): boolean => { * Angles are relative to mathematical angles of a unit circle from -2π > θ > 2π */ const isOnlyBottomHalf = (angleStart: Radian, angleEnd: Radian): boolean => { - const [a, b] = [angleStart, angleEnd].sort(); + const [a, b] = ([angleStart, angleEnd] as [number, number]).sort(); return (a >= Math.PI && b <= 2 * Math.PI) || (a >= -Math.PI && b <= 0); }; @@ -52,12 +52,12 @@ const isOnlyBottomHalf = (angleStart: Radian, angleEnd: Radian): boolean => { * Angles are relative to mathematical angles of a unit circle from -2π > θ > 2π */ const isWithinLimitedDomain = (angleStart: Radian, angleEnd: Radian): boolean => { - const [a, b] = [angleStart, angleEnd].sort(); + const [a, b] = ([angleStart, angleEnd] as [number, number]).sort(); return a > -2 * Math.PI && b < 2 * Math.PI; }; /** @internal */ -export const getTranformDirection = (angleStart: Radian, angleEnd: Radian): 1 | -1 => +export const getTransformDirection = (angleStart: Radian, angleEnd: Radian): 1 | -1 => hasTopGap(angleStart, angleEnd) || isOnlyBottomHalf(angleStart, angleEnd) ? -1 : 1; /** diff --git a/packages/charts/src/chart_types/goal_chart/state/chart_state.tsx b/packages/charts/src/chart_types/goal_chart/state/chart_state.tsx index d128fb0620..a778deb8fa 100644 --- a/packages/charts/src/chart_types/goal_chart/state/chart_state.tsx +++ b/packages/charts/src/chart_types/goal_chart/state/chart_state.tsx @@ -9,7 +9,7 @@ import React, { RefObject } from 'react'; import { getChartTypeDescriptionSelector } from './selectors/get_chart_type_description'; -import { getSpecOrNull } from './selectors/goal_spec'; +import { getGoalSpecSelector } from './selectors/get_goal_spec'; import { isTooltipVisibleSelector } from './selectors/is_tooltip_visible'; import { createOnElementClickCaller } from './selectors/on_element_click_caller'; import { createOnElementOutCaller } from './selectors/on_element_out_caller'; @@ -48,7 +48,7 @@ export class GoalState implements InternalChartState { } isInitialized(globalState: GlobalChartState) { - return getSpecOrNull(globalState) !== null ? InitStatus.Initialized : InitStatus.ChartNotInitialized; + return getGoalSpecSelector(globalState) !== null ? InitStatus.Initialized : InitStatus.ChartNotInitialized; } isBrushAvailable() { diff --git a/packages/charts/src/chart_types/goal_chart/state/selectors/geometries.ts b/packages/charts/src/chart_types/goal_chart/state/selectors/geometries.ts index 380b297784..d162ab8aa3 100644 --- a/packages/charts/src/chart_types/goal_chart/state/selectors/geometries.ts +++ b/packages/charts/src/chart_types/goal_chart/state/selectors/geometries.ts @@ -13,7 +13,7 @@ import { GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { getSpecs } from '../../../../state/selectors/get_specs'; -import { getSpecsFromStore } from '../../../../state/utils'; +import { getSpecFromStore } from '../../../../state/utils'; import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; import { geoms, Mark } from '../../layout/viewmodel/geoms'; import { GoalSpec } from '../../specs'; @@ -24,8 +24,8 @@ const getParentDimensions = (state: GlobalChartState) => state.parentDimensions; export const geometries = createCustomCachedSelector( [getSpecs, getParentDimensions, getChartThemeSelector], (specs, parentDimensions, theme): ShapeViewModel => { - const goalSpecs = getSpecsFromStore(specs, ChartType.Goal, SpecType.Series); - return goalSpecs.length === 1 ? render(goalSpecs[0], parentDimensions, theme) : nullShapeViewModel(theme); + const goalSpec = getSpecFromStore(specs, ChartType.Goal, SpecType.Series, false); + return goalSpec ? render(goalSpec, parentDimensions, theme) : nullShapeViewModel(theme); }, ); diff --git a/packages/charts/src/chart_types/goal_chart/state/selectors/get_chart_type_description.ts b/packages/charts/src/chart_types/goal_chart/state/selectors/get_chart_type_description.ts index 722ff0b646..447151c2f2 100644 --- a/packages/charts/src/chart_types/goal_chart/state/selectors/get_chart_type_description.ts +++ b/packages/charts/src/chart_types/goal_chart/state/selectors/get_chart_type_description.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { getSpecOrNull } from './goal_spec'; +import { getGoalSpecSelector } from './get_goal_spec'; import { createCustomCachedSelector } from '../../../../state/create_selector'; /** @internal */ -export const getChartTypeDescriptionSelector = createCustomCachedSelector([getSpecOrNull], (spec) => { +export const getChartTypeDescriptionSelector = createCustomCachedSelector([getGoalSpecSelector], (spec) => { return `${spec?.subtype ?? 'goal'} chart`; }); diff --git a/packages/charts/src/chart_types/goal_chart/state/selectors/goal_spec.ts b/packages/charts/src/chart_types/goal_chart/state/selectors/get_goal_spec.ts similarity index 67% rename from packages/charts/src/chart_types/goal_chart/state/selectors/goal_spec.ts rename to packages/charts/src/chart_types/goal_chart/state/selectors/get_goal_spec.ts index db7210caf2..1edb868006 100644 --- a/packages/charts/src/chart_types/goal_chart/state/selectors/goal_spec.ts +++ b/packages/charts/src/chart_types/goal_chart/state/selectors/get_goal_spec.ts @@ -9,11 +9,10 @@ import { ChartType } from '../../..'; import { SpecType } from '../../../../specs/constants'; import { GlobalChartState } from '../../../../state/chart_state'; -import { getSpecsFromStore } from '../../../../state/utils'; +import { getSpecFromStore } from '../../../../state/utils'; import { GoalSpec } from '../../specs'; /** @internal */ -export function getSpecOrNull(state: GlobalChartState): GoalSpec | null { - const specs = getSpecsFromStore(state.specs, ChartType.Goal, SpecType.Series); - return specs.length > 0 ? specs[0] : null; +export function getGoalSpecSelector(state: GlobalChartState): GoalSpec { + return getSpecFromStore(state.specs, ChartType.Goal, SpecType.Series, true); } diff --git a/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_click_caller.ts b/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_click_caller.ts index b1703941c8..90080bccd9 100644 --- a/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_click_caller.ts +++ b/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_click_caller.ts @@ -8,7 +8,7 @@ import { Selector } from 'reselect'; -import { getSpecOrNull } from './goal_spec'; +import { getGoalSpecSelector } from './get_goal_spec'; import { getPickedShapesLayerValues } from './picked_shapes'; import { ChartType } from '../../..'; import { getOnElementClickSelector } from '../../../../common/event_handler_selectors'; @@ -30,7 +30,7 @@ export function createOnElementClickCaller(): (state: GlobalChartState) => void return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartType.Goal) { selector = createCustomCachedSelector( - [getSpecOrNull, getLastClickSelector, getSettingsSpecSelector, getPickedShapesLayerValues], + [getGoalSpecSelector, getLastClickSelector, getSettingsSpecSelector, getPickedShapesLayerValues], getOnElementClickSelector(prev), ); } diff --git a/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_out_caller.ts b/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_out_caller.ts index 5cd7fcc2dc..dd89f4ab97 100644 --- a/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_out_caller.ts +++ b/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_out_caller.ts @@ -8,7 +8,7 @@ import { Selector } from 'react-redux'; -import { getSpecOrNull } from './goal_spec'; +import { getGoalSpecSelector } from './get_goal_spec'; import { getPickedShapesLayerValues } from './picked_shapes'; import { ChartType } from '../../..'; import { getOnElementOutSelector } from '../../../../common/event_handler_selectors'; @@ -28,7 +28,7 @@ export function createOnElementOutCaller(): (state: GlobalChartState) => void { return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartType.Goal) { selector = createCustomCachedSelector( - [getSpecOrNull, getPickedShapesLayerValues, getSettingsSpecSelector], + [getGoalSpecSelector, getPickedShapesLayerValues, getSettingsSpecSelector], getOnElementOutSelector(prev), ); } diff --git a/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_over_caller.ts b/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_over_caller.ts index 2eda55e35c..f26786618d 100644 --- a/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_over_caller.ts +++ b/packages/charts/src/chart_types/goal_chart/state/selectors/on_element_over_caller.ts @@ -8,7 +8,7 @@ import { Selector } from 'react-redux'; -import { getSpecOrNull } from './goal_spec'; +import { getGoalSpecSelector } from './get_goal_spec'; import { getPickedShapesLayerValues } from './picked_shapes'; import { ChartType } from '../../..'; import { getOnElementOverSelector } from '../../../../common/event_handler_selectors'; @@ -29,7 +29,7 @@ export function createOnElementOverCaller(): (state: GlobalChartState) => void { return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartType.Goal) { selector = createCustomCachedSelector( - [getSpecOrNull, getPickedShapesLayerValues, getSettingsSpecSelector], + [getGoalSpecSelector, getPickedShapesLayerValues, getSettingsSpecSelector], getOnElementOverSelector(prev), ); } diff --git a/packages/charts/src/chart_types/goal_chart/state/selectors/tooltip.ts b/packages/charts/src/chart_types/goal_chart/state/selectors/tooltip.ts index 56b6ae84ff..9aab386aa7 100644 --- a/packages/charts/src/chart_types/goal_chart/state/selectors/tooltip.ts +++ b/packages/charts/src/chart_types/goal_chart/state/selectors/tooltip.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { getSpecOrNull } from './goal_spec'; +import { getGoalSpecSelector } from './get_goal_spec'; import { getPickedShapes } from './picked_shapes'; import { Colors } from '../../../../common/colors'; import { TooltipInfo } from '../../../../components/tooltip/types'; @@ -25,7 +25,7 @@ const getBandColor = (value: number, bands: BandViewModel[]) => /** @internal */ export const getTooltipInfoSelector = createCustomCachedSelector( - [getSpecOrNull, getPickedShapes], + [getGoalSpecSelector, getPickedShapes], (spec, pickedShapes): TooltipInfo => { if (!spec) { return EMPTY_TOOLTIP; 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 0d04b1e833..f4608b9a95 100644 --- a/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts +++ b/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts @@ -299,8 +299,8 @@ export function shapeViewModel( const [start, end] = bound; const { left, top } = chartDimensions; - const topLeft = [Math.min(start.x, end.x) - left, Math.min(start.y, end.y) - top]; - const bottomRight = [Math.max(start.x, end.x) - left, Math.max(start.y, end.y) - top]; + const topLeft: [number, number] = [Math.min(start.x, end.x) - left, Math.min(start.y, end.y) - top]; + const bottomRight: [number, number] = [Math.max(start.x, end.x) - left, Math.max(start.y, end.y) - top]; // Find panel based on start pointer const { category: smHorizontalAccessorValue, panelOffset: hOffset } = getPanelPointCoordinate( @@ -359,14 +359,14 @@ export function shapeViewModel( ) => { const startValue = x[0]; const endValue = x[x.length - 1]; - const leftIndex = xValues.indexOf(startValue); - const rightIndex = xValues.indexOf(endValue) + (isRasterTimeScale(spec.xScale) && x.length > 1 ? 0 : 1); + const leftIndex = xValues.indexOf(startValue ?? NaN); + const rightIndex = xValues.indexOf(endValue ?? NaN) + (isRasterTimeScale(spec.xScale) && x.length > 1 ? 0 : 1); const isRightOutOfRange = rightIndex > xValues.length - 1 || rightIndex < 0; const isLeftOutOfRange = leftIndex > xValues.length - 1 || leftIndex < 0; - const startFromScale = xScale(isLeftOutOfRange ? xValues[0] : xValues[leftIndex]); - const endFromScale = xScale(isRightOutOfRange ? xValues[xValues.length - 1] : xValues[rightIndex]); + const startFromScale = xScale((isLeftOutOfRange ? xValues[0] : xValues[leftIndex]) ?? NaN); + const endFromScale = xScale((isRightOutOfRange ? xValues[xValues.length - 1] : xValues[rightIndex]) ?? NaN); if (startFromScale === undefined || endFromScale === undefined) { return null; @@ -423,7 +423,7 @@ export function shapeViewModel( ? undefined : { width: cellWidth, - x: chartDimensions.left + (xScale(xValues[index]) ?? NaN), + x: chartDimensions.left + (xScale(xValues[index] ?? NaN) ?? NaN), y: chartDimensions.top, height: chartDimensions.height, }; 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 247224aa82..7ef90004a8 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 @@ -123,7 +123,7 @@ export function renderHeatmapCanvas2d(ctx: CanvasRenderingContext2D, dpr: number { shouldAddEllipsis: true, wrapAtWord: false }, ).lines; // TODO improve the `wrapLines` code to handle results with short width - renderText(ctx, { x, y }, textLines.length > 0 ? textLines[0] : '…', font); + if (textLines[0]) renderText(ctx, { x, y }, textLines[0], font); }); }); }); @@ -149,14 +149,16 @@ export function renderHeatmapCanvas2d(ctx: CanvasRenderingContext2D, dpr: number 16, { shouldAddEllipsis: true, wrapAtWord: false }, ).lines; - renderText( - ctx, - { x, y }, - textLines.length > 0 ? textLines[0] : '…', - { ...theme.xAxisLabel, baseline: 'middle', align }, - // negative rotation due to the canvas rotation direction - radToDeg(-elementSizes.xLabelRotation), - ); + if (textLines[0]) { + renderText( + ctx, + { x, y }, + textLines[0], + { ...theme.xAxisLabel, baseline: 'middle', align }, + // negative rotation due to the canvas rotation direction + radToDeg(-elementSizes.xLabelRotation), + ); + } }); }); }); diff --git a/packages/charts/src/chart_types/heatmap/scales/band_color_scale.ts b/packages/charts/src/chart_types/heatmap/scales/band_color_scale.ts index d3e6ecf69a..9aa21b33e9 100644 --- a/packages/charts/src/chart_types/heatmap/scales/band_color_scale.ts +++ b/packages/charts/src/chart_types/heatmap/scales/band_color_scale.ts @@ -47,12 +47,7 @@ function getBandScale(bands: ColorBand[]): ColorScale { if (!isFiniteNumber(value)) { return Colors.Transparent.keyword; } - for (let i = 0; i < bands.length; i++) { - const { start, end, color } = bands[i]; - if (start <= value && value < end) { - return color; - } - } - return Colors.Transparent.keyword; + + return bands.find(({ start, end }) => start <= value && value < end)?.color ?? Colors.Transparent.keyword; }; } diff --git a/packages/charts/src/chart_types/heatmap/specs/heatmap.ts b/packages/charts/src/chart_types/heatmap/specs/heatmap.ts index 94daa1c941..3647855ae5 100644 --- a/packages/charts/src/chart_types/heatmap/specs/heatmap.ts +++ b/packages/charts/src/chart_types/heatmap/specs/heatmap.ts @@ -101,7 +101,8 @@ export interface HeatmapSpec extends Spec { yAxisLabelFormatter: LabelAccessor; } -const buildProps = buildSFProps()( +/** @internal */ +export const heatmapBuildProps = buildSFProps()( { chartType: ChartType.Heatmap, specType: SpecType.Series, @@ -133,13 +134,13 @@ const buildProps = buildSFProps()( export const Heatmap = function ( props: SFProps< HeatmapSpec, - keyof (typeof buildProps)['overrides'], - keyof (typeof buildProps)['defaults'], - keyof (typeof buildProps)['optionals'], - keyof (typeof buildProps)['requires'] + keyof (typeof heatmapBuildProps)['overrides'], + keyof (typeof heatmapBuildProps)['defaults'], + keyof (typeof heatmapBuildProps)['optionals'], + keyof (typeof heatmapBuildProps)['requires'] >, ) { - const { defaults, overrides } = buildProps; + const { defaults, overrides } = heatmapBuildProps; useSpecFactory>({ ...defaults, ...stripUndefined(props), ...overrides }); return null; }; diff --git a/packages/charts/src/chart_types/heatmap/state/chart_state.tsx b/packages/charts/src/chart_types/heatmap/state/chart_state.tsx index 4dd8cfb74f..eccd0dd0d1 100644 --- a/packages/charts/src/chart_types/heatmap/state/chart_state.tsx +++ b/packages/charts/src/chart_types/heatmap/state/chart_state.tsx @@ -16,7 +16,6 @@ import { getDebugStateSelector } from './selectors/get_debug_state'; import { getHeatmapTableSelector } from './selectors/get_heatmap_table'; import { getLegendItemsLabelsSelector } from './selectors/get_legend_items_labels'; import { getTooltipAnchorSelector } from './selectors/get_tooltip_anchor'; -import { getSpecOrNull } from './selectors/heatmap_spec'; import { isBrushAvailableSelector } from './selectors/is_brush_available'; import { isEmptySelector } from './selectors/is_empty'; import { isTooltipVisibleSelector } from './selectors/is_tooltip_visible'; @@ -55,8 +54,8 @@ export class HeatmapState implements InternalChartState { onPointerUpdate: (state: GlobalChartState) => void = createOnPointerUpdateCaller(); - isInitialized(globalState: GlobalChartState) { - return getSpecOrNull(globalState) !== null ? InitStatus.Initialized : InitStatus.ChartNotInitialized; + isInitialized() { + return InitStatus.Initialized; } isBrushAvailable(globalState: GlobalChartState) { diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/compute_legend.ts b/packages/charts/src/chart_types/heatmap/state/selectors/compute_legend.ts index cc4e3022df..11a47a60cc 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/compute_legend.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/compute_legend.ts @@ -7,7 +7,7 @@ */ import { getColorScale } from './get_color_scale'; -import { getSpecOrNull } from './heatmap_spec'; +import { getHeatmapSpecSelector } from './get_heatmap_spec'; import { isEmptySelector } from './is_empty'; import { LegendItem } from '../../../../common/legend'; import { createCustomCachedSelector } from '../../../../state/create_selector'; @@ -16,7 +16,7 @@ import { getDeselectedSeriesSelector } from '../../../../state/selectors/get_des const EMPTY_LEGEND: LegendItem[] = []; /** @internal */ export const computeLegendSelector = createCustomCachedSelector( - [getSpecOrNull, getColorScale, getDeselectedSeriesSelector, isEmptySelector], + [getHeatmapSpecSelector, getColorScale, getDeselectedSeriesSelector, isEmptySelector], (spec, { bands }, deselectedDataSeries, empty): LegendItem[] => { if (spec === null || empty) { return EMPTY_LEGEND; 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 668b65456b..077a92ff0e 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 @@ -96,8 +96,8 @@ export const getDebugStateSelector = createCustomCachedSelector( function getLegendState(legendItems: LegendItem[]): DebugStateLegend { const items = legendItems .filter(({ isSeriesHidden }) => !isSeriesHidden) - .map(({ label: name, color, seriesIdentifiers: [{ key }] }) => ({ - key, + .map(({ label: name, color, seriesIdentifiers: [seriesIdentifier] }) => ({ + key: seriesIdentifier?.key ?? '', name, color, })); 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 d508d7436b..59b9598589 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 @@ -10,11 +10,10 @@ import { ChartType } from '../../..'; import { SpecType } from '../../../../specs'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getSpecs } from '../../../../state/selectors/get_specs'; -import { getSpecsFromStore } from '../../../../state/utils'; +import { getSpecFromStore } from '../../../../state/utils'; import { HeatmapSpec } from '../../specs'; /** @internal */ export const getHeatmapSpecSelector = createCustomCachedSelector([getSpecs], (specs): HeatmapSpec => { - const spec = getSpecsFromStore(specs, ChartType.Heatmap, SpecType.Series); - return spec[0]; + return getSpecFromStore(specs, ChartType.Heatmap, SpecType.Series, true); }); diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_tooltip_anchor.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_tooltip_anchor.ts index cb1dfa4116..70b84f84c3 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_tooltip_anchor.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_tooltip_anchor.ts @@ -24,16 +24,15 @@ export const getTooltipAnchorSelector = createCustomCachedSelector( getChartThemeSelector, ], (shapes, { chartDimensions }, position, smScales, { heatmap }): AnchorPosition => { - if (Array.isArray(shapes) && shapes.length > 0) { - const [ - { - x, - y, - width, - height, - datum: { smHorizontalAccessorValue = '', smVerticalAccessorValue = '' }, - }, - ] = shapes; + const shape = Array.isArray(shapes) && shapes[0]; + if (shape) { + const { + x, + y, + width, + height, + datum: { smHorizontalAccessorValue = '', smVerticalAccessorValue = '' }, + } = shape; const scaledPanelXOffset = smScales.horizontal.scale(smHorizontalAccessorValue); const scaledPanelYOffset = smScales.vertical.scale(smVerticalAccessorValue); diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/heatmap_spec.ts b/packages/charts/src/chart_types/heatmap/state/selectors/heatmap_spec.ts deleted file mode 100644 index a34755931e..0000000000 --- a/packages/charts/src/chart_types/heatmap/state/selectors/heatmap_spec.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 { ChartType } from '../../..'; -import { SpecType } from '../../../../specs/constants'; -import { GlobalChartState } from '../../../../state/chart_state'; -import { getSpecsFromStore } from '../../../../state/utils'; -import { HeatmapSpec } from '../../specs/heatmap'; - -/** @internal */ -export function getSpecOrNull(state: GlobalChartState): HeatmapSpec | null { - const specs = getSpecsFromStore(state.specs, ChartType.Heatmap, SpecType.Series); - return specs.length > 0 ? specs[0] : null; -} 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 221c342a7c..32677c4e82 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 @@ -9,7 +9,6 @@ import { OutputSelector } from 'reselect'; import { getPickedCells } from './get_picked_cells'; -import { getSpecOrNull } from './heatmap_spec'; import { isBrushEndProvided } from './is_brush_available'; import { ChartType } from '../../..'; import { HeatmapBrushEvent, SettingsSpec } from '../../../../specs/settings'; @@ -18,7 +17,6 @@ import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getLastDragSelector } from '../../../../state/selectors/get_last_drag'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_spec'; import { DragCheckProps, hasDragged } from '../../../../utils/events'; -import { HeatmapSpec } from '../../specs'; /** * Will call the onBrushEnd listener every time the following preconditions are met: @@ -31,7 +29,7 @@ export function createOnBrushEndCaller(): (state: GlobalChartState) => void { let selector: OutputSelector< GlobalChartState, void, - (res1: DragState | null, res2: HeatmapSpec | null, res3: SettingsSpec, res4: HeatmapBrushEvent | null) => void + (res1: DragState | null, res3: SettingsSpec, res4: HeatmapBrushEvent | null) => void > | null = null; return (state) => { @@ -42,13 +40,13 @@ export function createOnBrushEndCaller(): (state: GlobalChartState) => void { return; } selector = createCustomCachedSelector( - [getLastDragSelector, getSpecOrNull, getSettingsSpecSelector, getPickedCells], - (lastDrag, spec, { onBrushEnd }, pickedCells): void => { + [getLastDragSelector, getSettingsSpecSelector, getPickedCells], + (lastDrag, { onBrushEnd }, pickedCells): void => { const nextProps: DragCheckProps = { lastDrag, onBrushEnd, }; - if (!spec || !onBrushEnd || pickedCells === null) { + if (!onBrushEnd || pickedCells === null) { return; } if (lastDrag !== null && hasDragged(prevProps, nextProps)) { diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/on_element_click_caller.ts b/packages/charts/src/chart_types/heatmap/state/selectors/on_element_click_caller.ts index 97bcb253fd..73a240256b 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/on_element_click_caller.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/on_element_click_caller.ts @@ -8,7 +8,7 @@ import { Selector } from 'reselect'; -import { getSpecOrNull } from './heatmap_spec'; +import { getHeatmapSpecSelector } from './get_heatmap_spec'; import { getPickedShapes } from './picked_shapes'; import { ChartType } from '../../..'; import { SeriesIdentifier } from '../../../../common/series_id'; @@ -33,7 +33,7 @@ export function createOnElementClickCaller(): (state: GlobalChartState) => void return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartType.Heatmap) { selector = createCustomCachedSelector( - [getSpecOrNull, getLastClickSelector, getSettingsSpecSelector, getPickedShapes], + [getHeatmapSpecSelector, getLastClickSelector, getSettingsSpecSelector, getPickedShapes], (spec, lastClick: PointerState | null, settings: SettingsSpec, pickedShapes): void => { if (!spec) { return; diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/on_element_out_caller.ts b/packages/charts/src/chart_types/heatmap/state/selectors/on_element_out_caller.ts index 70e8eba54d..b799c07e0b 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/on_element_out_caller.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/on_element_out_caller.ts @@ -8,7 +8,7 @@ import { Selector } from 'react-redux'; -import { getSpecOrNull } from './heatmap_spec'; +import { getHeatmapSpecSelector } from './get_heatmap_spec'; import { getPickedShapes } from './picked_shapes'; import { ChartType } from '../../..'; import { GlobalChartState } from '../../../../state/chart_state'; @@ -28,7 +28,7 @@ export function createOnElementOutCaller(): (state: GlobalChartState) => void { return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartType.Heatmap) { selector = createCustomCachedSelector( - [getSpecOrNull, getPickedShapes, getSettingsSpecSelector], + [getHeatmapSpecSelector, getPickedShapes, getSettingsSpecSelector], (spec, pickedShapes, settings): void => { if (!spec) { return; diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/on_element_over_caller.ts b/packages/charts/src/chart_types/heatmap/state/selectors/on_element_over_caller.ts index bf387f8a4e..2b7a8599c0 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/on_element_over_caller.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/on_element_over_caller.ts @@ -8,13 +8,14 @@ import { Selector } from 'react-redux'; -import { getSpecOrNull } from './heatmap_spec'; +import { getHeatmapSpecSelector } from './get_heatmap_spec'; import { getPickedShapes } from './picked_shapes'; import { ChartType } from '../../..'; import { SeriesIdentifier } from '../../../../common/series_id'; import { GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_spec'; +import { isNil } from '../../../../utils/common'; import { Cell, isPickedCells } from '../../layout/types/viewmodel_types'; function isOverElement(prev: Cell[], next: Cell[]) { @@ -26,7 +27,7 @@ function isOverElement(prev: Cell[], next: Cell[]) { } return !next.every((nextCell, index) => { const prevCell = prev[index]; - if (prevCell === null) { + if (isNil(prevCell)) { return false; } return nextCell.value === prevCell.value && nextCell.x === prevCell.x && nextCell.y === prevCell.y; @@ -45,7 +46,7 @@ export function createOnElementOverCaller(): (state: GlobalChartState) => void { return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartType.Heatmap) { selector = createCustomCachedSelector( - [getSpecOrNull, getPickedShapes, getSettingsSpecSelector], + [getHeatmapSpecSelector, getPickedShapes, getSettingsSpecSelector], (spec, nextPickedShapes, settings): void => { if (!spec) { return; diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/on_pointer_update_caller.ts b/packages/charts/src/chart_types/heatmap/state/selectors/on_pointer_update_caller.ts index 5eb2efacc2..33d14d7cea 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/on_pointer_update_caller.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/on_pointer_update_caller.ts @@ -8,7 +8,7 @@ import { Selector } from 'reselect'; -import { getSpecOrNull } from './heatmap_spec'; +import { getHeatmapSpecSelector } from './get_heatmap_spec'; import { getPickedGridCell } from './picked_shapes'; import { ChartType } from '../../..'; import { @@ -18,11 +18,10 @@ import { PointerUpdateTrigger, SettingsSpec, } from '../../../../specs'; -import { GlobalChartState, PointerState } from '../../../../state/chart_state'; +import { GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getActivePointerPosition } from '../../../../state/selectors/get_active_pointer_position'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { getLastClickSelector } from '../../../../state/selectors/get_last_click'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_spec'; function isSameEventValue(a: PointerOverEvent, b: PointerOverEvent, changeTrigger: PointerUpdateTrigger) { @@ -55,14 +54,13 @@ export function createOnPointerUpdateCaller(): (state: GlobalChartState) => void if (selector === null && state.chartType === ChartType.Heatmap) { selector = createCustomCachedSelector( [ - getSpecOrNull, - getLastClickSelector, + getHeatmapSpecSelector, getSettingsSpecSelector, getActivePointerPosition, getPickedGridCell, getChartIdSelector, ], - (spec, lastClick: PointerState | null, settings: SettingsSpec, currentPointer, gridCell, chartId): void => { + (spec, settings: SettingsSpec, currentPointer, gridCell, chartId): void => { if (!spec) { return; } 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 a055b5d11d..166382cc95 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/tooltip.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/tooltip.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { getSpecOrNull } from './heatmap_spec'; +import { getHeatmapSpecSelector } from './get_heatmap_spec'; import { getPickedShapes } from './picked_shapes'; import { RGBATupleToString } from '../../../../common/color_library_wrappers'; import { Colors } from '../../../../common/colors'; @@ -21,7 +21,7 @@ const EMPTY_TOOLTIP = Object.freeze({ /** @internal */ export const getTooltipInfoSelector = createCustomCachedSelector( - [getSpecOrNull, getPickedShapes], + [getHeatmapSpecSelector, getPickedShapes], (spec, pickedShapes): TooltipInfo => { if (!spec) { return EMPTY_TOOLTIP; diff --git a/packages/charts/src/chart_types/heatmap/state/utils/axis.ts b/packages/charts/src/chart_types/heatmap/state/utils/axis.ts index 6936bf5920..c2adc3469d 100644 --- a/packages/charts/src/chart_types/heatmap/state/utils/axis.ts +++ b/packages/charts/src/chart_types/heatmap/state/utils/axis.ts @@ -269,16 +269,19 @@ function computeCompressedScale( if (i >= itemsPerSideSize.length - 2) { return false; } - const currentItemRightSide = domainPositions[i] * scaleMultiplier + rightSide + pad(style.padding, 'right'); + + const currentItemRightSide = (domainPositions[i] ?? 0) * scaleMultiplier + rightSide + pad(style.padding, 'right'); const nextItemLeftSize = - domainPositions[i + 1] * scaleMultiplier - itemsPerSideSize[i + 1][0] - pad(style.padding, 'left'); + (domainPositions[i + 1] ?? 0) * scaleMultiplier - + (itemsPerSideSize[i + 1]?.[0] ?? 0) - + pad(style.padding, 'left'); return currentItemRightSide > nextItemLeftSize; }); const leftMargin = isFiniteNumber(bounds[0]) - ? globalItemWidth[bounds[0]][0] - scaleMultiplier * globalDomainPositions[bounds[0]] + ? (globalItemWidth[bounds[0]]?.[0] ?? 0) - scaleMultiplier * (globalDomainPositions[bounds[0]] ?? 0) : 0; - const rightMargin = isFiniteNumber(bounds[1]) ? globalItemWidth[bounds[1]][1] : 0; + const rightMargin = isFiniteNumber(bounds[1]) ? globalItemWidth[bounds[1]]?.[1] ?? 0 : 0; return { // the horizontal space diff --git a/packages/charts/src/chart_types/metric/renderer/dom/index.tsx b/packages/charts/src/chart_types/metric/renderer/dom/index.tsx index bf7a4b82a2..1b7e9c9d36 100644 --- a/packages/charts/src/chart_types/metric/renderer/dom/index.tsx +++ b/packages/charts/src/chart_types/metric/renderer/dom/index.tsx @@ -70,17 +70,17 @@ class Component extends React.Component { initialized, size: { width, height }, a11y, - specs, + specs: [spec], // ignoring other specs style, onElementClick, onElementOut, onElementOver, } = this.props; - if (!initialized || specs.length === 0 || width === 0 || height === 0) { + if (!initialized || !spec || width === 0 || height === 0) { return null; } - // ignoring other specs - const { data } = specs[0]; + + const { data } = spec; const totalRows = data.length; const maxColumns = data.reduce((acc, row) => { diff --git a/packages/charts/src/chart_types/metric/renderer/dom/text.tsx b/packages/charts/src/chart_types/metric/renderer/dom/text.tsx index 4784c49da7..a17c36f623 100644 --- a/packages/charts/src/chart_types/metric/renderer/dom/text.tsx +++ b/packages/charts/src/chart_types/metric/renderer/dom/text.tsx @@ -53,7 +53,7 @@ const SUBTITLE_FONT: Font = { function findRange(ranges: [number, number, BreakPoint][], value: number): BreakPoint { const range = ranges.find(([min, max]) => min <= value && value < max); - return range ? range[2] : ranges[0][2]; + return range ? range[2] : ranges[0]?.[2] ?? 's'; } type ElementVisibility = { @@ -117,7 +117,7 @@ function elementVisibility( return withTextMeasure((textMeasure) => { const visibilityBreakpoint = responsiveBreakPoints.find((breakpoint) => isVisible(breakpoint, textMeasure)) ?? - responsiveBreakPoints[responsiveBreakPoints.length - 1]; + responsiveBreakPoints[responsiveBreakPoints.length - 1]!; return { ...visibilityBreakpoint, titleLines: wrapText( @@ -283,8 +283,8 @@ function splitNumericSuffixPrefix(text: string): { emphasis: 'normal' | 'small'; .split('') .reduce<{ emphasis: 'normal' | 'small'; textParts: string[] }[]>((acc, curr) => { const emphasis = curr === '.' || curr === ',' || isFiniteNumber(Number.parseInt(curr)) ? 'normal' : 'small'; - if (acc.length > 0 && acc[acc.length - 1].emphasis === emphasis) { - acc[acc.length - 1].textParts.push(curr); + if (acc.length > 0 && acc[acc.length - 1]?.emphasis === emphasis) { + acc[acc.length - 1]?.textParts.push(curr); } else { acc.push({ emphasis, textParts: [curr] }); } diff --git a/packages/charts/src/chart_types/partition_chart/layout/utils/circline_geometry.ts b/packages/charts/src/chart_types/partition_chart/layout/utils/circline_geometry.ts index 80af3ef18a..2398278796 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/utils/circline_geometry.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/utils/circline_geometry.ts @@ -85,8 +85,8 @@ function circlineValidSectors(refC: CirclinePredicate, c: CirclineArc): Circline // These conditions don't happen; kept for documentation purposes: // if (circlineIntersections.length !== 2) throw new Error('Problem in intersection calculation.') // if (from > to) throw new Error('From/to problem in intersection calculation.') - if (circlineIntersections.length !== 2) return []; const [p1, p2] = circlineIntersections; + if (!p1 || !p2) return []; const aPre1 = Math.atan2(p1.y - c.y, p1.x - c.x); const aPre2 = Math.atan2(p2.y - c.y, p2.x - c.x); const a1p = Math.max(from, Math.min(to, aPre1 < 0 ? aPre1 + TAU : aPre1)); @@ -105,10 +105,8 @@ function circlineValidSectors(refC: CirclinePredicate, c: CirclineArc): Circline // imperative, slightly optimized buildup of `result` as it's in the hot loop: const result = []; for (let i = 0; i < breakpoints.length - 1; i++) { - // eslint-disable-next-line no-shadow - const from = breakpoints[i]; - // eslint-disable-next-line no-shadow - const to = breakpoints[i + 1]; + const from = breakpoints[i] ?? 0; + const to = breakpoints[i + 1] ?? 0; const midAngle = (from + to) / 2; // no winding clip ie. `meanAngle()` would be wrong here const xx = x + r * Math.cos(midAngle); const yy = y + r * Math.sin(midAngle); @@ -124,8 +122,10 @@ export function conjunctiveConstraint(constraints: RingSectorConstruction, c: Ci for (let i = 0; i < constraints.length; i++) { const refC = constraints[i]; // reference circle const nextValids: CirclineArc[] = []; + if (!refC) break; for (let j = 0; j < valids.length; j++) { const cc = valids[j]; + if (!cc) continue; const currentValids = circlineValidSectors(refC, cc); nextValids.push(...currentValids); } diff --git a/packages/charts/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts b/packages/charts/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts index a736729660..90b03e3bfe 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts @@ -127,10 +127,10 @@ export function pathAccessor(n: ArrayEntry) { } /** @public */ -export function getNodeName(node: ArrayNode) { +export function getNodeName(node: ArrayNode): string { const index = node[SORT_INDEX_KEY]; - const arrayEntry: ArrayEntry = node[PARENT_KEY][CHILDREN_KEY][index]; - return entryKey(arrayEntry); + const arrayEntry = node[PARENT_KEY][CHILDREN_KEY][index]; + return arrayEntry ? entryKey(arrayEntry) : ''; } /** @internal */ @@ -242,7 +242,7 @@ export function mapsToArrays( mapNode[PATH_KEY] = newPath; // in-place mutation, so disabled `no-param-reassign` mapNode.children.forEach((entry) => buildPaths(entry, newPath)); }; - buildPaths(tree[0], innerGroups); + if (tree[0]) buildPaths(tree[0], innerGroups); return tree; } diff --git a/packages/charts/src/chart_types/partition_chart/layout/utils/highlighted_geoms.ts b/packages/charts/src/chart_types/partition_chart/layout/utils/highlighted_geoms.ts index efe8112406..cd21ad368b 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/utils/highlighted_geoms.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/utils/highlighted_geoms.ts @@ -31,13 +31,13 @@ const legendStrategies: Record = { (legendPath) => ({ path, dataName }) => // highlight all identically named items which are within the same depth (ring) as the hovered legend depth - legendPath.length === path.length && dataName === legendPath[legendPath.length - 1].value, + legendPath.length === path.length && dataName === legendPath[legendPath.length - 1]?.value, key: (legendPath) => ({ dataName }) => // highlight all identically named items, no matter where they are - dataName === legendPath[legendPath.length - 1].value, + dataName === legendPath[legendPath.length - 1]?.value, nodeWithDescendants: (legendPath) => diff --git a/packages/charts/src/chart_types/partition_chart/layout/utils/sunburst.ts b/packages/charts/src/chart_types/partition_chart/layout/utils/sunburst.ts index b11bd702ec..26b0af5985 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/utils/sunburst.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/utils/sunburst.ts @@ -26,6 +26,7 @@ export function sunburst( for (let i = 0; i < nodeCount; i++) { const index = clockwiseSectors ? i : nodeCount - i - 1; const node = nodes[depth === 1 && specialFirstInnermostSector ? (index + 1) % nodeCount : index]; + if (!node) continue; const area = areaAccessor(node); result.push({ node, x0: currentOffsetX, y0, x1: currentOffsetX + area, y1: y0 + heightStep }); const children = childrenAccessor(node); diff --git a/packages/charts/src/chart_types/partition_chart/layout/utils/treemap.ts b/packages/charts/src/chart_types/partition_chart/layout/utils/treemap.ts index 5e22b03ed1..9f1ec62684 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/utils/treemap.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/utils/treemap.ts @@ -76,7 +76,7 @@ function bestVector( nodes: HierarchyOfArrays, height: number, areaAccessor: (e: ArrayEntry) => number, - layout: LayerLayout, + layout: LayerLayout | null, ): LayoutElement { let previousWorstAspectRatio = -1; let currentWorstAspectRatio = 0; @@ -97,10 +97,12 @@ function bestVector( function vectorNodeCoordinates(vectorLayout: LayoutElement, x0Base: number, y0Base: number, vertical: boolean) { const { nodes, dependentSize, sectionSizes, sectionOffsets } = vectorLayout; return nodes.map((e: ArrayEntry, i: number) => { - const x0 = vertical ? x0Base + sectionOffsets[i] : x0Base; - const y0 = vertical ? y0Base : y0Base + sectionOffsets[i]; - const x1 = vertical ? x0 + sectionSizes[i] : x0 + dependentSize; - const y1 = vertical ? y0 + dependentSize : y0 + sectionSizes[i]; + const offset = sectionOffsets[i] ?? 0; + const size = sectionSizes[i] ?? 0; + const x0 = vertical ? x0Base + offset : x0Base; + const y0 = vertical ? y0Base : y0Base + offset; + const x1 = vertical ? x0 + size : x0 + dependentSize; + const y1 = vertical ? y0 + dependentSize : y0 + size; return { node: e, x0, y0, x1, y1 }; }); } @@ -125,7 +127,7 @@ export function treemap( ): Array { if (nodes.length === 0) return []; // some bias toward horizontal rectangles with a golden ratio of width to height - const depth = nodes[0][1][DEPTH_KEY] - 1; + const depth = (nodes[0]?.[1][DEPTH_KEY] ?? 1) - 1; const layerLayout = layouts[depth] ?? null; const vertical = layerLayout === LayerLayout.vertical || (!layerLayout && outerWidth / GOLDEN_RATIO <= outerHeight); const independentSize = vertical ? outerWidth : outerHeight; diff --git a/packages/charts/src/chart_types/partition_chart/layout/utils/waffle.ts b/packages/charts/src/chart_types/partition_chart/layout/utils/waffle.ts index bb66444809..1d10967213 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/utils/waffle.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/utils/waffle.ts @@ -24,6 +24,9 @@ export function waffle( height: outerHeight, }: { x0: number; y0: number; width: number; height: number }, ): Array { + const root = tree[0]; + if (!root || !root[1]) return []; + const size = Math.min(outerWidth, outerHeight); const widthOffset = Math.max(0, outerWidth - size) / 2; const heightOffset = Math.max(0, outerHeight - size) / 2; @@ -33,7 +36,7 @@ export function waffle( const valuePerCell = totalValue / cellCount; let valueSoFar = 0; let lastIndex = 0; - const root = tree[0]; + return [ { node: root, x0: 0, y0: 0, x1: size, y1: size }, ...root[1][CHILDREN_KEY].flatMap((entry) => { diff --git a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/fill_text_layout.ts b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/fill_text_layout.ts index e6829993f0..9348248dfa 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/fill_text_layout.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/fill_text_layout.ts @@ -210,8 +210,6 @@ type GetShapeRowGeometry = ( type ShapeConstructor = (n: ShapeTreeNode) => C; -type NodeWithOrigin = { node: QuadViewModel; origin: PointTuple }; - function fill( shapeConstructor: ShapeConstructor, getShapeRowGeometry: GetShapeRowGeometry, @@ -233,13 +231,18 @@ function fill( const container = shapeConstructor(node); const rotation = getRotation(node); - const layer = layers[node.depth - 1] || {}; + const layer = layers[node.depth - 1]; + + if (!layer) { + throw new Error(`Failed to find layer at ${node.depth - 1}`); + } + const verticalAlignment = middleAlign ? VerticalAlignments.middle : node.depth < layers.length ? VerticalAlignments.bottom : VerticalAlignments.top; - const fontSizes = allFontSizes[Math.min(node.depth, allFontSizes.length) - 1]; + const fontSizes = allFontSizes[Math.min(node.depth, allFontSizes.length) - 1] ?? []; const { fontStyle, fontVariant, fontFamily, fontWeight, valueFormatter, padding, clipText } = { ...fillLabel, valueFormatter: formatter, @@ -355,6 +358,12 @@ function tryFontSize( // iterate through rows while (currentRowIndex < targetRowCount) { const currentRow = rowSet.rows[currentRowIndex]; + + if (!currentRow) { + currentRowIndex++; + continue; + } + const currentRowWords = currentRow.rowWords; // current row geometries @@ -383,6 +392,7 @@ function tryFontSize( while (measuredBoxes.length > 0 && rowHasRoom) { // adding box to row const [currentBox] = measuredBoxes; + if (!currentBox) continue; const wordBeginning = currentRowLength; currentRowLength += currentBox.width + wordSpacing; @@ -441,14 +451,15 @@ function getRowSet( // find largest fitting font size const largestIndex = fontSizes.length - 1; - const response = (i: number) => i + (tryFunction(identityRowSet(), fontSizes[i]).completed ? 0 : largestIndex + 1); + const response = (i: number) => + i + (tryFunction(identityRowSet(), fontSizes[i] ?? 0).completed ? 0 : largestIndex + 1); const fontSizeIndex = monotonicHillClimb(response, largestIndex, largestIndex, integerSnap); if (!(fontSizeIndex >= 0)) { return identityRowSet(); } - const { rowSet, completed } = tryFunction(identityRowSet(), fontSizes[fontSizeIndex]); // todo in the future, make the hill climber also yield the result to avoid this +1 call + const { rowSet, completed } = tryFunction(identityRowSet(), fontSizes[fontSizeIndex] ?? 0); // todo in the future, make the hill climber also yield the result to avoid this +1 call return { ...rowSet, rows: rowSet.rows.filter((r) => completed && Number.isFinite(r.length)) }; } @@ -490,7 +501,7 @@ export function fillTextLayout( // get font size spec from config, which layer.fillLabel properties can override const { minFontSize, maxFontSize, idealFontSizeJump } = { ...style, - ...(l < layers.length && layers[l].fillLabel), + ...(l < layers.length && layers[l]?.fillLabel), }; const fontSizeMagnification = maxFontSize / minFontSize; const fontSizeJumpCount = Math.round(logarithm(idealFontSizeJump, fontSizeMagnification)); @@ -519,12 +530,10 @@ export function fillTextLayout( return childNodes .map((node: QuadViewModel, i: number) => ({ node, origin: textFillOrigins[i] })) - .sort((a: NodeWithOrigin, b: NodeWithOrigin) => b.node.value - a.node.value) - .reduce( - ( - { rowSets, fontSizes }: { rowSets: RowSet[]; fontSizes: Pixels[][] }, - { node, origin }: { node: QuadViewModel; origin: [Pixels, Pixels] }, - ) => { + .sort((a, b) => b.node.value - a.node.value) + .reduce<{ rowSets: RowSet[]; fontSizes: Pixels[][] }>( + ({ rowSets, fontSizes }, { node, origin }) => { + if (!origin) return { rowSets, fontSizes }; const nextRowSet = filler(fontSizes, origin, node); const { fontSize } = nextRowSet; const layerIndex = node.depth - 1; @@ -537,7 +546,7 @@ export function fillTextLayout( ), }; }, - { rowSets: [] as RowSet[], fontSizes: allFontSizes }, + { rowSets: [], fontSizes: allFontSizes }, ).rowSets; }; } diff --git a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.test.ts b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.test.ts index 4abb6b099a..01ea83d037 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.test.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.test.ts @@ -24,7 +24,7 @@ describe('Test', () => { test('getHierarchyOfArrays should omit negative values', () => { const outerResult = getHierarchyOfArrays(rawFacts, valueAccessor, groupByRollupAccessors, [], []); expect(outerResult.length).toBe(1); - const results = entryValue(outerResult[0]); + const results = entryValue(outerResult[0]!); expect(results[CHILDREN_KEY].length).toBe(rawFacts.filter((d: any) => valueAccessor(d) >= 0).length); }); }); diff --git a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.ts b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.ts index da141fe5b5..9c47ae3261 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.ts @@ -113,6 +113,7 @@ export function getExtraValueMap( ): Map { for (let i = 0; i < tree.length; i++) { const branch = tree[i]; + if (!branch) continue; const [key, arrayNode] = branch; const { value, path, [CHILDREN_KEY]: children } = arrayNode; const values: LegendItemExtraValues = new Map(); diff --git a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/link_text_layout.ts b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/link_text_layout.ts index c832aad87a..a9afa69cbe 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/link_text_layout.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/link_text_layout.ts @@ -155,7 +155,7 @@ function nodeToLinkLabel({ // calculate and remember vertical offset, as linked labels accrete const poolIndex = rightSide + (1 - north); const relativeY = north * y; - const yOffset = Math.max(currentY[poolIndex] + rowPitch, relativeY + yRelativeIncrement, rowPitch / 2); + const yOffset = Math.max((currentY[poolIndex] ?? 0) + rowPitch, relativeY + yRelativeIncrement, rowPitch / 2); currentY[poolIndex] = yOffset; // more geometry: the part that depends on vertical position diff --git a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/picked_shapes.ts b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/picked_shapes.ts index 0f673d806a..9ed07495da 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/picked_shapes.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/picked_shapes.ts @@ -14,7 +14,7 @@ import { SpecId } from '../../../../utils/ids'; import { Point } from '../../../../utils/point'; import { ContinuousDomainFocus } from '../../renderer/canvas/partition'; import { MODEL_KEY, percentValueGetter } from '../config'; -import { QuadViewModel, ShapeViewModel, ValueGetter } from '../types/viewmodel_types'; +import { QuadViewModel, ShapeViewModel } from '../types/viewmodel_types'; import { AGGREGATE_KEY, ArrayNode, @@ -31,9 +31,9 @@ import { export const pickedShapes = ( models: ShapeViewModel[], { x, y }: Point, - foci: ContinuousDomainFocus[], + [focus]: ContinuousDomainFocus[], ): QuadViewModel[] => - models.flatMap(({ diskCenter, pickQuads }) => pickQuads(x - diskCenter.x, y - diskCenter.y, foci[0])); + focus ? models.flatMap(({ diskCenter, pickQuads }) => pickQuads(x - diskCenter.x, y - diskCenter.y, focus)) : []; /** @internal */ export function pickShapesLayerValues(shapes: QuadViewModel[]): LayerValue[][] { @@ -74,7 +74,6 @@ export function pickShapesLayerValues(shapes: QuadViewModel[]): LayerValue[][] { export function pickShapesTooltipValues( shapes: QuadViewModel[], shapeViewModel: ShapeViewModel[], - valueGetter: ValueGetter, valueFormatter: ValueFormatter, percentFormatter: ValueFormatter, id: SpecId, @@ -91,9 +90,11 @@ export function pickShapesTooltipValues( values: shapes .filter(({ depth }) => depth === maxDepth) // eg. lowest layer in a treemap, where layers overlap in screen space; doesn't apply to sunburst/flame .flatMap((viewModel) => { + const entryNode = viewModel[PARENT_KEY][CHILDREN_KEY][viewModel[SORT_INDEX_KEY]]; + if (!entryNode) return []; const values: TooltipValue[] = [ getTooltipValueFromNode( - entryValue(viewModel[PARENT_KEY][CHILDREN_KEY][viewModel[SORT_INDEX_KEY]]), + entryValue(entryNode), labelFormatters, valueFormatter, percentFormatter, diff --git a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/scenegraph.ts b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/scenegraph.ts index 3913cac679..f5de36c043 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/scenegraph.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/scenegraph.ts @@ -24,7 +24,7 @@ import { DEPTH_KEY, HierarchyOfArrays } from '../utils/group_by_rollup'; function rawTextGetter(layers: Layer[]): RawTextGetter { return (node: ShapeTreeNode) => { - const accessorFn = layers[node[DEPTH_KEY] - 1].nodeLabel || ((d) => d); + const accessorFn = layers[node[DEPTH_KEY] - 1]?.nodeLabel || ((d) => d); return `${accessorFn(node.dataName)}`; }; } diff --git a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts index a767fbc096..eb7c0639fc 100644 --- a/packages/charts/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts +++ b/packages/charts/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts @@ -94,7 +94,7 @@ export const isSimpleLinear = (layout: PartitionLayout, fillLabel: FillLabelConf isLinear(layout) && layers.every((l) => l.fillLabel?.clipText ?? fillLabel?.clipText); function grooveAccessor(n: ArrayEntry) { - return entryValue(n).depth > 1 ? 1 : [0, 2][entryValue(n).depth]; + return entryValue(n).depth > 1 ? 1 : [0, 2][entryValue(n).depth] ?? NaN; } function topGrooveAccessor(topGroovePx: Pixels) { @@ -158,16 +158,12 @@ export function makeQuadViewModel( return childNodes.map((node) => { const layer = layers[node.depth - 1]; const fill = layer?.shape?.fillColor ?? RGBATupleToString(Colors.DarkOpaqueRed.rgba); - - const fillColor = - typeof fill === 'function' - ? fill( - node.dataName, - node.sortIndex, - entryValue(node[MODEL_KEY][CHILDREN_KEY][node[SORT_INDEX_KEY]]), - node[MODEL_KEY].children, - ) - : fill; + const entry = node[MODEL_KEY][CHILDREN_KEY][node[SORT_INDEX_KEY]]; + const fillColor = !entry + ? RGBATupleToString(Colors.DarkOpaqueRed.rgba) + : typeof fill === 'function' + ? fill(node.dataName, node.sortIndex, entryValue(entry), node[MODEL_KEY].children) + : fill; const strokeWidth = sectorLineWidth; const strokeStyle = sectorLineStroke; const textNegligible = node.y1px - node.y0px < minRectHeightForText; @@ -190,7 +186,7 @@ export function makeOutsideLinksViewModel( return outsideFillNodes .map((node, i: number) => { const rowSet = rowSets[i]; - if (!rowSet.rows.reduce((p, row) => p + row.rowWords.length, 0)) return { points: [] }; + if (!rowSet?.rows.reduce((p, row) => p + row.rowWords.length, 0)) return { points: [] }; const radius = ringSectorOuterRadius(node); const midAngle = trueBearingToStandardPositionAngle(meanAngle(node.x0, node.x1)); const cos = Math.cos(midAngle); @@ -350,8 +346,13 @@ export function shapeViewModel( return nullShapeViewModel(layout, style, diskCenter); } - const longestPath = ([, { children, path }]: ArrayEntry): number => - children.length > 0 ? children.reduce((p, n) => Math.max(p, longestPath(n)), 0) : path.length; + const longestPath = (entry?: ArrayEntry): number => { + const [, node] = entry ?? []; + if (!node) return NaN; // should never happen + const { children, path } = node; + return children.length > 0 ? children.reduce((p, n) => Math.max(p, longestPath(n)), 0) : path.length; + }; + const maxDepth = longestPath(tree[0]) - 2; // don't include the root node const childNodes = rawChildNodes( layout, diff --git a/packages/charts/src/chart_types/partition_chart/renderer/canvas/canvas_renderers.ts b/packages/charts/src/chart_types/partition_chart/renderer/canvas/canvas_renderers.ts index 6b905472c2..3e6429e9db 100644 --- a/packages/charts/src/chart_types/partition_chart/renderer/canvas/canvas_renderers.ts +++ b/packages/charts/src/chart_types/partition_chart/renderer/canvas/canvas_renderers.ts @@ -193,10 +193,11 @@ function renderFillOutsideLinks( ctx.strokeStyle = linkLabelTextColor; outsideLinksViewModel.forEach(({ points }) => { ctx.beginPath(); - ctx.moveTo(points[0][0], points[0][1]); - for (let i = 1; i < points.length; i++) { - ctx.lineTo(points[i][0], points[i][1]); - } + + points.forEach(([x, y], index) => { + return index === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); + }); + ctx.stroke(); }); }); diff --git a/packages/charts/src/chart_types/partition_chart/renderer/canvas/partition.tsx b/packages/charts/src/chart_types/partition_chart/renderer/canvas/partition.tsx index 38996d43b7..1f3cbdb0ff 100644 --- a/packages/charts/src/chart_types/partition_chart/renderer/canvas/partition.tsx +++ b/packages/charts/src/chart_types/partition_chart/renderer/canvas/partition.tsx @@ -122,11 +122,11 @@ class PartitionComponent extends React.Component { chartDimensions: { width, height }, forwardStageRef, } = this.props; - if (!forwardStageRef.current || !this.ctx || !initialized || width === 0 || height === 0) { + const [focus] = this.props.geometriesFoci; + if (!forwardStageRef.current || !this.ctx || !initialized || width === 0 || height === 0 || !focus) { return; } const picker = this.props.geometries.pickQuads; - const focus = this.props.geometriesFoci[0]; const box = forwardStageRef.current.getBoundingClientRect(); const { diskCenter } = this.props.geometries; const x = e.clientX - box.left - diskCenter.x; @@ -183,12 +183,15 @@ class PartitionComponent extends React.Component { const { ctx, devicePixelRatio, props } = this; clearCanvas(ctx, props.background); props.multiGeometries.forEach((geometries, geometryIndex) => { + const focus = props.geometriesFoci[geometryIndex]; + if (!focus) return; + const renderer = isSimpleLinear(geometries.layout, geometries.style.fillLabel, geometries.layers) ? renderLinearPartitionCanvas2d : isWaffle(geometries.layout) ? renderWrappedPartitionCanvas2d : renderPartitionCanvas2d; - renderer(ctx, devicePixelRatio, geometries, props.geometriesFoci[geometryIndex], this.animationState); + renderer(ctx, devicePixelRatio, geometries, focus, this.animationState); }); } } @@ -228,7 +231,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { return { isRTL: hasMostlyRTLLabels(multiGeometries), initialized: true, - geometries: multiGeometries.length > 0 ? multiGeometries[0] : nullShapeViewModel(), + geometries: multiGeometries[0] ?? nullShapeViewModel(), multiGeometries, chartDimensions: getChartContainerDimensionsSelector(state), geometriesFoci: partitionDrilldownFocus(state), diff --git a/packages/charts/src/chart_types/partition_chart/state/selectors/compute_legend.ts b/packages/charts/src/chart_types/partition_chart/state/selectors/compute_legend.ts index 26b4127e20..1d1ae749fe 100644 --- a/packages/charts/src/chart_types/partition_chart/state/selectors/compute_legend.ts +++ b/packages/charts/src/chart_types/partition_chart/state/selectors/compute_legend.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { partitionMultiGeometries } from './geometries'; import { getPartitionSpecs } from './get_partition_specs'; import { getTrees } from './tree'; import { RGBATupleToString } from '../../../../common/color_library_wrappers'; @@ -42,8 +41,10 @@ function compareDescendingLegendItemValues(aItem: LegendNode, bItem: LegendNode) /** @internal */ export const computeLegendSelector = createCustomCachedSelector( - [getSettingsSpecSelector, getPartitionSpecs, getLegendConfigSelector, partitionMultiGeometries, getTrees], - (settings, specs, { flatLegend, legendMaxDepth, legendPosition }, geometries, trees): LegendItem[] => { + [getSettingsSpecSelector, getPartitionSpecs, getLegendConfigSelector, getTrees], + (settings, [spec], { flatLegend, legendMaxDepth, legendPosition }, trees): LegendItem[] => { + if (!spec) return []; + const sortingFn = flatLegend && settings.legendSort; return trees.flatMap((tree) => { @@ -52,19 +53,19 @@ export const computeLegendSelector = createCustomCachedSelector( sortingFn( { smAccessorValue: tree.smAccessorValue, - specId: aItem.item.seriesIdentifiers[0].specId, - key: aItem.item.seriesIdentifiers[0].key, + specId: aItem.item.seriesIdentifiers[0]?.specId, + key: aItem.item.seriesIdentifiers[0]?.key, } as SeriesIdentifier, { smAccessorValue: tree.smAccessorValue, - specId: bItem.item.seriesIdentifiers[0].specId, - key: bItem.item.seriesIdentifiers[0].key, + specId: bItem.item.seriesIdentifiers[0]?.specId, + key: bItem.item.seriesIdentifiers[0]?.key, } as SeriesIdentifier, ) : undefined; const useHierarchicalLegend = isHierarchicalLegend(flatLegend, legendPosition); - const { valueFormatter } = specs[0]; - const items = walkTree(specs[0].id, useHierarchicalLegend, valueFormatter, tree.tree, specs[0].layers, 0); + const { valueFormatter } = spec; + const items = walkTree(spec.id, useHierarchicalLegend, valueFormatter, tree.tree, spec.layers, 0); return items .filter((d) => { const depth = d.item.depth ?? -1; @@ -76,9 +77,9 @@ export const computeLegendSelector = createCustomCachedSelector( }) .sort( customSortingFn ?? - (specs[0].layout === PartitionLayout.waffle // waffle has inherent top to bottom descending order + (spec.layout === PartitionLayout.waffle // waffle has inherent top to bottom descending order ? compareDescendingLegendItemValues - : isLinear(specs[0].layout) // icicle/flame are sorted by name + : isLinear(spec.layout) // icicle/flame are sorted by name ? compareLegendItemNames : () => 0), // all others are sorted by hierarchy ) diff --git a/packages/charts/src/chart_types/partition_chart/state/selectors/drilldown_active.ts b/packages/charts/src/chart_types/partition_chart/state/selectors/drilldown_active.ts index e88d3fe77b..0d605c20da 100644 --- a/packages/charts/src/chart_types/partition_chart/state/selectors/drilldown_active.ts +++ b/packages/charts/src/chart_types/partition_chart/state/selectors/drilldown_active.ts @@ -15,6 +15,6 @@ import { isSimpleLinear } from '../../layout/viewmodel/viewmodel'; export const drilldownActive = createCustomCachedSelector( [getPartitionSpecs, getChartThemeSelector], (specs, { partition }) => { - return specs.length === 1 && isSimpleLinear(specs[0].layout, partition.fillLabel, specs[0].layers); // singleton! + return specs.length === 1 && specs[0] && isSimpleLinear(specs[0].layout, partition.fillLabel, specs[0].layers); }, ); diff --git a/packages/charts/src/chart_types/partition_chart/state/selectors/get_debug_state.test.ts b/packages/charts/src/chart_types/partition_chart/state/selectors/get_debug_state.test.ts index 2742c1600b..a6cbd8f31e 100644 --- a/packages/charts/src/chart_types/partition_chart/state/selectors/get_debug_state.test.ts +++ b/packages/charts/src/chart_types/partition_chart/state/selectors/get_debug_state.test.ts @@ -21,7 +21,7 @@ import { } from '../../../../specs/settings'; import { onMouseDown, onMouseUp, onPointerMove } from '../../../../state/actions/mouse'; import { GlobalChartState } from '../../../../state/chart_state'; -import { DebugState, PartitionDebugState, SinglePartitionDebugState } from '../../../../state/types'; +import { DebugState, SinglePartitionDebugState } from '../../../../state/types'; import { PartitionLayout } from '../../layout/types/config_types'; import { isSunburst } from '../../layout/viewmodel/viewmodel'; @@ -83,14 +83,14 @@ describe.each([ // small multiple panels expect(debugState.partition).toHaveLength(1); // partition sectors - expect(debugState.partition![0].partitions).toHaveLength(numberOfElements); + expect(debugState.partition?.[0]?.partitions).toHaveLength(numberOfElements); }); it('can click on every sector', () => { - const [{ partitions }] = debugState.partition as PartitionDebugState[]; + const { partitions } = debugState.partition![0]!; let counter = 0; for (let index = 0; index < partitions.length; index++) { - const partition = partitions[index]; + const partition = partitions[index]!; if (!isSunburst(partitionLayout) && partition.depth < 2) { continue; } @@ -122,7 +122,7 @@ function expectCorrectClickInfo( store.dispatch(onMouseUp({ x, y }, index * 3 + 2)); expect(onClickListener).toHaveBeenCalledTimes(index + 1); - const obj = onClickListener.mock.calls[index][0][0][0] as LayerValue[]; + const obj = onClickListener.mock.calls[index]![0]![0]![0] as LayerValue[]; // pick the last element of the path expect(obj[obj.length - 1]).toMatchObject({ depth, diff --git a/packages/charts/src/chart_types/partition_chart/state/selectors/get_screen_reader_data.ts b/packages/charts/src/chart_types/partition_chart/state/selectors/get_screen_reader_data.ts index b14b150c33..e85c7dbb7f 100644 --- a/packages/charts/src/chart_types/partition_chart/state/selectors/get_screen_reader_data.ts +++ b/packages/charts/src/chart_types/partition_chart/state/selectors/get_screen_reader_data.ts @@ -35,15 +35,15 @@ export interface PartitionData { * @internal */ const getScreenReaderDataForPartitions = ( - [{ valueFormatter }]: PartitionSpec[], + [spec]: PartitionSpec[], shapeViewModels: ShapeViewModel[], ): PartitionSectionData[] => { return shapeViewModels.flatMap(({ quadViewModel, layers, panel }) => quadViewModel.map(({ depth, value, dataName, parent, path }) => { const label = layers[depth - 1]?.nodeLabel?.(dataName) ?? dataName; - const parentValue = path.length > 1 ? path[path.length - 2].value : undefined; + const parentValue = path.length > 1 ? path[path.length - 2]?.value : undefined; const parentName = - depth > 1 && parentValue ? layers[depth - 2]?.nodeLabel?.(parentValue) ?? path[path.length - 1].value : 'none'; + depth > 1 && parentValue ? layers[depth - 2]?.nodeLabel?.(parentValue) ?? path[path.length - 1]?.value : 'none'; return { panelTitle: panel.title, @@ -52,7 +52,7 @@ const getScreenReaderDataForPartitions = ( parentName, percentage: `${Math.round((value / parent[STATISTICS_KEY].globalAggregate) * 100)}%`, value, - valueText: valueFormatter ? valueFormatter(value) : `${value}`, + valueText: spec?.valueFormatter ? spec.valueFormatter(value) : `${value}`, }; }), ); @@ -70,7 +70,7 @@ export const getScreenReaderDataSelector = createCustomCachedSelector( }; } return { - hasMultipleLayers: specs[0].layers.length > 1, + hasMultipleLayers: (specs[0]?.layers.length ?? NaN) > 1, isSmallMultiple: shapeViewModel.length > 1, data: getScreenReaderDataForPartitions(specs, shapeViewModel), }; diff --git a/packages/charts/src/chart_types/partition_chart/state/selectors/partition_spec.ts b/packages/charts/src/chart_types/partition_chart/state/selectors/partition_spec.ts index c63865e903..6bee6ed40c 100644 --- a/packages/charts/src/chart_types/partition_chart/state/selectors/partition_spec.ts +++ b/packages/charts/src/chart_types/partition_chart/state/selectors/partition_spec.ts @@ -9,7 +9,7 @@ import { ChartType } from '../../..'; import { SpecType } from '../../../../specs'; import { GlobalChartState } from '../../../../state/chart_state'; -import { getSpecsFromStore } from '../../../../state/utils'; +import { getSpecsFromStore, getSpecFromStore } from '../../../../state/utils'; import { PartitionSpec } from '../../specs'; /** @internal */ @@ -19,6 +19,5 @@ export function getPartitionSpecs(state: GlobalChartState): PartitionSpec[] { /** @internal */ export function getPartitionSpec(state: GlobalChartState): PartitionSpec | null { - const partitionSpecs = getPartitionSpecs(state); - return partitionSpecs.length > 0 ? partitionSpecs[0] : null; // singleton! + return getSpecFromStore(state.specs, ChartType.Partition, SpecType.Series, false); } diff --git a/packages/charts/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts b/packages/charts/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts index 7dfc501c8d..00397fc040 100644 --- a/packages/charts/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts +++ b/packages/charts/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts @@ -78,11 +78,11 @@ describe('Picked shapes selector', () => { test('check initial geoms', () => { addSeries(store, treemapSpec); const treemapGeometries = partitionMultiGeometries(store.getState())[0]; - expect(treemapGeometries.quadViewModel).toHaveLength(6); + expect(treemapGeometries?.quadViewModel).toHaveLength(6); addSeries(store, sunburstSpec); const sunburstGeometries = partitionMultiGeometries(store.getState())[0]; - expect(sunburstGeometries.quadViewModel).toHaveLength(6); + expect(sunburstGeometries?.quadViewModel).toHaveLength(6); }); test('treemap check picked geometries', () => { const onClickListener = jest.fn(); @@ -90,7 +90,7 @@ describe('Picked shapes selector', () => { onElementClick: onClickListener, }); const geometries = partitionMultiGeometries(store.getState())[0]; - expect(geometries.quadViewModel).toHaveLength(6); + expect(geometries?.quadViewModel).toHaveLength(6); const onElementClickCaller = createOnElementClickCaller(); store.subscribe(() => { @@ -167,7 +167,7 @@ describe('Picked shapes selector', () => { }, ); const geometries = partitionMultiGeometries(store.getState())[0]; - expect(geometries.quadViewModel).toHaveLength(2); + expect(geometries?.quadViewModel).toHaveLength(2); const onElementClickCaller = createOnElementClickCaller(); store.subscribe(() => { @@ -208,7 +208,7 @@ describe('Picked shapes selector', () => { onElementClick: onClickListener, }); const geometries = partitionMultiGeometries(store.getState())[0]; - expect(geometries.quadViewModel).toHaveLength(6); + expect(geometries?.quadViewModel).toHaveLength(6); const onElementClickCaller = createOnElementClickCaller(); store.subscribe(() => { diff --git a/packages/charts/src/chart_types/partition_chart/state/selectors/tooltip.ts b/packages/charts/src/chart_types/partition_chart/state/selectors/tooltip.ts index 53570105ba..9eb6d7dc28 100644 --- a/packages/charts/src/chart_types/partition_chart/state/selectors/tooltip.ts +++ b/packages/charts/src/chart_types/partition_chart/state/selectors/tooltip.ts @@ -20,14 +20,7 @@ export const getTooltipInfoSelector = createCustomCachedSelector( [getPartitionSpec, getPickedShapes, partitionMultiGeometries], (spec, pickedShapes, shapeViewModel): TooltipInfo => { return spec - ? pickShapesTooltipValues( - pickedShapes, - shapeViewModel, - spec.valueGetter, - spec.valueFormatter, - spec.percentFormatter, - spec.id, - ) + ? pickShapesTooltipValues(pickedShapes, shapeViewModel, spec.valueFormatter, spec.percentFormatter, spec.id) : EMPTY_TOOLTIP; }, ); diff --git a/packages/charts/src/chart_types/partition_chart/state/selectors/tree.ts b/packages/charts/src/chart_types/partition_chart/state/selectors/tree.ts index 71885fa4ca..33e26fadaf 100644 --- a/packages/charts/src/chart_types/partition_chart/state/selectors/tree.ts +++ b/packages/charts/src/chart_types/partition_chart/state/selectors/tree.ts @@ -94,6 +94,6 @@ function getTreesForSpec( /** @internal */ export const getTrees = createCustomCachedSelector( [getPartitionSpecs, getSmallMultiplesSpecs, getGroupBySpecs], - (partitionSpecs, smallMultiplesSpecs, groupBySpecs): StyledTree[] => - partitionSpecs.length > 0 ? getTreesForSpec(partitionSpecs[0], smallMultiplesSpecs, groupBySpecs) : [], // singleton! + ([spec], smallMultiplesSpecs, groupBySpecs): StyledTree[] => + spec ? getTreesForSpec(spec, smallMultiplesSpecs, groupBySpecs) : [], ); diff --git a/packages/charts/src/chart_types/timeslip/projections/axis_model.ts b/packages/charts/src/chart_types/timeslip/projections/axis_model.ts index 7dd4f9c2a4..1956a4f18b 100644 --- a/packages/charts/src/chart_types/timeslip/projections/axis_model.ts +++ b/packages/charts/src/chart_types/timeslip/projections/axis_model.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { isDefined } from '../../../utils/common'; + /** @internal */ export const oneTwoFive = (mantissa: number) => (mantissa > 5 ? 10 : mantissa > 2 ? 5 : mantissa > 1 ? 2 : 1); /** @internal */ @@ -50,7 +52,10 @@ export const getDecimalTicks = ( } } return bestCandidate.length > maximumTickCount - ? [...(maximumTickCount > 1 ? [bestCandidate[0]] : []), bestCandidate[bestCandidate.length - 1]] + ? [ + ...(maximumTickCount > 1 && isDefined(bestCandidate[0]) ? [bestCandidate[0]] : []), + bestCandidate[bestCandidate.length - 1] ?? NaN, + ] : []; }; @@ -62,7 +67,7 @@ export const axisModel = ( const domainMin = Math.min(...domainLandmarks); const domainMax = Math.max(...domainLandmarks); const niceTicks = getDecimalTicks(domainMin, domainMax, desiredTickCount); - const niceDomainMin = niceTicks.length >= 2 ? niceTicks[0] : domainMin; - const niceDomainMax = niceTicks.length >= 2 ? niceTicks[niceTicks.length - 1] : domainMax; + const niceDomainMin = niceTicks.length >= 2 ? niceTicks[0]! : domainMin; + const niceDomainMax = niceTicks.length >= 2 ? niceTicks[niceTicks.length - 1]! : domainMax; return { niceDomainMin, niceDomainMax, niceTicks }; }; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/config.ts b/packages/charts/src/chart_types/timeslip/timeslip/config.ts index 072a92269a..b581536665 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/config.ts +++ b/packages/charts/src/chart_types/timeslip/timeslip/config.ts @@ -8,7 +8,7 @@ import { LocaleOptions } from './render/annotations/time_extent'; import { getValidatedTimeZone, getZoneFromSpecs } from '../../../utils/time_zone'; -import { cachedZonedDateTimeFrom, timeProp } from '../../xy_chart/axes/timeslip/chrono/cached_chrono'; +import { cachedZonedDateTimeFrom, TimeProp } from '../../xy_chart/axes/timeslip/chrono/cached_chrono'; import { RasterConfig, TimeFormatter } from '../../xy_chart/axes/timeslip/continuous_time_rasters'; import { DEFAULT_LOCALE, MINIMUM_TICK_PIXEL_DISTANCE } from '../../xy_chart/axes/timeslip/multilayer_ticks'; @@ -107,8 +107,8 @@ export const config: TimeslipConfig = { barChroma: { r: 96, g: 146, b: 192 }, barFillAlpha: 0.3, lineThicknessSteps, - domainFrom: cachedZonedDateTimeFrom({ timeZone, year: 2002, month: 1, day: 1 })[timeProp.epochSeconds], - domainTo: cachedZonedDateTimeFrom({ timeZone, year: 2022, month: 1, day: 1 })[timeProp.epochSeconds], + domainFrom: cachedZonedDateTimeFrom({ timeZone, year: 2002, month: 1, day: 1 })[TimeProp.EpochSeconds], + domainTo: cachedZonedDateTimeFrom({ timeZone, year: 2022, month: 1, day: 1 })[TimeProp.EpochSeconds], smallFontSize, cssFontShorthand: `normal normal 100 ${smallFontSize}px Inter, Helvetica, Arial, sans-serif`, monospacedFontShorthand: `normal normal 100 ${smallFontSize}px "Roboto Mono", Consolas, Menlo, Courier, monospace`, diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/raster.ts b/packages/charts/src/chart_types/timeslip/timeslip/render/raster.ts index b9938d87ef..40c21d0a39 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/render/raster.ts +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/raster.ts @@ -8,6 +8,7 @@ import { Layers, renderColumnBars, renderColumnTickLabels } from './column'; import { doing, executing, filtering, mapping, pipeline } from '../../../../common/iterables'; +import { isNil } from '../../../../utils/common'; import { NumberFormatter, Interval, @@ -176,9 +177,9 @@ export const renderRaster = const lineNestLevel = a[i] === a[0] ? 0 : textNestLevel; const textNestLevelRowLimited = Math.min(config.maxLabelRowCount, textNestLevel); // max. N rows const lineNestLevelRowLimited = Math.min(config.maxLabelRowCount, lineNestLevel); - const lineThickness = config.lineThicknessSteps[i]; + const lineThickness = config.lineThicknessSteps[i] ?? NaN; const luma = - config.lumaSteps[i] * + (config.lumaSteps[i] ?? NaN) * (config.darkMode ? (config.a11y.contrast === 'low' ? 0.5 : 1) : config.a11y.contrast === 'low' ? 1.5 : 1); const halfLineThickness = lineThickness / 2; const notTooDenseGridLayer = notTooDense(domainFrom, domainTo, 0, cartesianWidth, TIMESLIP_MAX_TIME_GRID_COUNT); @@ -191,7 +192,7 @@ export const renderRaster = const renderBar = finestRaster && valid && - dataState.binUnit === layers[0].unit && + dataState.binUnit === layers[0]?.unit && dataState.binUnitCount === layers[0].unitMultiplier; if (labeled && textNestLevel <= config.maxLabelRowCount) @@ -240,7 +241,7 @@ export const renderRaster = } // render specially the tick that just precedes the domain, therefore may insert into it (eg. intentionally, via needing to see tick texts) - if (binStartList.length > 0 && binStartList[0].minimum < domainFrom) { + if (!isNil(binStartList[0]) && binStartList[0].minimum < domainFrom) { const precedingBinStart = binStartList[0]; if (finestRaster) { // condition necessary, otherwise it'll be the binStart of some temporally coarser bin diff --git a/packages/charts/src/chart_types/timeslip/utils/multitouch.ts b/packages/charts/src/chart_types/timeslip/utils/multitouch.ts index 707ebcba1d..a020181325 100644 --- a/packages/charts/src/chart_types/timeslip/utils/multitouch.ts +++ b/packages/charts/src/chart_types/timeslip/utils/multitouch.ts @@ -43,8 +43,12 @@ export const setNewMultitouch = (multitouch: MappedTouch[], newMultitouch: Mappe export const eraseMultitouch = (multitouch: MappedTouch[]) => multitouch.splice(0, Infinity); /** @internal */ -export const getPinchRatio = (multitouch: MappedTouch[], newMultitouch: MappedTouch[]) => - (multitouch[1].position - multitouch[0].position) / (newMultitouch[1].position - newMultitouch[0].position); +export const getPinchRatio = (multitouch: MappedTouch[], newMultitouch: MappedTouch[]) => { + return ( + (multitouch[1]?.position ?? NaN - (multitouch[0]?.position ?? NaN)) / + ((newMultitouch[1]?.position ?? NaN) - (newMultitouch[0]?.position ?? NaN)) + ); +}; /** @internal */ export const twoTouchPinch = (multitouch: MappedTouch[]) => multitouch.length === 2; diff --git a/packages/charts/src/chart_types/wordcloud/state/chart_state.tsx b/packages/charts/src/chart_types/wordcloud/state/chart_state.tsx index 3281bb6dac..5e6b67c151 100644 --- a/packages/charts/src/chart_types/wordcloud/state/chart_state.tsx +++ b/packages/charts/src/chart_types/wordcloud/state/chart_state.tsx @@ -8,7 +8,7 @@ import React from 'react'; -import { getSpecOrNull } from './selectors/wordcloud_spec'; +import { getWordcloudSpecSelector } from './selectors/wordcloud_spec'; import { ChartType } from '../..'; import { DEFAULT_CSS_CURSOR } from '../../../common/constants'; import { LegendItem } from '../../../common/legend'; @@ -29,7 +29,7 @@ export class WordcloudState implements InternalChartState { chartType = ChartType.Wordcloud; isInitialized(globalState: GlobalChartState) { - return getSpecOrNull(globalState) !== null ? InitStatus.Initialized : InitStatus.ChartNotInitialized; + return getWordcloudSpecSelector(globalState) !== null ? InitStatus.Initialized : InitStatus.ChartNotInitialized; } isBrushAvailable() { diff --git a/packages/charts/src/chart_types/wordcloud/state/selectors/geometries.ts b/packages/charts/src/chart_types/wordcloud/state/selectors/geometries.ts index 88dde53677..2bc0c4d041 100644 --- a/packages/charts/src/chart_types/wordcloud/state/selectors/geometries.ts +++ b/packages/charts/src/chart_types/wordcloud/state/selectors/geometries.ts @@ -13,7 +13,7 @@ import { GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { getSpecs } from '../../../../state/selectors/get_specs'; -import { getSpecsFromStore } from '../../../../state/utils'; +import { getSpecFromStore } from '../../../../state/utils'; import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; import { WordcloudSpec } from '../../specs'; @@ -23,7 +23,7 @@ const getParentDimensions = (state: GlobalChartState) => state.parentDimensions; export const geometries = createCustomCachedSelector( [getSpecs, getChartThemeSelector, getParentDimensions], (specs, theme, parentDimensions): ShapeViewModel => { - const wordcloudSpecs = getSpecsFromStore(specs, ChartType.Wordcloud, SpecType.Series); - return wordcloudSpecs.length === 1 ? render(wordcloudSpecs[0], theme, parentDimensions) : nullShapeViewModel(); + const wordcloudSpec = getSpecFromStore(specs, ChartType.Wordcloud, SpecType.Series, false); + return wordcloudSpec ? render(wordcloudSpec, theme, parentDimensions) : nullShapeViewModel(); }, ); diff --git a/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_click_caller.ts b/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_click_caller.ts index 47cf11a83e..1056d58b07 100644 --- a/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_click_caller.ts +++ b/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_click_caller.ts @@ -9,7 +9,7 @@ import { Selector } from 'reselect'; import { getPickedShapesLayerValues } from './picked_shapes'; -import { getSpecOrNull } from './wordcloud_spec'; +import { getWordcloudSpecSelector } from './wordcloud_spec'; import { ChartType } from '../../..'; import { getOnElementClickSelector } from '../../../../common/event_handler_selectors'; import { GlobalChartState, PointerStates } from '../../../../state/chart_state'; @@ -30,7 +30,7 @@ export function createOnElementClickCaller(): (state: GlobalChartState) => void return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartType.Wordcloud) { selector = createCustomCachedSelector( - [getSpecOrNull, getLastClickSelector, getSettingsSpecSelector, getPickedShapesLayerValues], + [getWordcloudSpecSelector, getLastClickSelector, getSettingsSpecSelector, getPickedShapesLayerValues], getOnElementClickSelector(prev), ); } diff --git a/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_out_caller.ts b/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_out_caller.ts index 9aff0736c2..be5d9fd2e9 100644 --- a/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_out_caller.ts +++ b/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_out_caller.ts @@ -9,7 +9,7 @@ import { Selector } from 'react-redux'; import { getPickedShapesLayerValues } from './picked_shapes'; -import { getSpecOrNull } from './wordcloud_spec'; +import { getWordcloudSpecSelector } from './wordcloud_spec'; import { ChartType } from '../../..'; import { getOnElementOutSelector } from '../../../../common/event_handler_selectors'; import { GlobalChartState } from '../../../../state/chart_state'; @@ -28,7 +28,7 @@ export function createOnElementOutCaller(): (state: GlobalChartState) => void { return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartType.Wordcloud) { selector = createCustomCachedSelector( - [getSpecOrNull, getPickedShapesLayerValues, getSettingsSpecSelector], + [getWordcloudSpecSelector, getPickedShapesLayerValues, getSettingsSpecSelector], getOnElementOutSelector(prev), ); } diff --git a/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_over_caller.ts b/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_over_caller.ts index bf8dd5dbd4..6bf2130668 100644 --- a/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_over_caller.ts +++ b/packages/charts/src/chart_types/wordcloud/state/selectors/on_element_over_caller.ts @@ -9,7 +9,7 @@ import { Selector } from 'react-redux'; import { getPickedShapesLayerValues } from './picked_shapes'; -import { getSpecOrNull } from './wordcloud_spec'; +import { getWordcloudSpecSelector } from './wordcloud_spec'; import { ChartType } from '../../..'; import { getOnElementOverSelector } from '../../../../common/event_handler_selectors'; import { LayerValue } from '../../../../specs'; @@ -29,7 +29,7 @@ export function createOnElementOverCaller(): (state: GlobalChartState) => void { return (state: GlobalChartState) => { if (selector === null && state.chartType === ChartType.Wordcloud) { selector = createCustomCachedSelector( - [getSpecOrNull, getPickedShapesLayerValues, getSettingsSpecSelector], + [getWordcloudSpecSelector, getPickedShapesLayerValues, getSettingsSpecSelector], getOnElementOverSelector(prev), ); } diff --git a/packages/charts/src/chart_types/wordcloud/state/selectors/wordcloud_spec.ts b/packages/charts/src/chart_types/wordcloud/state/selectors/wordcloud_spec.ts index 9a3ca32f49..7467998f8c 100644 --- a/packages/charts/src/chart_types/wordcloud/state/selectors/wordcloud_spec.ts +++ b/packages/charts/src/chart_types/wordcloud/state/selectors/wordcloud_spec.ts @@ -9,11 +9,10 @@ import { ChartType } from '../../..'; import { SpecType } from '../../../../specs/constants'; import { GlobalChartState } from '../../../../state/chart_state'; -import { getSpecsFromStore } from '../../../../state/utils'; +import { getSpecFromStore } from '../../../../state/utils'; import { WordcloudSpec } from '../../specs'; /** @internal */ -export function getSpecOrNull(state: GlobalChartState): WordcloudSpec | null { - const specs = getSpecsFromStore(state.specs, ChartType.Wordcloud, SpecType.Series); - return specs.length > 0 ? specs[0] : null; +export function getWordcloudSpecSelector(state: GlobalChartState): WordcloudSpec | null { + return getSpecFromStore(state.specs, ChartType.Wordcloud, SpecType.Series, false); } diff --git a/packages/charts/src/chart_types/xy_chart/annotations/line/dimensions.ts b/packages/charts/src/chart_types/xy_chart/annotations/line/dimensions.ts index f68a6e88b9..63e7497197 100644 --- a/packages/charts/src/chart_types/xy_chart/annotations/line/dimensions.ts +++ b/packages/charts/src/chart_types/xy_chart/annotations/line/dimensions.ts @@ -142,8 +142,8 @@ function computeXDomainLineAnnotationDimensions( return; } if (isContinuousScale(xScale) && typeof dataValue === 'number') { - const [minDomain] = xScale.domain; - const maxDomain = isHistogramMode ? xScale.domain[1] + xScale.minInterval : xScale.domain[1]; + const [minDomain, scaleMaxDomain] = xScale.domain; + const maxDomain = isHistogramMode ? scaleMaxDomain + xScale.minInterval : scaleMaxDomain; if (dataValue < minDomain || dataValue > maxDomain) { return; } diff --git a/packages/charts/src/chart_types/xy_chart/annotations/rect/dimensions.test.ts b/packages/charts/src/chart_types/xy_chart/annotations/rect/dimensions.test.ts index 52f2117fce..91ebce4dd5 100644 --- a/packages/charts/src/chart_types/xy_chart/annotations/rect/dimensions.test.ts +++ b/packages/charts/src/chart_types/xy_chart/annotations/rect/dimensions.test.ts @@ -61,25 +61,25 @@ describe('Rect Annotation Dimensions', () => { 'rect', ) as AnnotationRectProps[]; - expect(dims1.rect.x).toBe(10); - expect(dims1.rect.width).toBeCloseTo(90); - expect(dims1.rect.y).toBe(0); - expect(dims1.rect.height).toBe(100); + expect(dims1?.rect.x).toBe(10); + expect(dims1?.rect.width).toBeCloseTo(90); + expect(dims1?.rect.y).toBe(0); + expect(dims1?.rect.height).toBe(100); - expect(dims2.rect.x).toBe(0); - expect(dims2.rect.width).toBe(10); - expect(dims2.rect.y).toBe(0); - expect(dims2.rect.height).toBe(100); + expect(dims2?.rect.x).toBe(0); + expect(dims2?.rect.width).toBe(10); + expect(dims2?.rect.y).toBe(0); + expect(dims2?.rect.height).toBe(100); - expect(dims3.rect.x).toBe(0); - expect(dims3.rect.width).toBe(100); - expect(dims3.rect.y).toBe(0); - expect(dims3.rect.height).toBe(90); + expect(dims3?.rect.x).toBe(0); + expect(dims3?.rect.width).toBe(100); + expect(dims3?.rect.y).toBe(0); + expect(dims3?.rect.height).toBe(90); - expect(dims4.rect.x).toBe(0); - expect(dims4.rect.width).toBeCloseTo(100); - expect(dims4.rect.y).toBe(90); - expect(dims4.rect.height).toBe(10); + expect(dims4?.rect.x).toBe(0); + expect(dims4?.rect.width).toBeCloseTo(100); + expect(dims4?.rect.y).toBe(90); + expect(dims4?.rect.height).toBe(10); }); test('should determine if a point is within a rectangle annotation', () => { diff --git a/packages/charts/src/chart_types/xy_chart/annotations/rect/dimensions.ts b/packages/charts/src/chart_types/xy_chart/annotations/rect/dimensions.ts index 509dbe788e..0ce3390e86 100644 --- a/packages/charts/src/chart_types/xy_chart/annotations/rect/dimensions.ts +++ b/packages/charts/src/chart_types/xy_chart/annotations/rect/dimensions.ts @@ -176,12 +176,12 @@ function scaleXonBandScale( scaledX1 += xScale.originalBandwidth + padding; // give the x1 value a maximum of the chart range if (scaledX1 > xScale.range[1]) { - [, scaledX1] = xScale.range; + scaledX1 = xScale.range[1]; } scaledX0 -= padding; if (scaledX0 < xScale.range[0]) { - [scaledX0] = xScale.range; + scaledX0 = xScale.range[0]; } const width = Math.abs(scaledX1 - scaledX0); return { @@ -231,7 +231,7 @@ function limitValueToDomainRange( } else { const min = isNil(minValue) || !scale.domain.includes(minValue) ? scale.domain[0] : minValue; const max = isNil(maxValue) || !scale.domain.includes(maxValue) ? scale.domain[scale.domain.length - 1] : maxValue; - return [min, max]; + return [min ?? null, max ?? null]; } } diff --git a/packages/charts/src/chart_types/xy_chart/annotations/rect/tooltip.ts b/packages/charts/src/chart_types/xy_chart/annotations/rect/tooltip.ts index 674eee90b2..f719f445c8 100644 --- a/packages/charts/src/chart_types/xy_chart/annotations/rect/tooltip.ts +++ b/packages/charts/src/chart_types/xy_chart/annotations/rect/tooltip.ts @@ -25,8 +25,8 @@ export function getRectAnnotationTooltipState( chartDimensions: Dimensions, specId: SpecId, ): AnnotationTooltipState | null { - for (let i = 0; i < annotationRects.length; i++) { - const { rect, panel, datum, id } = annotationRects[i]; + for (const annotationRect of annotationRects) { + const { rect, panel, datum, id } = annotationRect; const newRect = transformRotateRect(rect, rotation, panel); const startX = newRect.x + chartDimensions.left + panel.left; const endX = startX + newRect.width; diff --git a/packages/charts/src/chart_types/xy_chart/annotations/tooltip.ts b/packages/charts/src/chart_types/xy_chart/annotations/tooltip.ts index b6b0f2fff2..64653fda27 100644 --- a/packages/charts/src/chart_types/xy_chart/annotations/tooltip.ts +++ b/packages/charts/src/chart_types/xy_chart/annotations/tooltip.ts @@ -31,8 +31,8 @@ export function computeRectAnnotationTooltipState( for (let i = 0; i < sortedAnnotationSpecs.length; i++) { const spec = sortedAnnotationSpecs[i]; - const annotationDimension = annotationDimensions.get(spec.id); - if (spec.hideTooltips || !annotationDimension) { + const annotationDimension = spec?.id && annotationDimensions.get(spec.id); + if (!spec || spec.hideTooltips || !annotationDimension) { continue; } const { customTooltip, customTooltipDetails } = spec; diff --git a/packages/charts/src/chart_types/xy_chart/axes/axes_sizes.ts b/packages/charts/src/chart_types/xy_chart/axes/axes_sizes.ts index 9904b7c341..8b485d564b 100644 --- a/packages/charts/src/chart_types/xy_chart/axes/axes_sizes.ts +++ b/packages/charts/src/chart_types/xy_chart/axes/axes_sizes.ts @@ -22,7 +22,7 @@ const getAxisSizeForLabel = ( { axes: sharedAxesStyles, chartMargins }: Theme, axesStyles: Map, { maxLabelBboxWidth = 0, maxLabelBboxHeight = 0 }: TickLabelBounds, - smSpec?: SmallMultiplesSpec, + smSpec: SmallMultiplesSpec | null, ) => { const { tickLine, axisTitle, axisPanelTitle, tickLabel } = axesStyles.get(axisSpec.id) ?? sharedAxesStyles; const horizontal = isHorizontalAxis(axisSpec.position); @@ -63,7 +63,7 @@ export function getAxesDimensions( axisDimensions: AxesTicksDimensions, axesStyles: Map, axisSpecs: AxisSpec[], - smSpec?: SmallMultiplesSpec, + smSpec: SmallMultiplesSpec | null, ): PerSideDistance & { margin: { left: number } } { const sizes = [...axisDimensions].reduce( (acc, [id, tickLabelBounds]) => { diff --git a/packages/charts/src/chart_types/xy_chart/axes/timeslip/chrono/cached_chrono.ts b/packages/charts/src/chart_types/xy_chart/axes/timeslip/chrono/cached_chrono.ts index 1468f93db1..dac0a98f04 100644 --- a/packages/charts/src/chart_types/xy_chart/axes/timeslip/chrono/cached_chrono.ts +++ b/packages/charts/src/chart_types/xy_chart/axes/timeslip/chrono/cached_chrono.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { $Values } from 'utility-types'; + import { addTime, propsFromCalendarObj, CalendarUnit } from './chrono'; const timeProps = ['epochSeconds', 'dayOfWeek']; @@ -13,6 +15,14 @@ const timeProps = ['epochSeconds', 'dayOfWeek']; /** @internal */ export const timeProp = Object.fromEntries(timeProps.map((propName, i) => [propName, i])); +/** @public */ +export const TimeProp = Object.freeze({ + EpochSeconds: 0 as const, + DayOfWeek: 1 as const, +}); +/** @public */ +export type TimeProp = $Values; + const zonedDateTimeFromCache: Record = {}; // without caching, even luxon is choppy with zoom and pan interface TemporalArgs { diff --git a/packages/charts/src/chart_types/xy_chart/axes/timeslip/continuous_time_rasters.ts b/packages/charts/src/chart_types/xy_chart/axes/timeslip/continuous_time_rasters.ts index a915195dcc..44c65c68c4 100644 --- a/packages/charts/src/chart_types/xy_chart/axes/timeslip/continuous_time_rasters.ts +++ b/packages/charts/src/chart_types/xy_chart/axes/timeslip/continuous_time_rasters.ts @@ -9,7 +9,7 @@ /* eslint-disable-next-line eslint-comments/disable-enable-pair */ /* eslint-disable @typescript-eslint/unbound-method */ -import { cachedTimeDelta, cachedZonedDateTimeFrom, timeProp } from './chrono/cached_chrono'; +import { cachedTimeDelta, cachedZonedDateTimeFrom, TimeProp } from './chrono/cached_chrono'; import { epochInSecondsToYear } from './chrono/chrono'; import { LOCALE_TRANSLATIONS } from './locale_translations'; @@ -94,13 +94,13 @@ const monthBasedIntervals = ( for (const { year } of years.intervals(domainFrom, domainTo)) { for (let month = 1; month <= 12; month += unitMultiplier) { const timePoint = cachedZonedDateTimeFrom({ timeZone, year, month, day: 1 }); - const binStart = timePoint[timeProp.epochSeconds]; + const binStart = timePoint[TimeProp.EpochSeconds]; const binEnd = cachedZonedDateTimeFrom({ timeZone, year: month <= 12 - unitMultiplier ? year : year + 1, month: ((month + unitMultiplier - 1) % 12) + 1, day: 1, - })[timeProp.epochSeconds]; + })[TimeProp.EpochSeconds]; yield { year, month, minimum: binStart, supremum: binEnd }; } } @@ -172,13 +172,13 @@ export const continuousTimeRasters = ({ minimumTickPixelDistance, locale }: Rast const toYear = epochInSecondsToYear(timeZone, domainTo); for (let year = fromYear; year <= toYear; year++) { const timePoint = cachedZonedDateTimeFrom({ timeZone, year, month: 1, day: 1 }); - const binStart = timePoint[timeProp.epochSeconds]; + const binStart = timePoint[TimeProp.EpochSeconds]; const binEnd = cachedZonedDateTimeFrom({ timeZone, year: year + 1, month: 1, day: 1, - })[timeProp.epochSeconds]; + })[TimeProp.EpochSeconds]; yield { year, minimum: binStart, supremum: binEnd }; } }, @@ -201,13 +201,13 @@ export const continuousTimeRasters = ({ minimumTickPixelDistance, locale }: Rast const toYear = epochInSecondsToYear(timeZone, domainTo); for (let year = Math.floor(fromYear / 10) * 10; year <= Math.ceil(toYear / 10) * 10; year += 10) { const timePoint = cachedZonedDateTimeFrom({ timeZone, year, month: 1, day: 1 }); - const binStart = timePoint[timeProp.epochSeconds]; + const binStart = timePoint[TimeProp.EpochSeconds]; const binEnd = cachedZonedDateTimeFrom({ timeZone, year: year + 10, month: 1, day: 1, - })[timeProp.epochSeconds]; + })[TimeProp.EpochSeconds]; yield { year, minimum: binStart, supremum: binEnd }; } }, @@ -258,8 +258,8 @@ export const continuousTimeRasters = ({ minimumTickPixelDistance, locale }: Rast day: dayOfMonth, }; const timePoint = cachedZonedDateTimeFrom(temporalArgs); - const dayOfWeek: number = timePoint[timeProp.dayOfWeek]; - const binStart = timePoint[timeProp.epochSeconds]; + const dayOfWeek: number = timePoint[TimeProp.DayOfWeek]; + const binStart = timePoint[TimeProp.EpochSeconds]; const binEnd = cachedTimeDelta(temporalArgs, 'days', 1); if (Number.isFinite(binStart) && Number.isFinite(binEnd)) yield { @@ -286,9 +286,9 @@ export const continuousTimeRasters = ({ minimumTickPixelDistance, locale }: Rast for (let dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) { const temporalArgs = { timeZone, year, month, day: dayOfMonth }; const timePoint = cachedZonedDateTimeFrom(temporalArgs); - const dayOfWeek = timePoint[timeProp.dayOfWeek]; + const dayOfWeek = timePoint[TimeProp.DayOfWeek]; if (dayOfWeek !== 1) continue; - const binStart = timePoint[timeProp.epochSeconds]; + const binStart = timePoint[TimeProp.EpochSeconds]; if (Number.isFinite(binStart)) { yield { dayOfMonth, minimum: binStart, supremum: cachedTimeDelta(temporalArgs, 'days', 7) }; } @@ -349,7 +349,7 @@ export const continuousTimeRasters = ({ minimumTickPixelDistance, locale }: Rast hour, }; const timePoint = cachedZonedDateTimeFrom(temporalArgs); - const binStart = timePoint[timeProp.epochSeconds]; + const binStart = timePoint[TimeProp.EpochSeconds]; return Number.isNaN(binStart) ? [] : { @@ -365,7 +365,7 @@ export const continuousTimeRasters = ({ minimumTickPixelDistance, locale }: Rast }), ) as Array ).map((b: Interval & YearToHour, i, a) => - Object.assign(b, { supremum: i === a.length - 1 ? b.supremum : a[i + 1].minimum }), + Object.assign(b, { supremum: i === a.length - 1 ? b.supremum : a[i + 1]?.minimum }), ), minorTickLabelFormat: new Intl.DateTimeFormat(locale, { ...hourFormat, diff --git a/packages/charts/src/chart_types/xy_chart/axes/timeslip/numerical_rasters.ts b/packages/charts/src/chart_types/xy_chart/axes/timeslip/numerical_rasters.ts index 9fb428aa47..bf2b26a939 100644 --- a/packages/charts/src/chart_types/xy_chart/axes/timeslip/numerical_rasters.ts +++ b/packages/charts/src/chart_types/xy_chart/axes/timeslip/numerical_rasters.ts @@ -31,7 +31,7 @@ export const numericalRasters = ({ minimumTickPixelDistance, locale }: RasterCon intervals: (domainFrom, domainTo) => getDecimalTicks(domainFrom, domainTo, i === 0 ? 20 : 5, oneFive).map((d, i, a) => ({ minimum: d, - supremum: i < a.length - 1 ? a[i + 1] : d + (d - a[i - 1]), + supremum: i < a.length - 1 ? a[i + 1] ?? NaN : d + (d - (a[i - 1] ?? NaN)), })), detailedLabelFormat: (n: number) => format((n - 1300000000000) / 1e6), minorTickLabelFormat: (n: number) => format((n - 1300000000000) / 1e6), diff --git a/packages/charts/src/chart_types/xy_chart/domains/x_domain.ts b/packages/charts/src/chart_types/xy_chart/domains/x_domain.ts index 5189902644..d39afde736 100644 --- a/packages/charts/src/chart_types/xy_chart/domains/x_domain.ts +++ b/packages/charts/src/chart_types/xy_chart/domains/x_domain.ts @@ -132,7 +132,9 @@ export function findMinInterval(xValues: number[]): number { return xValues.length < 2 ? xValues.length : [...xValues].sort(compareByValueAsc).reduce((minInterval, current, i, sortedValues) => { - return i < xValues.length - 1 ? Math.min(minInterval, Math.abs(sortedValues[i + 1] - current)) : minInterval; + return i < xValues.length - 1 + ? Math.min(minInterval, Math.abs((sortedValues[i + 1] ?? 0) - current)) + : minInterval; }, Infinity); } diff --git a/packages/charts/src/chart_types/xy_chart/domains/y_domain.test.ts b/packages/charts/src/chart_types/xy_chart/domains/y_domain.test.ts index c244b2569c..a1596e41b2 100644 --- a/packages/charts/src/chart_types/xy_chart/domains/y_domain.test.ts +++ b/packages/charts/src/chart_types/xy_chart/domains/y_domain.test.ts @@ -226,10 +226,10 @@ describe('Y Domain', () => { const groupValues = [...splitSpecs.values()]; expect(groupKeys).toEqual(['group1', 'group2']); expect(groupValues.length).toBe(2); - expect(groupValues[0].nonStacked).toEqual([spec1]); - expect(groupValues[1].nonStacked).toEqual([spec2]); - expect(groupValues[0].stacked).toEqual([]); - expect(groupValues[1].stacked).toEqual([]); + expect(groupValues[0]?.nonStacked).toEqual([spec1]); + expect(groupValues[1]?.nonStacked).toEqual([spec2]); + expect(groupValues[0]?.stacked).toEqual([]); + expect(groupValues[1]?.stacked).toEqual([]); }); test('Should split specs by groupId, two groups, stacked', () => { const spec1: BasicSeriesSpec = { @@ -263,10 +263,10 @@ describe('Y Domain', () => { const groupValues = [...splitSpecs.values()]; expect(groupKeys).toEqual(['group1', 'group2']); expect(groupValues.length).toBe(2); - expect(groupValues[0].stacked).toEqual([spec1]); - expect(groupValues[1].stacked).toEqual([spec2]); - expect(groupValues[0].nonStacked).toEqual([]); - expect(groupValues[1].nonStacked).toEqual([]); + expect(groupValues[0]?.stacked).toEqual([spec1]); + expect(groupValues[1]?.stacked).toEqual([spec2]); + expect(groupValues[0]?.nonStacked).toEqual([]); + expect(groupValues[1]?.nonStacked).toEqual([]); }); test('Should split specs by groupId, 1 group, stacked', () => { const spec1: BasicSeriesSpec = { @@ -300,8 +300,8 @@ describe('Y Domain', () => { const groupValues = [...splitSpecs.values()]; expect(groupKeys).toEqual(['group']); expect(groupValues.length).toBe(1); - expect(groupValues[0].stacked).toEqual([spec1, spec2]); - expect(groupValues[0].nonStacked).toEqual([]); + expect(groupValues[0]?.stacked).toEqual([spec1, spec2]); + expect(groupValues[0]?.nonStacked).toEqual([]); }); test('Should 3 split specs by groupId, 2 group, semi/stacked', () => { const spec1: BasicSeriesSpec = { @@ -348,10 +348,10 @@ describe('Y Domain', () => { const groupValues = [...splitSpecs.values()]; expect(groupKeys).toEqual(['group1', 'group2']); expect(groupValues.length).toBe(2); - expect(groupValues[0].stacked).toEqual([spec1, spec2]); - expect(groupValues[0].nonStacked).toEqual([]); - expect(groupValues[1].stacked).toEqual([spec3]); - expect(groupValues[0].nonStacked).toEqual([]); + expect(groupValues[0]?.stacked).toEqual([spec1, spec2]); + expect(groupValues[0]?.nonStacked).toEqual([]); + expect(groupValues[1]?.stacked).toEqual([spec3]); + expect(groupValues[0]?.nonStacked).toEqual([]); }); test('Should return a default Scale Linear for YScaleType when there are no specs', () => { @@ -418,10 +418,8 @@ describe('Y Domain', () => { store, ); - const { - yDomains: [{ domain }], - } = computeSeriesDomainsSelector(store.getState()); - expect(domain).toEqual([20, 20]); + const { yDomains } = computeSeriesDomainsSelector(store.getState()); + expect(yDomains[0]?.domain).toEqual([20, 20]); const warnMessage = 'custom yDomain for a is invalid, custom min is greater than computed max.'; expect(Logger.warn).toHaveBeenCalledWith(warnMessage); @@ -463,10 +461,8 @@ describe('Y Domain', () => { store, ); - const { - yDomains: [{ domain }], - } = computeSeriesDomainsSelector(store.getState()); - expect(domain).toEqual([-1, -1]); + const { yDomains } = computeSeriesDomainsSelector(store.getState()); + expect(yDomains[0]?.domain).toEqual([-1, -1]); const warnMessage = 'custom yDomain for a is invalid, custom max is less than computed max.'; expect(Logger.warn).toHaveBeenCalledWith(warnMessage); diff --git a/packages/charts/src/chart_types/xy_chart/domains/y_domain.ts b/packages/charts/src/chart_types/xy_chart/domains/y_domain.ts index ff6e814ec9..742259dc2f 100644 --- a/packages/charts/src/chart_types/xy_chart/domains/y_domain.ts +++ b/packages/charts/src/chart_types/xy_chart/domains/y_domain.ts @@ -56,11 +56,15 @@ function mergeYDomainForGroup( yScaleConfig: ScaleConfigs['y'], ): YDomain | null { const dataSeries = [...stacked, ...nonStacked]; - if (dataSeries.length === 0) return null; + if (!dataSeries[0]) return null; const [{ isStacked, stackMode, spec }] = dataSeries; const groupId = getSpecDomainGroupId(spec); - const { customDomain, type, nice, desiredTickCount } = yScaleConfig[groupId]; + const scaleConfig = yScaleConfig[groupId]; + + if (!scaleConfig) return null; + + const { customDomain, type, nice, desiredTickCount } = scaleConfig; const newCustomDomain: YDomainRange = customDomain ? { ...customDomain } : { min: NaN, max: NaN }; const { paddingUnit, padding, constrainPadding } = newCustomDomain; diff --git a/packages/charts/src/chart_types/xy_chart/legend/legend.test.ts b/packages/charts/src/chart_types/xy_chart/legend/legend.test.ts index ec480370f7..fc64d40ffb 100644 --- a/packages/charts/src/chart_types/xy_chart/legend/legend.test.ts +++ b/packages/charts/src/chart_types/xy_chart/legend/legend.test.ts @@ -297,9 +297,7 @@ describe('Legends', () => { ], store, ); - const { - formattedDataSeries: [{ key, specId }], - } = computeSeriesDomainsSelector(store.getState()); + const { key, specId } = computeSeriesDomainsSelector(store.getState()).formattedDataSeries[0]!; store.dispatch(onToggleDeselectSeriesAction([{ key, specId }])); const legend = computeLegendSelector(store.getState()); diff --git a/packages/charts/src/chart_types/xy_chart/legend/legend.ts b/packages/charts/src/chart_types/xy_chart/legend/legend.ts index 073a845fc8..a0833da2af 100644 --- a/packages/charts/src/chart_types/xy_chart/legend/legend.ts +++ b/packages/charts/src/chart_types/xy_chart/legend/legend.ts @@ -11,7 +11,7 @@ import { LegendItem } from '../../../common/legend'; import { SeriesKey, SeriesIdentifier } from '../../../common/series_id'; import { ScaleType } from '../../../scales/constants'; import { SettingsSpec, TickFormatterOptions } from '../../../specs'; -import { mergePartial } from '../../../utils/common'; +import { isDefined, mergePartial } from '../../../utils/common'; import { BandedAccessorType } from '../../../utils/geometry'; import { getLegendCompareFn, SeriesCompareFn } from '../../../utils/series_sort'; import { PointStyle, Theme } from '../../../utils/themes/theme'; @@ -181,16 +181,21 @@ export function computeLegend( const sortFn: SeriesCompareFn = settingsSpec.legendSort ?? legendSortFn; return groupBy( - legendItems.sort((a, b) => sortFn(a.seriesIdentifiers[0], b.seriesIdentifiers[0])), + legendItems.sort((a, b) => + a.seriesIdentifiers[0] && b.seriesIdentifiers[0] ? sortFn(a.seriesIdentifiers[0], b.seriesIdentifiers[0]) : 0, + ), ({ keys, childId }) => { return [...keys, childId].join('__'); // childId is used for band charts }, true, - ).map((d) => { - return { - ...d[0], - seriesIdentifiers: d.map(({ seriesIdentifiers: [s] }) => s), - path: d.map(({ path: [p] }) => p), - }; - }); + ) + .map((d) => { + if (!d[0]) return; + return { + ...d[0], + seriesIdentifiers: d.map(({ seriesIdentifiers: [s] }) => s).filter(isDefined), + path: d.map(({ path: [p] }) => p).filter(isDefined), + }; + }) + .filter(isDefined); } diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts index 61def6b0e1..9a12aab330 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/tick.ts @@ -47,7 +47,7 @@ export function renderTick( ...(axisPosition === Position.Left ? { x1: width, x2: width - tickSize } : { x1: 0, x2: tickSize }), }; const layered = typeof layer === 'number'; - const multilayerLuma = gridLine.lumaSteps[detailedLayer]; + const multilayerLuma = gridLine.lumaSteps[detailedLayer] ?? NaN; const strokeWidth = layered ? HIERARCHICAL_GRID_WIDTH : tickLine.strokeWidth; const color: RgbaTuple = layered ? [multilayerLuma, multilayerLuma, multilayerLuma, 1] : colorToRgba(tickLine.stroke); renderMultiLine(ctx, [xy], { color, width: strokeWidth }); diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/title.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/title.ts index 742fd6a373..85df7f85cd 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/title.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/title.ts @@ -75,6 +75,7 @@ export function renderTitle( 1, measureText(ctx), ); + if (!wrappedText[0]) return; if (debug) renderDebugRect(ctx, { x, y, width: horizontal ? width : height, height: font.fontSize }, rotation); renderText(ctx, { x: textX, y: textY }, wrappedText[0], font, rotation); } diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/points.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/points.ts index 4b9e6d086e..754e524659 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/points.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/points.ts @@ -54,7 +54,7 @@ export function renderPointGroup( .slice() .sort(({ radius: a }, { radius: b }) => b - a) .forEach(({ x, y, radius, transform, style, seriesIdentifier: { key }, panel }) => { - const { opacity } = geometryStateStyles[key]; + const opacity = geometryStateStyles[key]?.opacity ?? 1; const fill: Fill = { color: overrideOpacity(style.fill.color, (fillOpacity) => fillOpacity * opacity) }; const stroke: Stroke = { ...style.stroke, diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/primitives/text.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/primitives/text.ts index e1325e3641..22bd905407 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/primitives/text.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/primitives/text.ts @@ -95,6 +95,7 @@ export function wrapLines( const additionalWidth = shouldAddEllipsis ? getTextWidth(ELLIPSIS) : 0; for (let i = 0, max = lines.length; i < max; ++i) { let line = lines[i]; + if (!line) continue; let lineWidth = getTextWidth(line); if (lineWidth > maxWidth) { while (line.length > 0) { diff --git a/packages/charts/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx b/packages/charts/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx index f3683c974b..4014aba379 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx +++ b/packages/charts/src/chart_types/xy_chart/renderer/dom/annotations/annotations.tsx @@ -80,14 +80,13 @@ function renderAnnotationLineMarkers( animations?: AnnotationAnimationConfig[], ) { const getHoverParams = getAnnotationHoverParamsFn(hoveredIds, sharedStyle, animations); - return annotationLines.reduce((acc, props: AnnotationLineProps) => { - if (props.markers.length === 0) { - return acc; - } + return annotationLines.reduce((acc, { markers, ...props }: AnnotationLineProps) => { + if (!markers[0]) return acc; acc.push( & { +type LineMarkerProps = Pick & { + marker: AnnotationLineProps['markers'][number]; chartAreaRef: RefObject; chartDimensions: Dimensions; onDOMElementEnter: typeof onDOMElementEnterAction; @@ -52,7 +53,7 @@ export function LineMarker({ specId, datum, panel, - markers: [{ icon, body, color, position, alignment, dimension }], + marker: { icon, body, color, position, alignment, dimension }, chartAreaRef, chartDimensions, onDOMElementEnter, diff --git a/packages/charts/src/chart_types/xy_chart/rendering/rendering.areas.test.ts b/packages/charts/src/chart_types/xy_chart/rendering/rendering.areas.test.ts index 477f7a9f9c..60d0a7f174 100644 --- a/packages/charts/src/chart_types/xy_chart/rendering/rendering.areas.test.ts +++ b/packages/charts/src/chart_types/xy_chart/rendering/rendering.areas.test.ts @@ -79,7 +79,7 @@ describe('Rendering points - areas', () => { }), ]); const geometries = computeSeriesGeometriesSelector(store.getState()); - [{ value: areaGeometry }] = geometries.geometries.areas; + areaGeometry = geometries.geometries.areas[0]!.value; geometriesIndex = geometries.geometriesIndex; }); test('Can render an line and area paths', () => { @@ -135,7 +135,8 @@ describe('Rendering points - areas', () => { test('Can render two ordinal areas', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }, { value: secondArea }] = areas; + const { value: firstArea } = areas[0]!; + const { value: secondArea } = areas[1]!; expect(firstArea.lines[0]).toBe('M0,50L50,75'); expect(firstArea.area).toBe('M0,50L50,75L50,100L0,100Z'); expect(firstArea.color).toBe('red'); @@ -152,19 +153,19 @@ describe('Rendering points - areas', () => { }); test('can render first spec points', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }] = areas; + const { value: firstArea } = areas[0]!; expect(firstArea.points.length).toEqual(2); expect(firstArea.points).toMatchSnapshot(); }); test('can render second spec points', () => { const { areas } = geometries.geometries; - const [, { value: secondArea }] = areas; + const { value: secondArea } = areas[1]!; expect(secondArea.points.length).toEqual(2); expect(secondArea.points).toMatchSnapshot(); }); test('has the right number of geometry in the indexes', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }] = areas; + const { value: firstArea } = areas[0]!; expect(geometries.geometriesIndex.size).toEqual(firstArea.points.length); }); }); @@ -190,7 +191,7 @@ describe('Rendering points - areas', () => { test('Can render a linear area', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }] = areas; + const { value: firstArea } = areas[0]!; expect(firstArea.lines[0]).toBe('M0,0L100,50'); expect(firstArea.area).toBe('M0,0L100,50L100,100L0,100Z'); expect(firstArea.color).toBe('red'); @@ -200,7 +201,7 @@ describe('Rendering points - areas', () => { }); test('Can render two points', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }] = areas; + const { value: firstArea } = areas[0]!; expect(firstArea.points).toMatchSnapshot(); expect(geometries.geometriesIndex.size).toEqual(firstArea.points.length); }); @@ -238,7 +239,8 @@ describe('Rendering points - areas', () => { }); test('can render two linear areas', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }, { value: secondArea }] = areas; + const { value: firstArea } = areas[0]!; + const { value: secondArea } = areas[1]!; expect(firstArea.lines[0]).toBe('M0,50L100,75'); expect(firstArea.area).toBe('M0,50L100,75L100,100L0,100Z'); expect(firstArea.color).toBe('red'); @@ -255,14 +257,14 @@ describe('Rendering points - areas', () => { }); test('can render first spec points', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }] = areas; + const { value: firstArea } = areas[0]!; expect(firstArea.points.length).toEqual(2); expect(firstArea.points).toMatchSnapshot(); expect(geometries.geometriesIndex.size).toEqual(firstArea.points.length); }); test('can render second spec points', () => { const { areas } = geometries.geometries; - const [, { value: secondArea }] = areas; + const { value: secondArea } = areas[1]!; expect(secondArea.points.length).toEqual(2); expect(secondArea.points).toMatchSnapshot(); expect(geometries.geometriesIndex.size).toEqual(secondArea.points.length); @@ -289,7 +291,7 @@ describe('Rendering points - areas', () => { test('Can render a time area', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }] = areas; + const { value: firstArea } = areas[0]!; expect(firstArea.lines[0]).toBe('M0,0L100,50'); expect(firstArea.area).toBe('M0,0L100,50L100,100L0,100Z'); expect(firstArea.color).toBe('red'); @@ -299,7 +301,7 @@ describe('Rendering points - areas', () => { }); test('Can render two points', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }] = areas; + const { value: firstArea } = areas[0]!; expect(firstArea.points).toMatchSnapshot(); expect(geometries.geometriesIndex.size).toEqual(firstArea.points.length); }); @@ -337,13 +339,13 @@ describe('Rendering points - areas', () => { test('can render first spec points', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }] = areas; + const { value: firstArea } = areas[0]!; expect(firstArea.points).toMatchSnapshot(); expect(geometries.geometriesIndex.size).toEqual(firstArea.points.length); }); test('can render second spec points', () => { const { areas } = geometries.geometries; - const [, { value: secondArea }] = areas; + const { value: secondArea } = areas[1]!; expect(secondArea.points).toMatchSnapshot(); expect(geometries.geometriesIndex.size).toEqual(secondArea.points.length); @@ -378,8 +380,8 @@ describe('Rendering points - areas', () => { test('Can render a split area and line', () => { const { areas } = geometries.geometries; - const [{ value: firstArea }] = areas; - expect(firstArea.lines[0].split('M').length - 1).toBe(3); + const { value: firstArea } = areas[0]!; + expect(firstArea.lines[0]!.split('M').length - 1).toBe(3); expect(firstArea.area.split('M').length - 1).toBe(3); expect(firstArea.color).toBe('red'); expect(firstArea.seriesIdentifier.seriesKeys).toEqual([1]); @@ -391,11 +393,9 @@ describe('Rendering points - areas', () => { geometriesIndex, geometries: { areas }, } = geometries; - const [ - { - value: { points }, - }, - ] = areas; + const { + value: { points }, + } = areas[0]!; // all the points minus the undefined ones on a log scale expect(points.length).toBe(7); // all the points expect null geometries @@ -407,7 +407,7 @@ describe('Rendering points - areas', () => { expect(zeroValueIndexdGeometry).toBeDefined(); expect(zeroValueIndexdGeometry.length).toBe(1); // the zero value is scaled to NaN - expect(zeroValueIndexdGeometry[0].y).toBe(NaN); + expect(zeroValueIndexdGeometry[0]?.y).toBe(NaN); // default area theme point radius expect((zeroValueIndexdGeometry[0] as PointGeometry).radius).toBe(LIGHT_THEME.areaSeriesStyle.point.radius); }); @@ -443,7 +443,7 @@ describe('Rendering points - areas', () => { const store = initStore([pointSeriesSpec1, pointSeriesSpec2]); const domains = computeSeriesDomainsSelector(store.getState()); - expect(domains.formattedDataSeries[0].data).toMatchSnapshot(); + expect(domains.formattedDataSeries[0]?.data).toMatchSnapshot(); }); it('Stacked areas with null values', () => { const pointSeriesSpec1: AreaSeriesSpec = MockSeriesSpec.area({ @@ -473,7 +473,7 @@ describe('Rendering points - areas', () => { const store = initStore([pointSeriesSpec1, pointSeriesSpec2]); const domains = computeSeriesDomainsSelector(store.getState()); - expect(domains.formattedDataSeries[0].data).toMatchSnapshot(); - expect(domains.formattedDataSeries[1].data).toMatchSnapshot(); + expect(domains.formattedDataSeries[0]?.data).toMatchSnapshot(); + expect(domains.formattedDataSeries[1]?.data).toMatchSnapshot(); }); }); diff --git a/packages/charts/src/chart_types/xy_chart/rendering/rendering.bands.test.ts b/packages/charts/src/chart_types/xy_chart/rendering/rendering.bands.test.ts index 1be3f0589e..003acf9834 100644 --- a/packages/charts/src/chart_types/xy_chart/rendering/rendering.bands.test.ts +++ b/packages/charts/src/chart_types/xy_chart/rendering/rendering.bands.test.ts @@ -36,11 +36,9 @@ describe('Rendering bands - areas', () => { } = computeSeriesGeometriesSelector(store.getState()); test('Can render upper and lower lines and area paths', () => { - const [ - { - value: { lines, area, color, seriesIdentifier, transform }, - }, - ] = areas; + const { + value: { lines, area, color, seriesIdentifier, transform }, + } = areas[0]!; expect(lines.length).toBe(2); expect(lines[0]).toBe('M0,0L50,50'); expect(lines[1]).toBe('M0,80L50,70'); @@ -52,11 +50,9 @@ describe('Rendering bands - areas', () => { }); test('Can render two points', () => { - const [ - { - value: { points }, - }, - ] = areas; + const { + value: { points }, + } = areas[0]!; expect(points).toMatchSnapshot(); }); }); @@ -84,11 +80,9 @@ describe('Rendering bands - areas', () => { } = computeSeriesGeometriesSelector(store.getState()); test('Can render upper and lower lines and area paths', () => { - const [ - { - value: { lines, area, color, seriesIdentifier, transform }, - }, - ] = areas; + const { + value: { lines, area, color, seriesIdentifier, transform }, + } = areas[0]!; expect(lines.length).toBe(2); expect(lines[0]).toBe('M0,0ZM50,50L75,50'); expect(lines[1]).toBe('M0,80ZM50,70L75,70'); @@ -100,11 +94,9 @@ describe('Rendering bands - areas', () => { }); test('Can render two points', () => { - const [ - { - value: { points }, - }, - ] = areas; + const { + value: { points }, + } = areas[0]!; expect(points).toMatchSnapshot(); }); }); @@ -127,11 +119,7 @@ describe('Rendering bands - areas', () => { const store = MockStore.default(); const settings = MockGlobalSpec.settingsNoMargins({ theme: { colors: { vizColors: ['red', 'blue'] } } }); MockStore.addSpecs([barSeriesSpec, settings], store); - const { - geometries: { - bars: [{ value: bars }], - }, - } = computeSeriesGeometriesSelector(store.getState()); + const { value: bars } = computeSeriesGeometriesSelector(store.getState()).geometries.bars[0]!; test('Can render two bars', () => { expect(bars).toMatchSnapshot(); diff --git a/packages/charts/src/chart_types/xy_chart/rendering/rendering.bars.test.ts b/packages/charts/src/chart_types/xy_chart/rendering/rendering.bars.test.ts index 44e4e827b7..8245583b58 100644 --- a/packages/charts/src/chart_types/xy_chart/rendering/rendering.bars.test.ts +++ b/packages/charts/src/chart_types/xy_chart/rendering/rendering.bars.test.ts @@ -36,7 +36,7 @@ describe('Rendering bars', () => { ); const { geometries } = computeSeriesGeometriesSelector(store.getState()); - expect(geometries.bars[0].value).toMatchSnapshot(); + expect(geometries.bars[0]?.value).toMatchSnapshot(); }); describe('Single series bar chart - ordinal', () => { @@ -67,7 +67,7 @@ describe('Rendering bars', () => { store, ); const { geometries } = computeSeriesGeometriesSelector(store.getState()); - expect(geometries.bars[0].value[0].displayValue).toBeDefined(); + expect(geometries.bars[0]?.value[0]?.displayValue).toBeDefined(); }); test('Can hide value labels if no formatter or showValueLabels is false/undefined', () => { @@ -97,7 +97,7 @@ describe('Rendering bars', () => { store, ); const { geometries } = computeSeriesGeometriesSelector(store.getState()); - expect(geometries.bars[0].value[0].displayValue).toBeUndefined(); + expect(geometries.bars[0]?.value[0]?.displayValue).toBeUndefined(); }); test('Can render bars with alternating value labels', () => { @@ -128,8 +128,8 @@ describe('Rendering bars', () => { ); const { geometries } = computeSeriesGeometriesSelector(store.getState()); - expect(geometries.bars[0].value[0].displayValue?.text).toBeDefined(); - expect(geometries.bars[0].value[1].displayValue?.text).toBeUndefined(); + expect(geometries.bars[0]?.value[0]?.displayValue?.text).toBeDefined(); + expect(geometries.bars[0]?.value[1]?.displayValue?.text).toBeUndefined(); }); test('Can render bars with contained value labels', () => { @@ -160,7 +160,7 @@ describe('Rendering bars', () => { ); const { geometries } = computeSeriesGeometriesSelector(store.getState()); - expect(geometries.bars[0].value[0].displayValue?.width).toBe(50); + expect(geometries.bars[0]?.value[0]?.displayValue?.width).toBe(50); }); }); describe('Multi series bar chart - ordinal', () => { @@ -205,10 +205,10 @@ describe('Rendering bars', () => { } = computeSeriesGeometriesSelector(store.getState()); test('can render first spec bars', () => { - expect(bars[0].value).toMatchSnapshot(); + expect(bars[0]?.value).toMatchSnapshot(); }); test('can render second spec bars', () => { - expect(bars[1].value).toMatchSnapshot(); + expect(bars[1]?.value).toMatchSnapshot(); }); }); }); diff --git a/packages/charts/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts b/packages/charts/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts index 64d0678e37..e660b394a3 100644 --- a/packages/charts/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts +++ b/packages/charts/src/chart_types/xy_chart/rendering/rendering.bubble.test.ts @@ -36,18 +36,16 @@ describe('Rendering points - bubble', () => { geometriesIndex, } = computeSeriesGeometriesSelector(store.getState()); test('Can render a bubble', () => { - const [{ value: bubbleGeometry }] = bubbles; + const { value: bubbleGeometry } = bubbles[0]!; expect(bubbleGeometry.points).toHaveLength(2); expect(bubbleGeometry.color).toBe('red'); expect(bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); expect(bubbleGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); }); test('Can render two points', () => { - const [ - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[0]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); @@ -89,7 +87,8 @@ describe('Rendering points - bubble', () => { } = computeSeriesGeometriesSelector(store.getState()); test('Can render two ordinal bubbles', () => { - const [{ value: firstBubble }, { value: secondBubble }] = bubbles; + const { value: firstBubble } = bubbles[0]!; + const { value: secondBubble } = bubbles[1]!; expect(firstBubble.points).toHaveLength(2); expect(firstBubble.color).toBe('red'); expect(firstBubble.seriesIdentifier.seriesKeys).toEqual([1]); @@ -102,20 +101,15 @@ describe('Rendering points - bubble', () => { expect(geometriesIndex.size).toEqual(4); }); test('can render first spec points', () => { - const [ - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[0]!; expect(points).toMatchSnapshot(); }); test('can render second spec points', () => { - const [ - , - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[1]!; expect(points).toMatchSnapshot(); }); }); @@ -141,18 +135,16 @@ describe('Rendering points - bubble', () => { } = computeSeriesGeometriesSelector(store.getState()); test('Can render a linear bubble', () => { - const [{ value: bubbleGeometry }] = bubbles; + const { value: bubbleGeometry } = bubbles[0]!; expect(bubbleGeometry.points).toHaveLength(2); expect(bubbleGeometry.color).toBe('red'); expect(bubbleGeometry.seriesIdentifier.seriesKeys).toEqual([1]); expect(bubbleGeometry.seriesIdentifier.specId).toEqual(SPEC_ID); }); test('Can render two points', () => { - const [ - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[0]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); }); @@ -194,7 +186,8 @@ describe('Rendering points - bubble', () => { } = computeSeriesGeometriesSelector(store.getState()); test('can render two linear bubbles', () => { - const [{ value: firstBubble }, { value: secondBubble }] = bubbles; + const { value: firstBubble } = bubbles[0]!; + const { value: secondBubble } = bubbles[1]!; expect(firstBubble.points).toHaveLength(2); expect(firstBubble.color).toBe('red'); expect(firstBubble.seriesIdentifier.seriesKeys).toEqual([1]); @@ -207,20 +200,15 @@ describe('Rendering points - bubble', () => { expect(geometriesIndex.size).toEqual(4); }); test('can render first spec points', () => { - const [ - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[0]!; expect(points).toMatchSnapshot(); }); test('can render second spec points', () => { - const [ - , - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[1]!; expect(points).toMatchSnapshot(); }); }); @@ -246,18 +234,16 @@ describe('Rendering points - bubble', () => { } = computeSeriesGeometriesSelector(store.getState()); test('Can render a time bubble', () => { - const [{ value: renderedBubble }] = bubbles; + const { value: renderedBubble } = bubbles[0]!; expect(renderedBubble.points).toHaveLength(2); expect(renderedBubble.color).toBe('red'); expect(renderedBubble.seriesIdentifier.seriesKeys).toEqual([1]); expect(renderedBubble.seriesIdentifier.specId).toEqual(SPEC_ID); }); test('Can render two points', () => { - const [ - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[0]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); }); @@ -297,21 +283,16 @@ describe('Rendering points - bubble', () => { geometriesIndex, } = computeSeriesGeometriesSelector(store.getState()); test('can render first spec points', () => { - const [ - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[0]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(4); }); test('can render second spec points', () => { - const [ - , - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[1]!; expect(points).toMatchSnapshot(); }); }); @@ -344,18 +325,16 @@ describe('Rendering points - bubble', () => { } = computeSeriesGeometriesSelector(store.getState()); test('Can render a split bubble', () => { - const [{ value: renderedBubble }] = bubbles; + const { value: renderedBubble } = bubbles[0]!; expect(renderedBubble.points).toHaveLength(7); expect(renderedBubble.color).toBe('red'); expect(renderedBubble.seriesIdentifier.seriesKeys).toEqual([1]); expect(renderedBubble.seriesIdentifier.specId).toEqual(SPEC_ID); }); test('Can render points', () => { - const [ - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[0]!; // all the points minus the undefined ones on a log scale expect(points.length).toBe(7); // we expect the same size of geometries as we exclude non-finite ones @@ -388,11 +367,9 @@ describe('Rendering points - bubble', () => { geometriesIndex, } = computeSeriesGeometriesSelector(store.getState()); test('Should render 2 points', () => { - const [ - { - value: { points }, - }, - ] = bubbles; + const { + value: { points }, + } = bubbles[0]!; // will not render the 4th point that is out of x domain, the 3rd point is not rendered due to the y Domain max of 1 expect(points).toHaveLength(2); // will keep the 3rd point as an indexedGeometry diff --git a/packages/charts/src/chart_types/xy_chart/rendering/rendering.lines.test.ts b/packages/charts/src/chart_types/xy_chart/rendering/rendering.lines.test.ts index aceb5430d4..83e711c50e 100644 --- a/packages/charts/src/chart_types/xy_chart/rendering/rendering.lines.test.ts +++ b/packages/charts/src/chart_types/xy_chart/rendering/rendering.lines.test.ts @@ -41,7 +41,7 @@ describe('Rendering points - line', () => { } = computeSeriesGeometriesSelector(store.getState()); test('Can render a line', () => { - const [{ value: lineGeometry }] = lines; + const { value: lineGeometry } = lines[0]!; expect(lineGeometry.line).toBe('M0,0L50,50'); expect(lineGeometry.color).toBe('red'); expect(lineGeometry.seriesIdentifier.seriesKeys).toEqual([1]); @@ -49,11 +49,9 @@ describe('Rendering points - line', () => { expect(lineGeometry.transform).toEqual({ x: 25, y: 0 }); }); test('Can render two points', () => { - const [ - { - value: { points }, - }, - ] = lines; + const { + value: { points }, + } = lines[0]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); @@ -97,7 +95,8 @@ describe('Rendering points - line', () => { } = computeSeriesGeometriesSelector(store.getState()); test('Can render two ordinal lines', () => { - const [{ value: firstLine }, { value: secondLine }] = lines; + const { value: firstLine } = lines[0]!; + const { value: secondLine } = lines[1]!; expect(firstLine.color).toBe('red'); expect(firstLine.seriesIdentifier.seriesKeys).toEqual([1]); expect(firstLine.seriesIdentifier.specId).toEqual(spec1Id); @@ -110,21 +109,16 @@ describe('Rendering points - line', () => { expect(secondLine.transform).toEqual({ x: 25, y: 0 }); }); test('can render first spec points', () => { - const [ - { - value: { points }, - }, - ] = lines; + const { + value: { points }, + } = lines[0]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); }); test('can render second spec points', () => { - const [ - , - { - value: { points }, - }, - ] = lines; + const { + value: { points }, + } = lines[1]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); }); @@ -152,7 +146,7 @@ describe('Rendering points - line', () => { } = computeSeriesGeometriesSelector(store.getState()); test('Can render a linear line', () => { - const [{ value: renderedLine }] = lines; + const { value: renderedLine } = lines[0]!; expect(renderedLine.line).toBe('M0,0L100,50'); expect(renderedLine.color).toBe('red'); expect(renderedLine.seriesIdentifier.seriesKeys).toEqual([1]); @@ -160,11 +154,9 @@ describe('Rendering points - line', () => { expect(renderedLine.transform).toEqual({ x: 0, y: 0 }); }); test('Can render two points', () => { - const [ - { - value: { points }, - }, - ] = lines; + const { + value: { points }, + } = lines[0]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); }); @@ -205,7 +197,8 @@ describe('Rendering points - line', () => { } = computeSeriesGeometriesSelector(store.getState()); test('can render two linear lines', () => { - const [{ value: firstLine }, { value: secondLine }] = lines; + const { value: firstLine } = lines[0]!; + const { value: secondLine } = lines[1]!; expect(firstLine.line).toBe('M0,50L100,75'); expect(firstLine.color).toBe('red'); expect(firstLine.seriesIdentifier.seriesKeys).toEqual([1]); @@ -219,21 +212,16 @@ describe('Rendering points - line', () => { expect(secondLine.transform).toEqual({ x: 0, y: 0 }); }); test('can render first spec points', () => { - const [ - { - value: { points }, - }, - ] = lines; + const { + value: { points }, + } = lines[0]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); }); test('can render second spec points', () => { - const [ - , - { - value: { points }, - }, - ] = lines; + const { + value: { points }, + } = lines[1]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); }); @@ -261,7 +249,7 @@ describe('Rendering points - line', () => { } = computeSeriesGeometriesSelector(store.getState()); test('Can render a time line', () => { - const [{ value: renderedLine }] = lines; + const { value: renderedLine } = lines[0]!; expect(renderedLine.line).toBe('M0,0L100,50'); expect(renderedLine.color).toBe('red'); expect(renderedLine.seriesIdentifier.seriesKeys).toEqual([1]); @@ -269,11 +257,9 @@ describe('Rendering points - line', () => { expect(renderedLine.transform).toEqual({ x: 0, y: 0 }); }); test('Can render two points', () => { - const [ - { - value: { points }, - }, - ] = lines; + const { + value: { points }, + } = lines[0]!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); }); @@ -318,14 +304,14 @@ describe('Rendering points - line', () => { test('can render first spec points', () => { const { value: { points }, - } = firstLine; + } = firstLine!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); }); test('can render second spec points', () => { const { value: { points }, - } = secondLine; + } = secondLine!; expect(points).toMatchSnapshot(); expect(geometriesIndex.size).toEqual(points.length); }); @@ -359,7 +345,7 @@ describe('Rendering points - line', () => { } = computeSeriesGeometriesSelector(store.getState()); test('should render a split line', () => { - const [{ value: renderedLine }] = lines; + const { value: renderedLine } = lines[0]!; expect(renderedLine.line.split('M').length - 1).toBe(3); expect(renderedLine.color).toBe('red'); expect(renderedLine.seriesIdentifier.seriesKeys).toEqual([1]); @@ -367,11 +353,9 @@ describe('Rendering points - line', () => { expect(renderedLine.transform).toEqual({ x: 0, y: 0 }); }); test('should render points', () => { - const [ - { - value: { points }, - }, - ] = lines; + const { + value: { points }, + } = lines[0]!; // all the points minus the undefined ones on a log scale expect(points.length).toBe(7); // all the points including null geometries @@ -415,11 +399,9 @@ describe('Rendering points - line', () => { geometriesIndex, } = computeSeriesGeometriesSelector(store.getState()); test('should render 3 points', () => { - const [ - { - value: { points }, - }, - ] = lines; + const { + value: { points }, + } = lines[0]!; // will not render the 4th point is out of the x domain, will not keep the 3rd point which is out of the y Domain expect(points.length).toBe(2); // will keep the 3rd point as an indexedGeometry @@ -449,7 +431,7 @@ describe('Rendering points - line', () => { const store = MockStore.default(); MockStore.addSpecs([pointSeriesSpec, axis], store); // eslint-disable-next-line prefer-destructuring - points = computeSeriesGeometriesSelector(store.getState()).geometries.lines[0].value.points; + points = computeSeriesGeometriesSelector(store.getState()).geometries.lines[0]!.value.points; }); describe.each([ diff --git a/packages/charts/src/chart_types/xy_chart/rendering/rendering.test.ts b/packages/charts/src/chart_types/xy_chart/rendering/rendering.test.ts index ac6700d741..c132e7f886 100644 --- a/packages/charts/src/chart_types/xy_chart/rendering/rendering.test.ts +++ b/packages/charts/src/chart_types/xy_chart/rendering/rendering.test.ts @@ -361,7 +361,7 @@ describe('Rendering utils', () => { const xScale = MockScale.default({ scale: jest.fn().mockImplementation((x) => x), bandwidth: 0, - range: [dataSeries.data[0].x as number, dataSeries.data[12].x as number], + range: [dataSeries.data[0]!.x as number, dataSeries.data[12]!.x as number], }); it('should return array pairs of non-null x regions with null end values', () => { @@ -380,7 +380,7 @@ describe('Rendering utils', () => { const data = dataSeries.data.slice(1, -1); const xScale = MockScale.default({ scale: jest.fn().mockImplementation((x) => x), - range: [data[0].x as number, data[10].x as number], + range: [data[0]!.x as number, data[10]!.x as number], }); const actual = getClippedRanges(data, xScale, 0); @@ -396,7 +396,7 @@ describe('Rendering utils', () => { const xScale = MockScale.default({ scale: jest.fn().mockImplementation((x) => x), bandwidth, - range: [dataSeries.data[0].x as number, (dataSeries.data[12].x as number) + bandwidth * (2 / 3)], + range: [dataSeries.data[0]!.x as number, (dataSeries.data[12]!.x as number) + bandwidth * (2 / 3)], }); const actual = getClippedRanges(dataSeries.data, xScale, 0); @@ -422,9 +422,9 @@ describe('Rendering utils', () => { it('should call scale to get x value for each datum', () => { getClippedRanges(dataSeries.data, xScale, 0); - expect(xScale.scale).toHaveBeenNthCalledWith(1, dataSeries.data[0].x); + expect(xScale.scale).toHaveBeenNthCalledWith(1, dataSeries.data[0]!.x); expect(xScale.scale).toHaveBeenCalledTimes(dataSeries.data.length); - expect(xScale.scale).toHaveBeenCalledWith(dataSeries.data[12].x); + expect(xScale.scale).toHaveBeenCalledWith(dataSeries.data[12]!.x); }); }); describe('#getRadiusFn', () => { @@ -464,7 +464,7 @@ describe('Rendering utils', () => { 15.29, 40.89, 13.39, 36.81, 44.66, 44.34, 51.01, 6.97, 34.04, 49.07, 45.11, 25.44, 8.98, 9.33, 50.62, 48.89, 44.34, 1, 33.09, 5.94, ]; - it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedValues[i]]))( + it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedValues[i]!]))( 'should return stepped value from domain - data[%#]', (mark, expected) => { expect(getRadius(mark)).toBeCloseTo(expected, 1); @@ -504,7 +504,7 @@ describe('Rendering utils', () => { describe('markSizeRatio - 1', () => { const getRadius = getRadiusFn(data, 1, 1); const expectedRadii = [2.62, 2.59, 1, 2.73, 2.63]; - it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]]))( + it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]!]))( 'should return stepped value - data[%#]', (mark, expected) => { expect(getRadius(mark)).toBeCloseTo(expected, 1); @@ -515,7 +515,7 @@ describe('Rendering utils', () => { describe('markSizeRatio - 10', () => { const getRadius = getRadiusFn(data, 1, 10); const expectedRadii = [9.09, 8.56, 1, 11.1, 9.38]; - it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]]))( + it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]!]))( 'should return stepped value - data[%#]', (mark, expected) => { expect(getRadius(mark)).toBeCloseTo(expected, 1); @@ -526,7 +526,7 @@ describe('Rendering utils', () => { describe('markSizeRatio - 100', () => { const getRadius = getRadiusFn(data, 1, 100); const expectedRadii = [80.71, 75.37, 1, 101, 83.61]; - it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]]))( + it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]!]))( 'should return stepped value - data[%#]', (mark, expected) => { expect(getRadius(mark)).toBeCloseTo(expected, 1); @@ -538,7 +538,7 @@ describe('Rendering utils', () => { // Should be treated as 100 const getRadius = getRadiusFn(data, 1, 1000); const expectedRadii = [80.71, 75.37, 1, 101, 83.61]; - it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]]))( + it.each<[number | null, number]>(data.map(({ mark }, i) => [mark, expectedRadii[i]!]))( 'should return stepped value - data[%#]', (mark, expected) => { expect(getRadius(mark)).toBeCloseTo(expected, 1); diff --git a/packages/charts/src/chart_types/xy_chart/rendering/utils.ts b/packages/charts/src/chart_types/xy_chart/rendering/utils.ts index 7c34238e39..63e6dad9f4 100644 --- a/packages/charts/src/chart_types/xy_chart/rendering/utils.ts +++ b/packages/charts/src/chart_types/xy_chart/rendering/utils.ts @@ -201,5 +201,5 @@ export function getY0ScaledValueFn(yScale: ScaleContinuous): (datum: DataSeriesD function getDomainPolarity(domain: number[]): number { // 1 if both numbers are positive, -1 if both are negative, 0 if zeros or mixed - return Math.sign(Math.sign(domain[0]) + Math.sign(domain[1])); + return Math.sign(Math.sign(domain[0] ?? NaN) + Math.sign(domain[1] ?? NaN)); } diff --git a/packages/charts/src/chart_types/xy_chart/state/chart_state.interactions.test.tsx b/packages/charts/src/chart_types/xy_chart/state/chart_state.interactions.test.tsx index 1f2ab7e85f..3f9a4e3476 100644 --- a/packages/charts/src/chart_types/xy_chart/state/chart_state.interactions.test.tsx +++ b/packages/charts/src/chart_types/xy_chart/state/chart_state.interactions.test.tsx @@ -104,7 +104,7 @@ describe('Chart state pointer interactions', () => { const { geometries } = computeSeriesGeometriesSelector(store.getState()); expect(geometries).toBeDefined(); expect(geometries.bars).toBeDefined(); - expect(geometries.bars[0].value.length).toBe(2); + expect(geometries.bars[0]?.value.length).toBe(2); }); test('can convert/limit mouse pointer positions relative to chart projection', () => { @@ -272,7 +272,7 @@ describe('Chart state pointer interactions', () => { store.dispatch(onPointerMove({ x: chartLeft + 10, y: chartTop + 10 }, 0)); MockStore.flush(store); expect(onPointerUpdateListener).toHaveBeenCalledTimes(1); - expect(onPointerUpdateListener.mock.calls[0][0]).toMatchObject({ + expect(onPointerUpdateListener.mock.calls[0]?.[0]).toMatchObject({ x: 0, y: [ { @@ -785,8 +785,8 @@ describe('Chart state pointer interactions', () => { const tooltipInfo = getHighlightedTooltipTooltipValuesSelector(store.getState()); expect(tooltipInfo.tooltip.header?.value).toBe(0); expect(tooltipInfo.tooltip.header?.formattedValue).toBe('bottom 0'); - expect(tooltipInfo.tooltip.values[0].value).toBe(10); - expect(tooltipInfo.tooltip.values[0].formattedValue).toBe('left 10'); + expect(tooltipInfo.tooltip.values[0]?.value).toBe(10); + expect(tooltipInfo.tooltip.values[0]?.formattedValue).toBe('left 10'); }); test('chart 90 deg rotated', () => { @@ -800,8 +800,8 @@ describe('Chart state pointer interactions', () => { const tooltipInfo = getHighlightedTooltipTooltipValuesSelector(store.getState()); expect(tooltipInfo.tooltip.header?.value).toBe(1); expect(tooltipInfo.tooltip.header?.formattedValue).toBe('left 1'); - expect(tooltipInfo.tooltip.values[0].value).toBe(5); - expect(tooltipInfo.tooltip.values[0].formattedValue).toBe('bottom 5'); + expect(tooltipInfo.tooltip.values[0]?.value).toBe(5); + expect(tooltipInfo.tooltip.values[0]?.formattedValue).toBe('bottom 5'); }); }); describe('brush', () => { @@ -863,7 +863,7 @@ describe('Chart state pointer interactions', () => { expect(brushEndListener).not.toHaveBeenCalled(); } else { expect(brushEndListener).toHaveBeenCalled(); - expect(brushEndListener.mock.calls[1][0]).toEqual({ x: [2.5, 3] }); + expect(brushEndListener.mock.calls[1]?.[0]).toEqual({ x: [2.5, 3] }); } const start3 = { x: 75, y: 0 }; @@ -875,7 +875,7 @@ describe('Chart state pointer interactions', () => { expect(brushEndListener).not.toHaveBeenCalled(); } else { expect(brushEndListener).toHaveBeenCalled(); - expect(brushEndListener.mock.calls[2][0]).toEqual({ x: [2.5, 3] }); + expect(brushEndListener.mock.calls[2]?.[0]).toEqual({ x: [2.5, 3] }); } const start4 = { x: 25, y: 0 }; @@ -887,7 +887,7 @@ describe('Chart state pointer interactions', () => { expect(brushEndListener).not.toHaveBeenCalled(); } else { expect(brushEndListener).toHaveBeenCalled(); - expect(brushEndListener.mock.calls[3][0]).toEqual({ x: [0, 0.5] }); + expect(brushEndListener.mock.calls[3]?.[0]).toEqual({ x: [0, 0.5] }); } store.dispatch(onMouseDown({ x: 25, y: 0 }, 1300)); @@ -944,7 +944,7 @@ describe('Chart state pointer interactions', () => { expect(brushEndListener).not.toHaveBeenCalled(); } else { expect(brushEndListener).toHaveBeenCalled(); - expect(brushEndListener.mock.calls[1][0]).toEqual({ x: [1, 1] }); + expect(brushEndListener.mock.calls[1]?.[0]).toEqual({ x: [1, 1] }); } const start3 = { x: 0, y: 75 }; @@ -956,7 +956,7 @@ describe('Chart state pointer interactions', () => { expect(brushEndListener).not.toHaveBeenCalled(); } else { expect(brushEndListener).toHaveBeenCalled(); - expect(brushEndListener.mock.calls[2][0]).toEqual({ x: [1, 1] }); // max of chart + expect(brushEndListener.mock.calls[2]?.[0]).toEqual({ x: [1, 1] }); // max of chart } const start4 = { x: 0, y: 25 }; @@ -968,7 +968,7 @@ describe('Chart state pointer interactions', () => { expect(brushEndListener).not.toHaveBeenCalled(); } else { expect(brushEndListener).toHaveBeenCalled(); - expect(brushEndListener.mock.calls[3][0]).toEqual({ x: [0, 0] }); + expect(brushEndListener.mock.calls[3]?.[0]).toEqual({ x: [0, 0] }); } }); test('can respond to a Y brush', () => { @@ -1037,7 +1037,7 @@ describe('Chart state pointer interactions', () => { expect(brushEndListener).not.toHaveBeenCalled(); } else { expect(brushEndListener).toHaveBeenCalled(); - expect(brushEndListener.mock.calls[1][0]).toEqual({ + expect(brushEndListener.mock.calls[1]?.[0]).toEqual({ y: [ { groupId: spec.groupId, @@ -1114,7 +1114,7 @@ describe('Chart state pointer interactions', () => { expect(brushEndListener).not.toHaveBeenCalled(); } else { expect(brushEndListener).toHaveBeenCalled(); - expect(brushEndListener.mock.calls[1][0]).toEqual({ + expect(brushEndListener.mock.calls[1]?.[0]).toEqual({ x: [2.5, 3], y: [ { @@ -1166,7 +1166,7 @@ describe('Negative bars click and hover', () => { store.dispatch(onPointerMove({ x: 50, y: 75 }, 0)); const highlightedGeoms = getHighlightedGeomsSelector(store.getState()); expect(highlightedGeoms.length).toBe(1); - expect(highlightedGeoms[0].value.datum).toEqual([1, -10]); + expect(highlightedGeoms[0]?.value.datum).toEqual([1, -10]); }); test('click negative bars', () => { store.dispatch(onPointerMove({ x: 50, y: 75 }, 0)); @@ -1174,7 +1174,7 @@ describe('Negative bars click and hover', () => { store.dispatch(onMouseUp({ x: 50, y: 75 }, 200)); expect(onElementClick).toHaveBeenCalled(); - const callArgs = onElementClick.mock.calls[0][0]; + const callArgs = onElementClick.mock.calls[0]?.[0]; expect(callArgs[0][0].datum).toEqual([1, -10]); }); }); @@ -1227,7 +1227,7 @@ describe('Clickable annotations', () => { store.dispatch(onMouseUp({ x: 130, y: 217 }, 200)); expect(onAnnotationClick).toHaveBeenCalled(); - const callArgs = onAnnotationClick.mock.calls[0][0]; + const callArgs = onAnnotationClick.mock.calls[0]?.[0]; expect(callArgs.rects[0].id).toEqual('rect1______0__1__0__4__details about this annotation__0'); // confirming there is only one rect annotation being picked up expect(callArgs.rects.length).toEqual(1); @@ -1294,7 +1294,7 @@ describe('Clickable annotations', () => { store.dispatch(onMouseUp({ x: 200, y: 195 }, 200)); expect(onAnnotationClick).toHaveBeenCalled(); - const callArgs = onAnnotationClick.mock.calls[0][0]; + const callArgs = onAnnotationClick.mock.calls[0]?.[0]; expect(callArgs.rects[1]).toMatchObject({ datum: { coordinates: { diff --git a/packages/charts/src/chart_types/xy_chart/state/chart_state.timescales.test.ts b/packages/charts/src/chart_types/xy_chart/state/chart_state.timescales.test.ts index 8c88f61f61..e007200a79 100644 --- a/packages/charts/src/chart_types/xy_chart/state/chart_state.timescales.test.ts +++ b/packages/charts/src/chart_types/xy_chart/state/chart_state.timescales.test.ts @@ -75,7 +75,7 @@ describe('Render chart', () => { expect(geometries).toBeDefined(); expect(geometries.lines).toBeDefined(); expect(geometries.lines.length).toBe(1); - expect(geometries.lines[0].value.points.length).toBe(3); + expect(geometries.lines[0]?.value.points.length).toBe(3); }); test('check mouse position correctly return inverted value', () => { store.dispatch(onPointerMove({ x: 15, y: 10 }, 0)); // check first valid tooltip @@ -83,22 +83,22 @@ describe('Render chart', () => { expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(day1); expect(tooltip.header?.formattedValue).toBe(`${day1}`); - expect(tooltip.values[0].value).toBe(10); - expect(tooltip.values[0].formattedValue).toBe(`${10}`); + expect(tooltip.values[0]?.value).toBe(10); + expect(tooltip.values[0]?.formattedValue).toBe(`${10}`); store.dispatch(onPointerMove({ x: 35, y: 10 }, 1)); // check second valid tooltip tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(day2); expect(tooltip.header?.formattedValue).toBe(`${day2}`); - expect(tooltip.values[0].value).toBe(22); - expect(tooltip.values[0].formattedValue).toBe(`${22}`); + expect(tooltip.values[0]?.value).toBe(22); + expect(tooltip.values[0]?.formattedValue).toBe(`${22}`); store.dispatch(onPointerMove({ x: 76, y: 10 }, 2)); // check third valid tooltip tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(day3); expect(tooltip.header?.formattedValue).toBe(`${day3}`); - expect(tooltip.values[0].value).toBe(6); - expect(tooltip.values[0].formattedValue).toBe(`${6}`); + expect(tooltip.values[0]?.value).toBe(6); + expect(tooltip.values[0]?.formattedValue).toBe(`${6}`); }); }); describe('line, utc-time, 5m interval', () => { @@ -149,7 +149,7 @@ describe('Render chart', () => { expect(geometries).toBeDefined(); expect(geometries.lines).toBeDefined(); expect(geometries.lines.length).toBe(1); - expect(geometries.lines[0].value.points.length).toBe(3); + expect(geometries.lines[0]?.value.points.length).toBe(3); }); test('check mouse position correctly return inverted value', () => { store.dispatch(onPointerMove({ x: 15, y: 10 }, 0)); // check first valid tooltip @@ -157,22 +157,22 @@ describe('Render chart', () => { expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date1); expect(tooltip.header?.formattedValue).toBe(`${date1}`); - expect(tooltip.values[0].value).toBe(10); - expect(tooltip.values[0].formattedValue).toBe(`${10}`); + expect(tooltip.values[0]?.value).toBe(10); + expect(tooltip.values[0]?.formattedValue).toBe(`${10}`); store.dispatch(onPointerMove({ x: 35, y: 10 }, 1)); // check second valid tooltip tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date2); expect(tooltip.header?.formattedValue).toBe(`${date2}`); - expect(tooltip.values[0].value).toBe(22); - expect(tooltip.values[0].formattedValue).toBe(`${22}`); + expect(tooltip.values[0]?.value).toBe(22); + expect(tooltip.values[0]?.formattedValue).toBe(`${22}`); store.dispatch(onPointerMove({ x: 76, y: 10 }, 2)); // check third valid tooltip tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date3); expect(tooltip.header?.formattedValue).toBe(`${date3}`); - expect(tooltip.values[0].value).toBe(6); - expect(tooltip.values[0].formattedValue).toBe(`${6}`); + expect(tooltip.values[0]?.value).toBe(6); + expect(tooltip.values[0]?.formattedValue).toBe(`${6}`); }); }); describe('line, non utc-time, 5m + 1s interval', () => { @@ -222,7 +222,7 @@ describe('Render chart', () => { expect(geometries).toBeDefined(); expect(geometries.lines).toBeDefined(); expect(geometries.lines.length).toBe(1); - expect(geometries.lines[0].value.points.length).toBe(3); + expect(geometries.lines[0]?.value.points.length).toBe(3); }); test('check scale values', () => { const xValues = [date1, date2, date3]; @@ -249,22 +249,22 @@ describe('Render chart', () => { expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date1); expect(tooltip.header?.formattedValue).toBe(`${date1}`); - expect(tooltip.values[0].value).toBe(10); - expect(tooltip.values[0].formattedValue).toBe(`${10}`); + expect(tooltip.values[0]?.value).toBe(10); + expect(tooltip.values[0]?.formattedValue).toBe(`${10}`); store.dispatch(onPointerMove({ x: 35, y: 10 }, 1)); // check second valid tooltip tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date2); expect(tooltip.header?.formattedValue).toBe(`${date2}`); - expect(tooltip.values[0].value).toBe(22); - expect(tooltip.values[0].formattedValue).toBe(`${22}`); + expect(tooltip.values[0]?.value).toBe(22); + expect(tooltip.values[0]?.formattedValue).toBe(`${22}`); store.dispatch(onPointerMove({ x: 76, y: 10 }, 2)); // check third valid tooltip tooltip = getTooltipInfoSelector(store.getState()); expect(tooltip.values.length).toBe(1); expect(tooltip.header?.value).toBe(date3); expect(tooltip.header?.formattedValue).toBe(`${date3}`); - expect(tooltip.values[0].value).toBe(6); - expect(tooltip.values[0].formattedValue).toBe(`${6}`); + expect(tooltip.values[0]?.value).toBe(6); + expect(tooltip.values[0]?.formattedValue).toBe(`${6}`); }); }); }); diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/count_bars_in_cluster.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/count_bars_in_cluster.ts index a8f053cd4c..e61fe6b0d2 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/count_bars_in_cluster.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/count_bars_in_cluster.ts @@ -31,7 +31,7 @@ export function countBarsInCluster({ formattedDataSeries }: SeriesDomainsAndData ); const barIndexByPanel = Object.keys(dataSeriesGroupedByPanel).reduce>((acc, panelKey) => { - const panelBars = dataSeriesGroupedByPanel[panelKey]; + const panelBars = dataSeriesGroupedByPanel[panelKey] ?? []; const barDataSeriesByBarIndex = groupBy( panelBars, (d) => { diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.test.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.test.ts index 7bcf7cc4c4..fe2fe1da05 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.test.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.test.ts @@ -76,8 +76,8 @@ describe('GroupIds and useDefaultGroupId', () => { const geoms = computeSeriesGeometriesSelector(store.getState()); const { bars } = geoms.geometries; expect(bars).toHaveLength(3); - expect(bars[0].value[0].width).toBe(40); - expect(bars[1].value[0].width).toBe(40); - expect(bars[2].value[0].width).toBe(40); + expect(bars[0]?.value[0]?.width).toBe(40); + expect(bars[1]?.value[0]?.width).toBe(40); + expect(bars[2]?.value[0]?.width).toBe(40); }); }); diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts index b6f6262394..b16a8f4572 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/get_api_scale_configs.ts @@ -70,8 +70,10 @@ export function getScaleConfigsFromSpecs( const scaleConfigsByGroupId = groupBy(seriesSpecs, getSpecDomainGroupId, true).reduce< Record >((acc, series) => { - const groupId = getSpecDomainGroupId(series[0]); - acc[groupId] = coerceYScaleTypes(series); + if (series[0]) { + const groupId = getSpecDomainGroupId(series[0]); + acc[groupId] = coerceYScaleTypes(series); + } return acc; }, {}); @@ -89,11 +91,19 @@ export function getScaleConfigsFromSpecs( if (!acc[groupId]) { acc[groupId] = { customDomain: customDomainByGroupId.get(groupId), - ...scaleConfigsByGroupId[groupId], + ...(scaleConfigsByGroupId[groupId] || { + nice: false, + type: ScaleType.Linear, + }), desiredTickCount, }; } - acc[groupId].desiredTickCount = Math.max(acc[groupId].desiredTickCount, desiredTickCount); + + acc[groupId]!.desiredTickCount = Math.max( + acc[groupId]?.desiredTickCount ?? Number.NEGATIVE_INFINITY, + desiredTickCount, + ); + return acc; }, {}); return { x, y }; diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/get_debug_state.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/get_debug_state.ts index 2f82b23448..e2588e8bea 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/get_debug_state.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/get_debug_state.ts @@ -190,7 +190,7 @@ function getAreaState(seriesNameMap: Map) { style, }, }: PerPanel): DebugStateArea => { - const [y1Path, y0Path] = lines; + const [y1Path = '', y0Path] = lines; const linePoints = points.reduce<{ y0: DebugStateValue[]; y1: DebugStateValue[]; diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/on_brush_end_caller.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/on_brush_end_caller.ts index e2f9f87d5d..610cf13099 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/on_brush_end_caller.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/on_brush_end_caller.ts @@ -162,10 +162,10 @@ function getXBrushExtent( : (value: number) => xScale.invert(value); const minPosScaled = invertValue(minPos + offset); const maxPosScaled = invertValue(maxPos + offset); - const maxDomainValue = - xScale.domain[1] + (histogramEnabled && allowBrushingLastHistogramBin ? xScale.minInterval : 0); + const [domainStart, domainEnd] = xScale.domain; + const maxDomainValue = domainEnd + (histogramEnabled && allowBrushingLastHistogramBin ? xScale.minInterval : 0); - const minValue = clamp(minPosScaled, xScale.domain[0], maxPosScaled); + const minValue = clamp(minPosScaled, domainStart, maxPosScaled); const maxValue = clamp(minPosScaled, maxPosScaled, maxDomainValue); return [minValue, maxValue]; @@ -214,8 +214,9 @@ function getYBrushExtents( const minPosScaled = yScale.invert(minPos); const maxPosScaled = yScale.invert(maxPos); - const minValue = clamp(minPosScaled, yScale.domain[0], maxPosScaled); - const maxValue = clamp(minPosScaled, maxPosScaled, yScale.domain[1]); + const [domainStart, domainEnd] = yScale.domain; + const minValue = clamp(minPosScaled, domainStart, maxPosScaled); + const maxValue = clamp(minPosScaled, maxPosScaled, domainEnd); yValues.push({ extent: [minValue, maxValue], groupId }); }); return yValues.length === 0 ? undefined : yValues; diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/on_element_over_caller.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/on_element_over_caller.ts index b3362d6dd2..d1c727c315 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/on_element_over_caller.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/on_element_over_caller.ts @@ -35,7 +35,7 @@ function isOverElement(prevProps: Props | null, nextProps: Props | null) { nextGeomValues.length > 0 && (nextGeomValues.length !== prevGeomValues.length || !nextGeomValues.every(({ value: next }, index) => { - const prev = prevGeomValues[index].value; + const prev = prevGeomValues[index]?.value; return prev && prev.x === next.x && prev.y === next.y && prev.accessor === next.accessor; })) ); diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/visible_ticks.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/visible_ticks.ts index f57457c7b8..01f97233f7 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/visible_ticks.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/visible_ticks.ts @@ -296,7 +296,7 @@ function getVisibleTickSets( const areAdjacentTimeLabelsUnique = scale.type === ScaleType.Time && !axisSpec.showDuplicatedTicks && - (areLabelsUnique || raster.ticks.every((d, i, a) => i === 0 || d.label !== a[i - 1].label)); + (areLabelsUnique || raster.ticks.every((d, i, a) => i === 0 || d.label !== a[i - 1]?.label)); const atLeastTwoTicks = uniqueLabels.size >= 2; const allTicksFit = !uniqueLabels.has(''); const compliant = diff --git a/packages/charts/src/chart_types/xy_chart/state/utils/utils.test.ts b/packages/charts/src/chart_types/xy_chart/state/utils/utils.test.ts index 38a9c6edb8..69b95f8adc 100644 --- a/packages/charts/src/chart_types/xy_chart/state/utils/utils.test.ts +++ b/packages/charts/src/chart_types/xy_chart/state/utils/utils.test.ts @@ -553,13 +553,13 @@ describe('Chart State utils', () => { const geometries = getGeometriesFromSpecs([line1, line2, line3]); - expect(geometries.geometries.lines[0].value.color).toBe('violet'); - expect(geometries.geometries.lines[0].value.style.line).toEqual({ + expect(geometries.geometries.lines[0]?.value.color).toBe('violet'); + expect(geometries.geometries.lines[0]?.value.style.line).toEqual({ visible: true, strokeWidth: 100, // the override strokeWidth opacity: 1, }); - expect(geometries.geometries.lines[0].value.style.point).toEqual({ + expect(geometries.geometries.lines[0]?.value.style.point).toEqual({ visible: true, fill: 'green', // the override strokeWidth opacity: 1, @@ -613,18 +613,18 @@ describe('Chart State utils', () => { const geometries = getGeometriesFromSpecs([area1, area2, area3]); - expect(geometries.geometries.areas[0].value.color).toBe('violet'); - expect(geometries.geometries.areas[0].value.style.area).toEqual({ + expect(geometries.geometries.areas[0]?.value.color).toBe('violet'); + expect(geometries.geometries.areas[0]?.value.style.area).toEqual({ visible: true, fill: 'area-fill-custom-color', opacity: 0.2, }); - expect(geometries.geometries.areas[0].value.style.line).toEqual({ + expect(geometries.geometries.areas[0]?.value.style.line).toEqual({ visible: true, strokeWidth: 100, opacity: 1, }); - expect(geometries.geometries.areas[0].value.style.point).toEqual({ + expect(geometries.geometries.areas[0]?.value.style.point).toEqual({ visible: false, fill: 'point-fill-custom-color', // the override strokeWidth opacity: 1, @@ -696,7 +696,7 @@ describe('Chart State utils', () => { const geometries = getGeometriesFromSpecs([line1, bar1]); - expect(geometries.geometries.bars[0].value[0].x).toBe(0); + expect(geometries.geometries.bars[0]?.value[0]?.x).toBe(0); }); }); diff --git a/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts b/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts index 1ec4dce9e0..d80244546d 100644 --- a/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts +++ b/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts @@ -205,7 +205,7 @@ export function computeSeriesGeometries( ); const barIndexByPanel = Object.keys(dataSeriesGroupedByPanel).reduce>((acc, panelKey) => { - const panelBars = dataSeriesGroupedByPanel[panelKey]; + const panelBars = dataSeriesGroupedByPanel[panelKey] ?? []; const barDataSeriesByBarIndex = groupBy(panelBars, (d) => getBarIndexKey(d, enableHistogramMode), false); acc[panelKey] = Object.keys(barDataSeriesByBarIndex); return acc; @@ -307,8 +307,6 @@ function renderGeometries( fallBackTickFormatter: TickFormatter, measureText: TextMeasure, ): Omit { - const len = dataSeries.length; - let i; const points: PointGeometry[] = []; const bars: Array> = []; const areas: Array> = []; @@ -328,20 +326,19 @@ function renderGeometries( }; const barsPadding = enableHistogramMode ? chartTheme.scales.histogramPadding : chartTheme.scales.barsPadding; - for (i = 0; i < len; i++) { - const ds = dataSeries[i]; + dataSeries.forEach((ds) => { const spec = getSpecsById(seriesSpecs, ds.specId); if (spec === undefined) { - continue; + return; } // compute the y scale const yScale = yScales.get(getSpecDomainGroupId(ds.spec)); if (!yScale) { - continue; + return; } // compute the panel unique key const barPanelKey = [ds.smVerticalAccessorValue, ds.smHorizontalAccessorValue].join('|'); - const barIndexOrder = barIndexOrderPerPanel[barPanelKey]; + const barIndexOrder = barIndexOrderPerPanel[barPanelKey] ?? []; // compute x scale const xScale = computeXScale({ xDomain, @@ -375,7 +372,7 @@ function renderGeometries( if (isBarSeriesSpec(spec)) { const shift = barIndexOrder.indexOf(getBarIndexKey(ds, enableHistogramMode)); - if (shift === -1) continue; // skip bar dataSeries if index is not available + if (shift === -1) return; // skip bar dataSeries if index is not available const barSeriesStyle = mergePartial(chartTheme.barSeriesStyle, spec.barSeriesStyle); const { yAxis } = getAxesSpecForSpecId(axesSpecs, spec.groupId, chartRotation); @@ -502,7 +499,7 @@ function renderGeometries( geometriesCounts.areasPoints += renderedAreas.areaGeometry.points.length; geometriesCounts.areas += 1; } - } + }); return { geometries: { diff --git a/packages/charts/src/chart_types/xy_chart/utils/default_series_sort_fn.ts b/packages/charts/src/chart_types/xy_chart/utils/default_series_sort_fn.ts index 560c645bf5..2c3e88fa46 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/default_series_sort_fn.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/default_series_sort_fn.ts @@ -33,7 +33,8 @@ export function defaultXYSeriesSort(a: DataSeries, b: DataSeries) { * Stacked are sorted from from top to bottom to respect the rendering order * @internal */ -export function defaultXYLegendSeriesSort(a: DataSeries, b: DataSeries) { +export function defaultXYLegendSeriesSort(a?: DataSeries, b?: DataSeries) { + if (!a || !b) return 0; if (a.groupId !== b.groupId) { return a.insertIndex - b.insertIndex; } diff --git a/packages/charts/src/chart_types/xy_chart/utils/dimensions.test.ts b/packages/charts/src/chart_types/xy_chart/utils/dimensions.test.ts index a33a6ff922..cc5c76ee65 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/dimensions.test.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/dimensions.test.ts @@ -82,7 +82,7 @@ describe('Computed chart dimensions', () => { const axisDims: AxesTicksDimensions = new Map(); const axisStyles = new Map(); const axisSpecs: AxisSpec[] = []; - const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); + const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs, null); expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width); expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height); expect(chartDimensions).toMatchSnapshot(); @@ -94,7 +94,7 @@ describe('Computed chart dimensions', () => { const axisStyles = new Map(); const axisSpecs = [axisLeftSpec]; axisDims.set('axis_1', axis1Dims); - const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); + const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs, null); expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width); expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height); expect(chartDimensions).toMatchSnapshot(); @@ -106,7 +106,7 @@ describe('Computed chart dimensions', () => { const axisStyles = new Map(); const axisSpecs = [{ ...axisLeftSpec, position: Position.Right }]; axisDims.set('axis_1', axis1Dims); - const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); + const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs, null); expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width); expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height); expect(chartDimensions).toMatchSnapshot(); @@ -123,7 +123,7 @@ describe('Computed chart dimensions', () => { }, ]; axisDims.set('axis_1', axis1Dims); - const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); + const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs, null); expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width); expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height); expect(chartDimensions).toMatchSnapshot(); @@ -140,7 +140,7 @@ describe('Computed chart dimensions', () => { }, ]; axisDims.set('axis_1', axis1Dims); - const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); + const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs, null); expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width); expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height); expect(chartDimensions).toMatchSnapshot(); @@ -155,7 +155,7 @@ describe('Computed chart dimensions', () => { }, ]; axisDims.set('foo', axis1Dims); - const chartDimensions = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); + const chartDimensions = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs, null); const expectedDims = { chartDimensions: { @@ -177,7 +177,14 @@ describe('Computed chart dimensions', () => { hide: true, position: Position.Bottom, }); - const hiddenAxisChartDimensions = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs); + const hiddenAxisChartDimensions = computeChartDimensions( + parentDim, + chartTheme, + axisDims, + axisStyles, + axisSpecs, + null, + ); expect(hiddenAxisChartDimensions).toEqual(expectedDims); }); diff --git a/packages/charts/src/chart_types/xy_chart/utils/dimensions.ts b/packages/charts/src/chart_types/xy_chart/utils/dimensions.ts index 5be8789aae..f545d2b094 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/dimensions.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/dimensions.ts @@ -38,7 +38,7 @@ export interface ChartDimensions { axisTickDimensions: AxesTicksDimensions, axesStyles: Map, axisSpecs: AxisSpec[], - smSpec?: SmallMultiplesSpec, + smSpec: SmallMultiplesSpec | null, ): ChartDimensions { const axesDimensions = getAxesDimensions(theme, axisTickDimensions, axesStyles, axisSpecs, smSpec); const chartWidth = parentDimensions.width - axesDimensions.left - axesDimensions.right; diff --git a/packages/charts/src/chart_types/xy_chart/utils/diverging_offsets.ts b/packages/charts/src/chart_types/xy_chart/utils/diverging_offsets.ts index b5154b36b5..43b3043042 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/diverging_offsets.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/diverging_offsets.ts @@ -40,20 +40,20 @@ export type XValueSeriesDatum = [XValue, SeriesValueMap]; function getWiggleOffsets(series: Series, order: number[]): number[] { const offsets = []; let y, j; - for (y = 0, j = 1; j < series[order[0]].length; ++j) { + for (y = 0, j = 1; j < (series[order[0] ?? 0]?.length ?? 0); ++j) { let i, s1, s2; for (i = 0, s1 = 0, s2 = 0; i < series.length; ++i) { // @ts-ignore - d3-shape type here is inaccurate const si = series[order[i]] as SeriesPoint[]; - const sij0 = si[j][1] || 0; - const sij1 = si[j - 1][1] || 0; + const sij0 = si[j]?.[1] || 0; + const sij1 = si[j - 1]?.[1] || 0; let s3 = (sij0 - sij1) / 2; for (let k = 0; k < i; ++k) { // @ts-ignore - d3-shape type here is inaccurate const sk = series[order[k]] as SeriesPoint[]; - const skj0 = sk[j][1] || 0; - const skj1 = sk[j - 1][1] || 0; + const skj0 = sk[j]?.[1] || 0; + const skj1 = sk[j - 1]?.[1] || 0; s3 += skj0 - skj1; } s1 += sij0; @@ -72,7 +72,7 @@ const divergingOffset = (isSilhouette = false) => { return function (series: Series, order: number[]): void { const n = series.length; if (!(n > 0)) return; - for (let i, j = 0, sumYn, sumYp, yp, yn = 0, s0 = series[order[0]], m = s0.length; j < m; ++j) { + for (let i, j = 0, sumYn, sumYp, yp, yn = 0, s0 = series[order[0] ?? 0], m = s0?.length ?? 0; j < m; ++j) { // sum negative values per x before to maintain original sort for negative values for (yn = 0, sumYn = 0, sumYp = 0, i = 0; i < n; ++i) { // @ts-ignore - d3-shape type here is inaccurate @@ -123,8 +123,8 @@ export const divergingSilhouette = divergingOffset(true); */ export function divergingWiggle(series: Series, order: number[]): void { const n = series.length; - const s0 = series[order[0]]; - const m = s0.length; + const s0 = series[order[0] ?? 0]; + const m = s0?.length ?? 0; if (!(n > 0) || !(m > 0)) return diverging(series, order); const offsets = getWiggleOffsets(series, order); @@ -139,7 +139,7 @@ export function divergingWiggle(series: Series(series: Series(series: Series, order: number[]): void { const n = series.length; if (!(n > 0)) return; - for (let i, j = 0, sumYn, sumYp; j < series[0].length; ++j) { + for (let i, j = 0, sumYn, sumYp; j < (series[0]?.length ?? 0); ++j) { for (sumYn = sumYp = i = 0; i < n; ++i) { // @ts-ignore - d3-shape type here is inaccurate const d = series[order[i]][j] as SeriesPoint; diff --git a/packages/charts/src/chart_types/xy_chart/utils/fill_series.ts b/packages/charts/src/chart_types/xy_chart/utils/fill_series.ts index 06c496a939..99ae0e3a53 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/fill_series.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/fill_series.ts @@ -31,15 +31,15 @@ export function fillSeries( } const filledData: typeof data = []; const missingValues = new Set(xValues); - for (let i = 0; i < data.length; i++) { - const { x } = data[i]; - filledData.push(data[i]); - missingValues.delete(x); - } + + data.forEach((datum) => { + filledData.push(datum); + missingValues.delete(datum.x); + }); const missingValuesArray = [...missingValues.values()]; - for (let i = 0; i < missingValuesArray.length; i++) { - const missingValue = missingValuesArray[i]; + + missingValuesArray.forEach((missingValue) => { const index = sortedXValues.indexOf(missingValue); filledData.splice(index, 0, { @@ -54,7 +54,8 @@ export function fillSeries( x: missingValue, }, }); - } + }); + return { ...series, data: filledData, diff --git a/packages/charts/src/chart_types/xy_chart/utils/fit_function.ts b/packages/charts/src/chart_types/xy_chart/utils/fit_function.ts index 2d198d6f91..d5a35e66d3 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/fit_function.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/fit_function.ts @@ -12,6 +12,7 @@ import { DataSeriesDatum } from './series'; import { Fit, FitConfig } from './specs'; import { datumXSortPredicate } from './stacked_series_utils'; import { ScaleType } from '../../../scales/constants'; +import { isNil } from '../../../utils/common'; /** * Fit type that requires previous and/or next `non-nullable` values @@ -215,9 +216,8 @@ export const fitFunction = ( let previousNonNullDatum: WithIndex | null = null; let nextNonNullDatum: WithIndex | null = null; - for (let i = 0; i < sortedData.length; i++) { + sortedData.forEach((currentValue, i) => { let j = i; - const currentValue = sortedData[i]; if ( currentValue.y1 === null && @@ -231,6 +231,7 @@ export const fitFunction = ( // Forward lookahead to get next non-null value for (j = i + 1; j < sortedData.length; j++) { const nextValue = sortedData[j]; + if (isNil(nextValue)) continue; if (nextValue.y1 !== null && nextValue.x !== null) { nextNonNullDatum = { @@ -259,7 +260,7 @@ export const fitFunction = ( if (nextNonNullDatum !== null && nextNonNullDatum.x <= currentValue.x) { nextNonNullDatum = null; } - } + }); return newData; }; diff --git a/packages/charts/src/chart_types/xy_chart/utils/get_linear_ticks.ts b/packages/charts/src/chart_types/xy_chart/utils/get_linear_ticks.ts index 18c4bf4373..aa023154ee 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/get_linear_ticks.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/get_linear_ticks.ts @@ -23,6 +23,7 @@ import { ScaleContinuousNumeric } from 'd3-scale'; +import { isNil } from '../../../utils/common'; import { PrimitiveValue } from '../../partition_chart/layout/utils/group_by_rollup'; const e10 = Math.sqrt(50); @@ -94,6 +95,10 @@ export function getNiceLinearTicks( let step; let maxIter = 10; + if (isNil(stop) || isNil(start)) { + return scale; + } + if (stop < start) { step = start; start = stop; diff --git a/packages/charts/src/chart_types/xy_chart/utils/grid_lines.ts b/packages/charts/src/chart_types/xy_chart/utils/grid_lines.ts index 020bc9eb5b..7cb7c6eae9 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/grid_lines.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/grid_lines.ts @@ -111,9 +111,9 @@ function getGridLinesForAxis( const strokeColor = overrideOpacity(colorToRgba(gridLineStyles.stroke), (strokeColorOpacity) => gridLineStyles.opacity !== undefined ? strokeColorOpacity * gridLineStyles.opacity : strokeColorOpacity, ); - const layered = typeof visibleTicksOfLayer[0].layer === 'number'; + const layered = typeof visibleTicksOfLayer[0]?.layer === 'number'; - const multilayerLuma = themeAxisStyle.gridLine.lumaSteps[detailedLayer]; + const multilayerLuma = themeAxisStyle.gridLine.lumaSteps[detailedLayer] ?? NaN; const stroke: Stroke = { color: layered ? [multilayerLuma, multilayerLuma, multilayerLuma, 1] : strokeColor, width: layered ? HIERARCHICAL_GRID_WIDTH : gridLineStyles.strokeWidth, diff --git a/packages/charts/src/chart_types/xy_chart/utils/group_data_series.ts b/packages/charts/src/chart_types/xy_chart/utils/group_data_series.ts index c8d1cbe71c..209ba034fa 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/group_data_series.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/group_data_series.ts @@ -26,7 +26,7 @@ export function groupBy(data: T[], keysOrKeyFn: GroupKeysOrKeyFn, asArray: if (!acc[key]) { acc[key] = []; } - acc[key].push(curr); + acc[key]!.push(curr); return acc; }, {}); return asArray ? Object.values(grouped) : grouped; diff --git a/packages/charts/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts b/packages/charts/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts index 0bed9f6181..b5dc22495c 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/nonstacked_series_utils.test.ts @@ -88,7 +88,7 @@ describe('Non-Stacked Series Utils', () => { MockStore.addSpecs(STANDARD_DATA_SET, store); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(formattedDataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0]?.data[0]).toMatchObject({ initialY0: null, initialY1: 10, x: 0, @@ -96,7 +96,7 @@ describe('Non-Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: null, initialY1: 20, x: 0, @@ -104,7 +104,7 @@ describe('Non-Stacked Series Utils', () => { y1: 20, mark: null, }); - expect(formattedDataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2]?.data[0]).toMatchObject({ initialY0: null, initialY1: 30, x: 0, @@ -118,7 +118,7 @@ describe('Non-Stacked Series Utils', () => { MockStore.addSpecs(WITH_NULL_DATASET, store); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, @@ -132,7 +132,7 @@ describe('Non-Stacked Series Utils', () => { MockStore.addSpecs(STANDARD_DATA_SET_WY0, store); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(formattedDataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0]?.data[0]).toMatchObject({ initialY0: 2, initialY1: 10, x: 0, @@ -140,7 +140,7 @@ describe('Non-Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: 4, initialY1: 20, x: 0, @@ -148,7 +148,7 @@ describe('Non-Stacked Series Utils', () => { y1: 20, mark: null, }); - expect(formattedDataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2]?.data[0]).toMatchObject({ initialY0: 6, initialY1: 30, x: 0, @@ -162,7 +162,7 @@ describe('Non-Stacked Series Utils', () => { MockStore.addSpecs(WITH_NULL_DATASET_WY0, store); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(formattedDataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0]?.data[0]).toMatchObject({ initialY0: 2, initialY1: 10, x: 0, @@ -170,7 +170,7 @@ describe('Non-Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, @@ -178,7 +178,7 @@ describe('Non-Stacked Series Utils', () => { y0: null, mark: null, }); - expect(formattedDataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2]?.data[0]).toMatchObject({ initialY0: 6, initialY1: 30, x: 0, @@ -195,10 +195,10 @@ describe('Non-Stacked Series Utils', () => { expect(formattedDataSeries.length).toBe(2); // this because linear non stacked area/lines doesn't fill up the dataset // with missing x data points - expect(formattedDataSeries[0].data.length).toBe(3); - expect(formattedDataSeries[1].data.length).toBe(2); + expect(formattedDataSeries[0]?.data.length).toBe(3); + expect(formattedDataSeries[1]?.data.length).toBe(2); - expect(formattedDataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0]?.data[0]).toMatchObject({ initialY0: null, initialY1: 1, x: 1, @@ -206,7 +206,7 @@ describe('Non-Stacked Series Utils', () => { y1: 1, mark: null, }); - expect(formattedDataSeries[0].data[1]).toMatchObject({ + expect(formattedDataSeries[0]?.data[1]).toMatchObject({ initialY0: null, initialY1: 2, x: 2, @@ -214,7 +214,7 @@ describe('Non-Stacked Series Utils', () => { y1: 2, mark: null, }); - expect(formattedDataSeries[0].data[2]).toMatchObject({ + expect(formattedDataSeries[0]?.data[2]).toMatchObject({ initialY0: null, initialY1: 4, x: 4, @@ -222,7 +222,7 @@ describe('Non-Stacked Series Utils', () => { y1: 4, mark: null, }); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: null, initialY1: 21, x: 1, @@ -230,7 +230,7 @@ describe('Non-Stacked Series Utils', () => { y1: 21, mark: null, }); - expect(formattedDataSeries[1].data[1]).toMatchObject({ + expect(formattedDataSeries[1]?.data[1]).toMatchObject({ initialY0: null, initialY1: 23, x: 3, @@ -271,7 +271,7 @@ describe('Non-Stacked Series Utils', () => { it('return fitted dataSeries', () => { const actual = testModule.applyFitFunctionToDataSeries(dataSeries, seriesSpecs, ScaleType.Linear); - expect(actual[0].data).toBe(dataSeriesData); + expect(actual[0]?.data).toBe(dataSeriesData); }); }); @@ -294,7 +294,7 @@ describe('Non-Stacked Series Utils', () => { it('return fitted dataSeries', () => { const actual = testModule.applyFitFunctionToDataSeries(dataSeries, seriesSpecs, ScaleType.Linear); - expect(actual[0].data).toBe(dataSeriesData); + expect(actual[0]?.data).toBe(dataSeriesData); }); }); }); diff --git a/packages/charts/src/chart_types/xy_chart/utils/scales.ts b/packages/charts/src/chart_types/xy_chart/utils/scales.ts index ba8e448d27..69d85fa72a 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/scales.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/scales.ts @@ -56,7 +56,7 @@ export function computeXScale(options: XScaleOptions): ScaleBand | ScaleContinuo if (isBandScale) { const [domainMin, domainMax] = domain as ContinuousDomain; const isSingleValueHistogram = !!enableHistogramMode && domainMax - domainMin === 0; - const adjustedDomain = [domainMin, isSingleValueHistogram ? domainMin + minInterval : domainMax]; + const adjustedDomain: [number, number] = [domainMin, isSingleValueHistogram ? domainMin + minInterval : domainMax]; const intervalCount = (adjustedDomain[1] - adjustedDomain[0]) / minInterval; const intervalCountOffset = isSingleValueHistogram ? 0 : 1; const bandwidth = rangeDiff / (intervalCount + intervalCountOffset); @@ -81,7 +81,7 @@ export function computeXScale(options: XScaleOptions): ScaleBand | ScaleContinuo ); } else { return new ScaleContinuous( - { type, domain: domain as number[], range, nice }, + { type, domain: domain as [number, number], range, nice }, { bandwidth: 0, minInterval, diff --git a/packages/charts/src/chart_types/xy_chart/utils/series.test.ts b/packages/charts/src/chart_types/xy_chart/utils/series.test.ts index a7031d9f8e..133ae22cf5 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/series.test.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/series.test.ts @@ -584,7 +584,7 @@ describe('Series', () => { { specId: id, xAccessor: 'x', - yAccessor: yAccessors[0], + yAccessor: yAccessors[0]!, splitAccessors: new Map(), seriesKeys: [], key: 'groupId{group}spec{splitSpec}yAccessor{y1}splitAccessors{}', @@ -630,7 +630,7 @@ describe('Series', () => { it('should get series label from spec', () => { const [identifier] = indentifiers; - const actual = getSeriesName(identifier, false, false, spec); + const actual = getSeriesName(identifier!, false, false, spec); expect(actual).toBe('a - y'); }); @@ -640,7 +640,7 @@ describe('Series', () => { yAccessors: ['y'], }; const [identifier] = MockSeriesIdentifier.fromSpecs([spec]); - const actual = getSeriesName(identifier, false, false, specSingleY); + const actual = getSeriesName(identifier!, false, false, specSingleY); expect(actual).toBe('a'); }); @@ -649,10 +649,10 @@ describe('Series', () => { it('should replace full label', () => { const label = 'My custom new label'; const [identifier] = indentifiers; - const actual = getSeriesName(identifier, false, false, { + const actual = getSeriesName(identifier!, false, false, { ...spec, name: ({ yAccessor, splitAccessors }) => - yAccessor === identifier.yAccessor && splitAccessors.get('g') === 'a' ? label : null, + yAccessor === identifier?.yAccessor && splitAccessors.get('g') === 'a' ? label : null, }); expect(actual).toBe(label); @@ -665,14 +665,14 @@ describe('Series', () => { name: ({ seriesKeys }) => seriesKeys.join(' - '), }; const [identifier] = MockSeriesIdentifier.fromSpecs([spec]); - const actual = getSeriesName(identifier, false, false, specSingleY); + const actual = getSeriesName(identifier!, false, false, specSingleY); expect(actual).toBe('a - y'); }); it('should replace yAccessor sub label with map', () => { const [identifier] = indentifiers; - const actual = getSeriesName(identifier, false, false, { + const actual = getSeriesName(identifier!, false, false, { ...spec, name: { names: [ @@ -692,7 +692,7 @@ describe('Series', () => { it('should join with custom delimiter', () => { const [identifier] = indentifiers; - const actual = getSeriesName(identifier, false, false, { + const actual = getSeriesName(identifier!, false, false, { ...spec, name: { names: [ @@ -712,7 +712,7 @@ describe('Series', () => { it('should replace splitAccessor sub label with map', () => { const [identifier] = indentifiers; - const actual = getSeriesName(identifier, false, false, { + const actual = getSeriesName(identifier!, false, false, { ...spec, name: { names: [ @@ -732,7 +732,7 @@ describe('Series', () => { it('should mind order of names', () => { const [identifier] = indentifiers; - const actual = getSeriesName(identifier, false, false, { + const actual = getSeriesName(identifier!, false, false, { ...spec, name: { names: [ @@ -753,7 +753,7 @@ describe('Series', () => { it('should mind sortIndex of names', () => { const [identifier] = indentifiers; - const actual = getSeriesName(identifier, false, false, { + const actual = getSeriesName(identifier!, false, false, { ...spec, name: { names: [ @@ -776,7 +776,7 @@ describe('Series', () => { it('should allow undefined sortIndex', () => { const [identifier] = indentifiers; - const actual = getSeriesName(identifier, false, false, { + const actual = getSeriesName(identifier!, false, false, { ...spec, name: { names: [ @@ -798,7 +798,7 @@ describe('Series', () => { it('should ignore missing names', () => { const [identifier] = indentifiers; - const actual = getSeriesName(identifier, false, false, { + const actual = getSeriesName(identifier!, false, false, { ...spec, name: { names: [ @@ -824,7 +824,7 @@ describe('Series', () => { it('should return fallback label if empty string', () => { const [identifier] = indentifiers; - const actual = getSeriesName(identifier, false, false, { + const actual = getSeriesName(identifier!, false, false, { ...spec, name: { names: [], @@ -938,7 +938,7 @@ describe('Series', () => { test('Can use functional y0Accessor', () => { const splitSeries = splitSeriesDataByAccessors( MockSeriesSpec.bar({ - data: KIBANA_METRICS.metrics.kibana_os_load[0].data.map((d: any) => ({ + data: KIBANA_METRICS.metrics.kibana_os_load.v1.data.map((d: any) => ({ x: d[0], max: d[1] + 4 + 4 * getRandomNumber(), min: d[1] - 4 - 4 * getRandomNumber(), diff --git a/packages/charts/src/chart_types/xy_chart/utils/series.ts b/packages/charts/src/chart_types/xy_chart/utils/series.ts index ef038de74a..dca8ebb277 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/series.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/series.ts @@ -10,7 +10,7 @@ import { applyFitFunctionToDataSeries } from './fit_function_utils'; import { groupBy } from './group_data_series'; import { BaseDatum, BasicSeriesSpec, SeriesNameConfigOptions, SeriesSpecs, SeriesType, StackMode } from './specs'; import { datumXSortPredicate, formatStackedDataSeriesValues } from './stacked_series_utils'; -import { Color } from '../../../common/colors'; +import { Color, Colors } from '../../../common/colors'; import { SmallMultiplesDatum, SmallMultiplesGroupBy } from '../../../common/panel_utils'; import { SeriesIdentifier, SeriesKey } from '../../../common/series_id'; import { ScaleType } from '../../../scales/constants'; @@ -336,6 +336,7 @@ export function getFormattedDataSeries( ); const fittedAndStackedDataSeries = stackedGroups.reduce((acc, dataSeries) => { + if (!dataSeries[0]) return acc; const [{ stackMode, seriesType }] = dataSeries; const formatted = formatStackedDataSeriesValues(dataSeries, xValues, seriesType, stackMode); return [...acc, ...formatted]; @@ -586,17 +587,19 @@ export function getSeriesColors( return [ds.specId, ds.groupId, ds.yAccessor, ...ds.splitAccessors.values()].join('__'); }, true, - ).forEach((ds) => { + ).forEach(([ds]) => { + if (!ds) return; const seriesKey = getSeriesKey( { - specId: ds[0].specId, - yAccessor: ds[0].yAccessor, - splitAccessors: ds[0].splitAccessors, + specId: ds.specId, + yAccessor: ds.yAccessor, + splitAccessors: ds.splitAccessors, }, - ds[0].groupId, + ds.groupId, ); const colorOverride = getHighestOverride(seriesKey, customColors, overrides); - const color = colorOverride || chartColors.vizColors[counter % chartColors.vizColors.length]; + const color = + colorOverride || chartColors.vizColors[counter % chartColors.vizColors.length] || Colors.White.keyword; seriesColorMap.set(seriesKey, color); counter++; diff --git a/packages/charts/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts b/packages/charts/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts index f3624b68c8..52755b4d7b 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts @@ -60,20 +60,20 @@ describe('Stacked Series Utils', () => { ); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - const [data0] = formattedDataSeries[0].data; - expect(data0.initialY1).toBe(10); - expect(data0.y0).toBe(0); - expect(data0.y1).toBe(0.1); - - const [data1] = formattedDataSeries[1].data; - expect(data1.initialY1).toBe(20); - expect(data1.y0).toBe(0.1); - expect(data1.y1).toBeCloseTo(0.3); - - const [data2] = formattedDataSeries[2].data; - expect(data2.initialY1).toBe(70); - expect(data2.y0).toBeCloseTo(0.3); - expect(data2.y1).toBe(1); + const [data0] = formattedDataSeries[0]!.data; + expect(data0?.initialY1).toBe(10); + expect(data0?.y0).toBe(0); + expect(data0?.y1).toBe(0.1); + + const [data1] = formattedDataSeries[1]!.data; + expect(data1?.initialY1).toBe(20); + expect(data1?.y0).toBe(0.1); + expect(data1?.y1).toBeCloseTo(0.3); + + const [data2] = formattedDataSeries[2]!.data; + expect(data2?.initialY1).toBe(70); + expect(data2?.y0).toBeCloseTo(0.3); + expect(data2?.y1).toBe(1); }); test('format data with nulls', () => { const store = MockStore.default(); @@ -91,12 +91,12 @@ describe('Stacked Series Utils', () => { ); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - const [data0] = formattedDataSeries[0].data; - expect(data0.initialY1).toBe(10); - expect(data0.y0).toBe(0); - expect(data0.y1).toBe(0.25); + const [data0] = formattedDataSeries[0]!.data; + expect(data0?.initialY1).toBe(10); + expect(data0?.y0).toBe(0); + expect(data0?.y1).toBe(0.25); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]!.data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, @@ -105,10 +105,10 @@ describe('Stacked Series Utils', () => { mark: null, }); - const [data2] = formattedDataSeries[2].data; - expect(data2.initialY1).toBe(30); - expect(data2.y0).toBe(0.25); - expect(data2.y1).toBe(1); + const [data2] = formattedDataSeries[2]!.data; + expect(data2?.initialY1).toBe(30); + expect(data2?.y0).toBe(0.25); + expect(data2?.y1).toBe(1); }); test('format data without nulls with y0 values', () => { const store = MockStore.default(); @@ -126,23 +126,23 @@ describe('Stacked Series Utils', () => { ); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - const [data0] = formattedDataSeries[0].data; - expect(data0.initialY0).toBeNull(); - expect(data0.initialY1).toBe(10); - expect(data0.y0).toBe(0); - expect(data0.y1).toBe(0.1); - - const [data1] = formattedDataSeries[1].data; - expect(data1.initialY0).toBeNull(); - expect(data1.initialY1).toBe(20); - expect(data1.y0).toBe(0.1); - expect(data1.y1).toBeCloseTo(0.3, 5); - - const [data2] = formattedDataSeries[2].data; - expect(data2.initialY0).toBeNull(); - expect(data2.initialY1).toBe(70); - expect(data2.y0).toBeCloseTo(0.3); - expect(data2.y1).toBe(1); + const [data0] = formattedDataSeries[0]!.data; + expect(data0?.initialY0).toBeNull(); + expect(data0?.initialY1).toBe(10); + expect(data0?.y0).toBe(0); + expect(data0?.y1).toBe(0.1); + + const [data1] = formattedDataSeries[1]!.data; + expect(data1?.initialY0).toBeNull(); + expect(data1?.initialY1).toBe(20); + expect(data1?.y0).toBe(0.1); + expect(data1?.y1).toBeCloseTo(0.3, 5); + + const [data2] = formattedDataSeries[2]!.data; + expect(data2?.initialY0).toBeNull(); + expect(data2?.initialY1).toBe(70); + expect(data2?.y0).toBeCloseTo(0.3); + expect(data2?.y1).toBe(1); }); test('format data with nulls - missing points', () => { const store = MockStore.default(); @@ -160,23 +160,23 @@ describe('Stacked Series Utils', () => { ); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - const [data0] = formattedDataSeries[0].data; - expect(data0.initialY0).toBeNull(); - expect(data0.initialY1).toBe(10); - expect(data0.y0).toBe(0); - expect(data0.y1).toBe(0.1); - - const [data1] = formattedDataSeries[1].data; - expect(data1.initialY0).toBe(null); - expect(data1.initialY1).toBe(null); - expect(data1.y0).toBe(0.1); - expect(data1.y1).toBe(0.1); - - const [data2] = formattedDataSeries[2].data; - expect(data2.initialY0).toBeNull(); - expect(data2.initialY1).toBe(90); - expect(data2.y0).toBe(0.1); - expect(data2.y1).toBe(1); + const [data0] = formattedDataSeries[0]!.data; + expect(data0?.initialY0).toBeNull(); + expect(data0?.initialY1).toBe(10); + expect(data0?.y0).toBe(0); + expect(data0?.y1).toBe(0.1); + + const [data1] = formattedDataSeries[1]!.data; + expect(data1?.initialY0).toBe(null); + expect(data1?.initialY1).toBe(null); + expect(data1?.y0).toBe(0.1); + expect(data1?.y1).toBe(0.1); + + const [data2] = formattedDataSeries[2]!.data; + expect(data2?.initialY0).toBeNull(); + expect(data2?.initialY1).toBe(90); + expect(data2?.y0).toBe(0.1); + expect(data2?.y1).toBe(1); }); test('format data without nulls on second series', () => { const store = MockStore.default(); @@ -197,9 +197,9 @@ describe('Stacked Series Utils', () => { const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); expect(formattedDataSeries).toHaveLength(2); - expect(formattedDataSeries[0].data).toHaveLength(4); - expect(formattedDataSeries[1].data).toHaveLength(4); - expect(formattedDataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0]!.data).toHaveLength(4); + expect(formattedDataSeries[1]!.data).toHaveLength(4); + expect(formattedDataSeries[0]!.data[0]).toMatchObject({ initialY0: null, initialY1: 10, x: 1, @@ -207,7 +207,7 @@ describe('Stacked Series Utils', () => { y1: 0.1, mark: null, }); - expect(formattedDataSeries[0].data[1]).toMatchObject({ + expect(formattedDataSeries[0]!.data[1]).toMatchObject({ initialY0: null, initialY1: 20, x: 2, @@ -215,7 +215,7 @@ describe('Stacked Series Utils', () => { y1: 1, mark: null, }); - expect(formattedDataSeries[0].data[3]).toMatchObject({ + expect(formattedDataSeries[0]!.data[3]).toMatchObject({ initialY0: null, initialY1: 40, x: 4, @@ -223,7 +223,7 @@ describe('Stacked Series Utils', () => { y1: 1, mark: null, }); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]!.data[0]).toMatchObject({ initialY0: null, initialY1: 90, x: 1, @@ -231,7 +231,7 @@ describe('Stacked Series Utils', () => { y1: 1, mark: null, }); - expect(formattedDataSeries[1].data[1]).toMatchObject({ + expect(formattedDataSeries[1]!.data[1]).toMatchObject({ initialY0: null, initialY1: null, x: 2, @@ -242,7 +242,7 @@ describe('Stacked Series Utils', () => { x: 2, }, }); - expect(formattedDataSeries[1].data[2]).toMatchObject({ + expect(formattedDataSeries[1]!.data[2]).toMatchObject({ initialY0: null, initialY1: 30, x: 3, diff --git a/packages/charts/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts b/packages/charts/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts index 64b96a614a..fb1a9d1466 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts @@ -96,10 +96,10 @@ describe('Stacked Series Utils', () => { // stacked series are reverse ordered const values = [ - formattedDataSeries[0].data[0].y0, - formattedDataSeries[0].data[0].y1, - formattedDataSeries[1].data[0].y1, - formattedDataSeries[2].data[0].y1, + formattedDataSeries[0]?.data[0]?.y0, + formattedDataSeries[0]?.data[0]?.y1, + formattedDataSeries[1]?.data[0]?.y1, + formattedDataSeries[2]?.data[0]?.y1, ]; expect(values).toEqual([0, 10, 30, 60]); }); @@ -116,10 +116,10 @@ describe('Stacked Series Utils', () => { const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); const values = [ - formattedDataSeries[0].data[0].y0, - formattedDataSeries[0].data[0].y1, - formattedDataSeries[1].data[0].y1, - formattedDataSeries[2].data[0].y1, + formattedDataSeries[0]?.data[0]?.y0, + formattedDataSeries[0]?.data[0]?.y1, + formattedDataSeries[1]?.data[0]?.y1, + formattedDataSeries[2]?.data[0]?.y1, ]; expect(values).toEqual([0, 0.16666666666666666, 0.5, 1]); }); @@ -129,10 +129,10 @@ describe('Stacked Series Utils', () => { const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); const values = [ - formattedDataSeries[0].data[0].y0, - formattedDataSeries[0].data[0].y1, - formattedDataSeries[1].data[0].y1, - formattedDataSeries[2].data[0].y1, + formattedDataSeries[0]?.data[0]?.y0, + formattedDataSeries[0]?.data[0]?.y1, + formattedDataSeries[1]?.data[0]?.y1, + formattedDataSeries[2]?.data[0]?.y1, ]; expect(values).toEqual([0, 10, 10, 40]); }); @@ -149,10 +149,10 @@ describe('Stacked Series Utils', () => { const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); const values = [ - formattedDataSeries[0].data[0].y0, - formattedDataSeries[0].data[0].y1, - formattedDataSeries[1].data[0].y1, - formattedDataSeries[2].data[0].y1, + formattedDataSeries[0]?.data[0]?.y0, + formattedDataSeries[0]?.data[0]?.y1, + formattedDataSeries[1]?.data[0]?.y1, + formattedDataSeries[2]?.data[0]?.y1, ]; expect(values).toEqual([0, 0.25, 0.25, 1]); }); @@ -163,7 +163,7 @@ describe('Stacked Series Utils', () => { MockStore.addSpecs(STANDARD_DATA_SET, store); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(formattedDataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0]?.data[0]).toMatchObject({ initialY0: null, initialY1: 10, x: 0, @@ -171,7 +171,7 @@ describe('Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: null, initialY1: 20, x: 0, @@ -179,7 +179,7 @@ describe('Stacked Series Utils', () => { y1: 30, mark: null, }); - expect(formattedDataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2]?.data[0]).toMatchObject({ initialY0: null, initialY1: 30, x: 0, @@ -193,7 +193,7 @@ describe('Stacked Series Utils', () => { MockStore.addSpecs(WITH_NULL_DATASET, store); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, @@ -207,7 +207,7 @@ describe('Stacked Series Utils', () => { MockStore.addSpecs(STANDARD_DATA_SET_WY0, store); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(formattedDataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0]?.data[0]).toMatchObject({ initialY0: 2, initialY1: 10, x: 0, @@ -215,7 +215,7 @@ describe('Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: 4, initialY1: 20, x: 0, @@ -223,7 +223,7 @@ describe('Stacked Series Utils', () => { y1: 30, mark: null, }); - expect(formattedDataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2]?.data[0]).toMatchObject({ initialY0: 6, initialY1: 30, x: 0, @@ -237,7 +237,7 @@ describe('Stacked Series Utils', () => { MockStore.addSpecs(WITH_NULL_DATASET_WY0, store); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(formattedDataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0]?.data[0]).toMatchObject({ initialY0: 2, initialY1: 10, x: 0, @@ -245,7 +245,7 @@ describe('Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: null, initialY1: null, x: 0, @@ -253,7 +253,7 @@ describe('Stacked Series Utils', () => { y1: 10, mark: null, }); - expect(formattedDataSeries[2].data[0]).toMatchObject({ + expect(formattedDataSeries[2]?.data[0]).toMatchObject({ initialY0: 6, initialY1: 30, x: 0, @@ -268,10 +268,10 @@ describe('Stacked Series Utils', () => { const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); expect(formattedDataSeries).toHaveLength(2); - expect(formattedDataSeries[0].data).toHaveLength(4); - expect(formattedDataSeries[1].data).toHaveLength(4); + expect(formattedDataSeries[0]?.data).toHaveLength(4); + expect(formattedDataSeries[1]?.data).toHaveLength(4); - expect(formattedDataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0]?.data[0]).toMatchObject({ initialY0: null, initialY1: 1, x: 1, @@ -279,7 +279,7 @@ describe('Stacked Series Utils', () => { y1: 1, mark: null, }); - expect(formattedDataSeries[0].data[1]).toMatchObject({ + expect(formattedDataSeries[0]?.data[1]).toMatchObject({ initialY0: null, initialY1: 2, x: 2, @@ -287,7 +287,7 @@ describe('Stacked Series Utils', () => { y1: 2, mark: null, }); - expect(formattedDataSeries[0].data[3]).toMatchObject({ + expect(formattedDataSeries[0]?.data[3]).toMatchObject({ initialY0: null, initialY1: 4, x: 4, @@ -295,7 +295,7 @@ describe('Stacked Series Utils', () => { y1: 4, mark: null, }); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: null, initialY1: 21, x: 1, @@ -303,7 +303,7 @@ describe('Stacked Series Utils', () => { y1: 22, mark: null, }); - expect(formattedDataSeries[1].data[2]).toMatchObject({ + expect(formattedDataSeries[1]?.data[2]).toMatchObject({ initialY0: null, initialY1: 23, x: 3, @@ -330,7 +330,7 @@ describe('Stacked Series Utils', () => { ); const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState()); - expect(formattedDataSeries[1].data[0]).toMatchObject({ + expect(formattedDataSeries[1]?.data[0]).toMatchObject({ initialY0: null, initialY1: 0, x: 1, @@ -338,7 +338,7 @@ describe('Stacked Series Utils', () => { y1: 0, mark: null, }); - expect(formattedDataSeries[0].data[0]).toMatchObject({ + expect(formattedDataSeries[0]?.data[0]).toMatchObject({ initialY0: null, initialY1: 0, x: 1, diff --git a/packages/charts/src/common/color_calcs.ts b/packages/charts/src/common/color_calcs.ts index 5797afc5a0..982504e3bb 100644 --- a/packages/charts/src/common/color_calcs.ts +++ b/packages/charts/src/common/color_calcs.ts @@ -13,7 +13,7 @@ import { getWCAG2ContrastRatio } from './wcag2_color_contrast'; /** @internal */ export function hueInterpolator(colors: RgbTuple[]) { - return (d: number) => RGBATupleToString(colors[Math.round(d * 255)]); + return (d: number) => RGBATupleToString(colors[Math.round(d * 255)] ?? Colors.White.rgba); } /** @internal */ diff --git a/packages/charts/src/common/event_handler_selectors.ts b/packages/charts/src/common/event_handler_selectors.ts index 9dc16b893f..e8e1699409 100644 --- a/packages/charts/src/common/event_handler_selectors.ts +++ b/packages/charts/src/common/event_handler_selectors.ts @@ -69,7 +69,7 @@ function isNewPickedShapes(prevPickedShapes: LayerValue[][], nextPickedShapes: L } return !nextPickedShapes.every((nextPickedShapeValues, index) => { const prevPickedShapeValues = prevPickedShapes[index]; - if (prevPickedShapeValues === null) { + if (!prevPickedShapeValues) { return false; } if (prevPickedShapeValues.length !== nextPickedShapeValues.length) { diff --git a/packages/charts/src/common/kingly.ts b/packages/charts/src/common/kingly.ts index f440eb9f39..672c69a9ea 100644 --- a/packages/charts/src/common/kingly.ts +++ b/packages/charts/src/common/kingly.ts @@ -301,11 +301,13 @@ const getUniforms = (gl: WebGL2RenderingContext, program: WebGLProgram): Uniform Logger.warn(`kinGLy exception: uniform location ${location} (name: ${name}, type: ${type}) not found`); // just appeasing the TS linter return []; } - const setValue = location ? uniformSetterLookup[type](gl, location) : () => {}; - if (GL_DEBUG && !setValue) { + const setValue = location ? uniformSetterLookup[type]?.(gl, location) : () => {}; + + if (!setValue) { Logger.warn(`kinGLy exception: no setValue for uniform GL[${type}] (name: ${name}) implemented yet`); return []; } + return [[name, setValue]]; }), ); @@ -407,8 +409,8 @@ export const createTexture = ( 'kinGLy exception: WebGL2 is guaranteed to support at least 32 textures but not necessarily more than that', ); - const srcFormat = textureSrcFormatLookup[internalFormat]; - const type = textureTypeLookup[internalFormat]; + const srcFormat = textureSrcFormatLookup[internalFormat] ?? NaN; + const type = textureTypeLookup[internalFormat] ?? NaN; const texture = gl.createTexture(); const setTextureContents = () => { @@ -510,12 +512,12 @@ export const getAttributes = ( const { name, type } = activeAttribInfo; if (name.startsWith('gl_')) return [name, () => {}]; // only populate expressly supplied attributes, NOT gl_VertexID or gl_InstanceID - const location = attributeLocations[name]; + const location = attributeLocations[name] ?? NaN; const buffer = gl.createBuffer(); gl.bindBuffer(GL.ARRAY_BUFFER, buffer); gl.enableVertexAttribArray(location); - const attribSize = attribSizeLookup[type]; - const attribElementType = attribElementTypeLookup[type]; + const attribSize = attribSizeLookup[type] ?? NaN; + const attribElementType = attribElementTypeLookup[type] ?? NaN; if (GL_DEBUG && (attribSize === undefined || attribElementType === undefined)) throw new Error(`Attribute type ${type} is not yet properly covered`); if (integerTypes.has(attribElementType)) { diff --git a/packages/charts/src/common/math.ts b/packages/charts/src/common/math.ts index 789d3ec44a..32adb2e89a 100644 --- a/packages/charts/src/common/math.ts +++ b/packages/charts/src/common/math.ts @@ -20,7 +20,7 @@ export function extent(array: number[]): [min: number, max: number] { let min = Infinity; let max = -Infinity; for (let i = 0; i < len; i += 1) { - const value = array[i]; + const value = array[i] ?? NaN; if (min > value) min = value; if (max < value) max = value; } diff --git a/packages/charts/src/components/legend/legend.tsx b/packages/charts/src/components/legend/legend.tsx index b05b257ff5..73cea1ae8c 100644 --- a/packages/charts/src/components/legend/legend.tsx +++ b/packages/charts/src/components/legend/legend.tsx @@ -123,7 +123,7 @@ function LegendComponent(props: LegendStateProps & LegendDispatchProps) { ...customProps, seriesIdentifiers, path, - extraValue: itemProps.extraValues.get(seriesIdentifiers[0].key)?.get(childId || ''), + extraValue: itemProps.extraValues.get(seriesIdentifiers[0]?.key ?? '')?.get(childId ?? ''), onItemOutAction: itemProps.mouseOutAction, onItemOverActon: () => itemProps.mouseOverAction(path), onItemClickAction: (negate: boolean) => itemProps.toggleDeselectSeriesAction(seriesIdentifiers, negate), diff --git a/packages/charts/src/components/legend/utils.ts b/packages/charts/src/components/legend/utils.ts index 287b454207..2807048a06 100644 --- a/packages/charts/src/components/legend/utils.ts +++ b/packages/charts/src/components/legend/utils.ts @@ -12,7 +12,7 @@ import { LegendItemExtraValues, LegendItem } from '../../common/legend'; export function getExtra(extraValues: Map, item: LegendItem, totalItems: number) { const { seriesIdentifiers, defaultExtra, childId, path } = item; // don't show extra if the legend item is associated with multiple series - if (extraValues.size === 0 || seriesIdentifiers.length > 1) { + if (extraValues.size === 0 || seriesIdentifiers.length > 1 || !seriesIdentifiers[0]) { return defaultExtra?.formatted ?? ''; } const [{ key }] = seriesIdentifiers; diff --git a/packages/charts/src/components/portal/utils.ts b/packages/charts/src/components/portal/utils.ts index 6abffc0985..4fb2ad1cdb 100644 --- a/packages/charts/src/components/portal/utils.ts +++ b/packages/charts/src/components/portal/utils.ts @@ -104,6 +104,8 @@ export function getElementZIndex(element: HTMLElement, cousin: HTMLElement): num // reverse the nodes to walk from top -> element for (let i = nodesToInspect.length - 1; i >= 0; i--) { const node = nodesToInspect[i]; + if (!node) continue; + // get this node's z-index css value const zIndex = window.document.defaultView!.getComputedStyle(node).getPropertyValue('z-index'); diff --git a/packages/charts/src/mocks/series/series_identifiers.ts b/packages/charts/src/mocks/series/series_identifiers.ts index 398e244d80..53e7ff09d5 100644 --- a/packages/charts/src/mocks/series/series_identifiers.ts +++ b/packages/charts/src/mocks/series/series_identifiers.ts @@ -34,6 +34,6 @@ export class MockSeriesIdentifier { } static fromSpec(specs: BasicSeriesSpec): XYChartSeriesIdentifier { - return MockSeriesIdentifier.fromSpecs([specs])[0]; + return MockSeriesIdentifier.fromSpecs([specs])[0]!; } } diff --git a/packages/charts/src/mocks/utils.ts b/packages/charts/src/mocks/utils.ts index aca70385f0..fabe77c55e 100644 --- a/packages/charts/src/mocks/utils.ts +++ b/packages/charts/src/mocks/utils.ts @@ -77,7 +77,7 @@ export const getRandomEntryFn = (seed = getRNGSeed()) => { const keys = Object.keys(entries); const index = Math.floor(rng() * keys.length); - const key = keys[index]; + const key = keys[index]!; return entries[key]; }; diff --git a/packages/charts/src/renderers/canvas/index.ts b/packages/charts/src/renderers/canvas/index.ts index 12869a2470..52d5d8dbbb 100644 --- a/packages/charts/src/renderers/canvas/index.ts +++ b/packages/charts/src/renderers/canvas/index.ts @@ -76,12 +76,14 @@ export function withClipRanges( if (negate) { clippedRanges.forEach(([x0, x1]) => ctx.rect(x0, y, x1 - x0, height)); } else { - const firstX = clippedRanges[0][0]; - const lastX = clippedRanges[clippedRanges.length - 1][1]; + const firstX = clippedRanges[0]?.[0] ?? NaN; + const lastX = clippedRanges[clippedRanges.length - 1]?.[1] ?? NaN; ctx.rect(0, -0.5, firstX, height); ctx.rect(lastX, y, width - lastX, height); clippedRanges.forEach(([, x0], i) => { - if (i < clippedRanges.length - 1) ctx.rect(x0, y, clippedRanges[i + 1][0] - x0, height); + if (i < clippedRanges.length - 1) { + ctx.rect(x0, y, (clippedRanges[i + 1]?.[0] ?? NaN) - x0, height); + } }); } ctx.clip(); diff --git a/packages/charts/src/scales/scale_band.ts b/packages/charts/src/scales/scale_band.ts index c994aa54c2..65ad159ac2 100644 --- a/packages/charts/src/scales/scale_band.ts +++ b/packages/charts/src/scales/scale_band.ts @@ -28,7 +28,7 @@ export class ScaleBand { readonly originalBandwidth: number; readonly type: ScaleBandType; readonly domain: (string | number)[]; - readonly range: number[]; + readonly range: [number, number]; readonly barsPadding: number; readonly minInterval: number; readonly unit?: string; @@ -62,7 +62,7 @@ export class ScaleBand { this.originalBandwidth = d3Scale.bandwidth() || 0; this.step = d3Scale.step(); this.domain = (inputDomain.length > 0 ? [...new Set(inputDomain)] : [undefined]) as (string | number)[]; - this.range = range.slice(); + this.range = [range[0], range[1]]; this.bandwidthPadding = this.bandwidth; const invertedScale = scaleQuantize() .domain(range) diff --git a/packages/charts/src/scales/scale_continuous.test.ts b/packages/charts/src/scales/scale_continuous.test.ts index 46f88ad362..53d36dd661 100644 --- a/packages/charts/src/scales/scale_continuous.test.ts +++ b/packages/charts/src/scales/scale_continuous.test.ts @@ -10,7 +10,7 @@ import { DateTime, Settings } from 'luxon'; import { ScaleContinuous } from '.'; import { LOG_MIN_ABS_DOMAIN, ScaleType } from './constants'; -import { limitLogScaleDomain } from './scale_continuous'; +import { ScaleData, limitLogScaleDomain } from './scale_continuous'; import { isContinuousScale, isLogarithmicScale } from './types'; import { computeXScale } from '../chart_types/xy_chart/utils/scales'; import { MockXDomain } from '../mocks/xy/domains'; @@ -40,7 +40,7 @@ describe('Scale Continuous', () => { const startTime = DateTime.fromISO('2019-01-01T00:00:00.000', { zone: 'utc' }); const midTime = DateTime.fromISO('2019-01-02T00:00:00.000', { zone: 'utc' }); const endTime = DateTime.fromISO('2019-01-03T00:00:00.000', { zone: 'utc' }); - const domain = [startTime.toMillis(), endTime.toMillis()]; + const domain: [number, number] = [startTime.toMillis(), endTime.toMillis()]; const minRange = 0; const maxRange = 100; const scale = new ScaleContinuous({ type: ScaleType.Time, domain, range: [minRange, maxRange] }); @@ -127,7 +127,7 @@ describe('Scale Continuous', () => { expect(scaleLinear.invertWithStep(120, data)).toEqual({ value: 3, withinBandwidth: false }); }); test('can get the right x value on linear scale with regular band 1', () => { - const domain = [0, 100]; + const domain: [number, number] = [0, 100]; const data = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]; // we tweak the maxRange removing the bandwidth to correctly compute @@ -187,10 +187,6 @@ describe('Scale Continuous', () => { }); describe('isSingleValue', () => { - test('should return true for domain with fewer than 2 values', () => { - const scale = new ScaleContinuous({ type: ScaleType.Linear, domain: [], range: [0, 100] }); - expect(scale.isSingleValue()).toBe(true); - }); test('should return true for domain with equal min and max values', () => { const scale = new ScaleContinuous({ type: ScaleType.Linear, domain: [1, 1], range: [0, 100] }); expect(scale.isSingleValue()).toBe(true); @@ -202,7 +198,7 @@ describe('Scale Continuous', () => { }); describe('xScale values with minInterval and bandwidth', () => { - const domain = [7.053400039672852, 1070.1354763603908]; + const domain: [number, number] = [7.053400039672852, 1070.1354763603908]; it('should return nice ticks when minInterval & bandwidth are 0', () => { const scale = new ScaleContinuous( @@ -457,54 +453,54 @@ describe('Scale Continuous', () => { }); describe('Domain pixel padding', () => { - const scaleOptions = Object.freeze({ + const scaleData = Object.freeze({ type: ScaleType.Linear, range: [0, 100] as Range, domain: [10, 60], }); it('should add pixel padding to domain', () => { - const scale = new ScaleContinuous(scaleOptions, { domainPixelPadding: 10 }); + const scale = new ScaleContinuous(scaleData, { domainPixelPadding: 10 }); expect(scale.domain).toEqual([3.75, 66.25]); }); it('should handle inverted domain pixel padding', () => { - const scale = new ScaleContinuous({ ...scaleOptions, domain: [60, 10] }, { domainPixelPadding: 10 }); + const scale = new ScaleContinuous({ ...scaleData, domain: [60, 10] }, { domainPixelPadding: 10 }); expect(scale.domain).toEqual([66.25, 3.75]); }); it('should handle negative domain pixel padding', () => { - const scale = new ScaleContinuous({ ...scaleOptions, domain: [-60, -20] }, { domainPixelPadding: 10 }); + const scale = new ScaleContinuous({ ...scaleData, domain: [-60, -20] }, { domainPixelPadding: 10 }); expect(scale.domain).toEqual([-65, -15]); }); it('should handle negative inverted domain pixel padding', () => { - const scale = new ScaleContinuous({ ...scaleOptions, domain: [-20, -60] }, { domainPixelPadding: 10 }); + const scale = new ScaleContinuous({ ...scaleData, domain: [-20, -60] }, { domainPixelPadding: 10 }); expect(scale.domain).toEqual([-15, -65]); }); it('should constrain pixel padding to zero', () => { - const scale = new ScaleContinuous(scaleOptions, { domainPixelPadding: 20 }); + const scale = new ScaleContinuous(scaleData, { domainPixelPadding: 20 }); expect(scale.domain).toEqual([0, 75]); }); it('should not constrain pixel padding to zero', () => { - const scale = new ScaleContinuous(scaleOptions, { domainPixelPadding: 18, constrainDomainPadding: false }); + const scale = new ScaleContinuous(scaleData, { domainPixelPadding: 18, constrainDomainPadding: false }); expect(scale.domain).toEqual([-4.0625, 74.0625]); }); it('should nice domain after pixel padding is applied', () => { const scale = new ScaleContinuous( - { ...scaleOptions, nice: true }, + { ...scaleData, nice: true }, { domainPixelPadding: 18, constrainDomainPadding: false }, ); expect(scale.domain).toEqual([-10, 80]); }); it('should not handle pixel padding when pixel is greater than half the total range', () => { - const criticalPadding = Math.abs(scaleOptions.range[0] - scaleOptions.range[1]) / 2; - const scale = new ScaleContinuous(scaleOptions, { domainPixelPadding: criticalPadding }); - expect(scale.domain).toEqual(scaleOptions.domain); + const criticalPadding = Math.abs(scaleData.range[0] - scaleData.range[1]) / 2; + const scale = new ScaleContinuous(scaleData, { domainPixelPadding: criticalPadding }); + expect(scale.domain).toEqual(scaleData.domain); }); }); diff --git a/packages/charts/src/scales/scale_continuous.ts b/packages/charts/src/scales/scale_continuous.ts index 569fdf5ba2..43f1fdb512 100644 --- a/packages/charts/src/scales/scale_continuous.ts +++ b/packages/charts/src/scales/scale_continuous.ts @@ -26,7 +26,7 @@ import { LogScaleOptions } from './types'; import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; import { getLinearTicks, getNiceLinearTicks } from '../chart_types/xy_chart/utils/get_linear_ticks'; import { screenspaceMarkerScaleCompressor } from '../solvers/screenspace_marker_scale_compressor'; -import { clamp, isFiniteNumber, mergePartial } from '../utils/common'; +import { clamp, isDefined, isFiniteNumber, mergePartial } from '../utils/common'; import { getMomentWithTz } from '../utils/data/date_time'; import { ContinuousDomain, Range } from '../utils/domain'; @@ -72,7 +72,7 @@ export class ScaleContinuous { readonly minInterval: number; readonly step: number; readonly type: ScaleContinuousType; - readonly domain: number[]; + readonly domain: [number, number]; readonly range: Range; readonly isInverted: boolean; readonly linearBase: number; @@ -141,12 +141,12 @@ export class ScaleContinuous { } } - const niceDomain = isNice ? (d3Scale.domain() as number[]) : dataDomain; + const niceDomain = isNice ? (d3Scale.domain() as [number, number]) : dataDomain; const paddedDomain = isPixelPadded ? getPixelPaddedDomain( totalRange, - niceDomain as [number, number], + niceDomain, scaleOptions.domainPixelPadding, scaleOptions.constrainDomainPadding, ) @@ -156,7 +156,7 @@ export class ScaleContinuous { if (isPixelPadded && isNice) (d3Scale as ScaleContinuousNumeric).nice(scaleOptions.desiredTickCount); - const nicePaddedDomain = isPixelPadded && isNice ? (d3Scale.domain() as number[]) : paddedDomain; + const nicePaddedDomain = isPixelPadded && isNice ? (d3Scale.domain() as [number, number]) : paddedDomain; this.tickValues = type === ScaleType.Time @@ -218,17 +218,19 @@ export class ScaleContinuous { const leftIndex = bisectLeft(data, bisectValue); if (leftIndex === 0) { - const withinBandwidth = invertedValue >= data[0]; + const [dataValue = NaN] = data; + const withinBandwidth = invertedValue >= dataValue; return { withinBandwidth, value: - data[0] + (withinBandwidth ? 0 : -this.minInterval * Math.ceil((data[0] - invertedValue) / this.minInterval)), + dataValue + + (withinBandwidth ? 0 : -this.minInterval * Math.ceil((dataValue - invertedValue) / this.minInterval)), }; } - const currentValue = data[leftIndex - 1]; + const currentValue = data[leftIndex - 1] ?? NaN; // pure linear scale if (this.bandwidth === 0) { - const nextValue = data[leftIndex]; + const nextValue = data[leftIndex] ?? NaN; const nextDiff = Math.abs(nextValue - invertedValue); const prevDiff = Math.abs(invertedValue - currentValue); return { @@ -245,25 +247,37 @@ export class ScaleContinuous { }; } + private isDegenerateDomain(): boolean { + return this.domain.every((v) => v === this.domain[0]); + } + isSingleValue(): boolean { - return this.isSingleValueHistogram || isDegenerateDomain(this.domain); + return this.isSingleValueHistogram || this.isDegenerateDomain(); } isValueInDomain(value: unknown): boolean { - return isFiniteNumber(value) && this.domain[0] <= value && value <= this.domain[1]; + const [start = NaN, end = NaN] = this.domain; + return isFiniteNumber(value) && start <= value && value <= end; } } -function getTimeTicks(domain: number[], desiredTickCount: number, timeZone: string, minInterval: number) { - const startDomain = getMomentWithTz(domain[0], timeZone); - const endDomain = getMomentWithTz(domain[1], timeZone); +function getTimeTicks(domain: [number, number], desiredTickCount: number, timeZone: string, minInterval: number) { + const [start, end] = domain; + const startDomain = getMomentWithTz(start, timeZone); + const endDomain = getMomentWithTz(end, timeZone); const offset = startDomain.utcOffset(); const shiftedDomainMin = startDomain.add(offset, 'minutes').valueOf(); const shiftedDomainMax = endDomain.add(offset, 'minutes').valueOf(); const tzShiftedScale = scaleUtc().domain([shiftedDomainMin, shiftedDomainMax]); let currentCount = desiredTickCount; let rawTicks = tzShiftedScale.ticks(desiredTickCount); - while (rawTicks.length > 2 && currentCount > 0 && rawTicks[1].valueOf() - rawTicks[0].valueOf() < minInterval) { + while ( + rawTicks.length > 2 && + currentCount > 0 && + isDefined(rawTicks[0]) && + isDefined(rawTicks[1]) && + rawTicks[1].valueOf() - rawTicks[0].valueOf() < minInterval + ) { currentCount--; rawTicks = tzShiftedScale.ticks(currentCount); } @@ -278,28 +292,29 @@ function getTimeTicks(domain: number[], desiredTickCount: number, timeZone: stri } function getLinearNonDenserTicks( - domain: number[], + domain: [number, number], desiredTickCount: number, base: number, minInterval: number, ): number[] { - const start = domain[0]; - const stop = domain[domain.length - 1]; + const [start, stop] = domain; let currentCount = desiredTickCount; let ticks = getLinearTicks(start, stop, desiredTickCount, base); - while (ticks.length > 2 && currentCount > 0 && ticks[1] - ticks[0] < minInterval) { + while ( + ticks.length > 2 && + currentCount > 0 && + isDefined(ticks[0]) && + isDefined(ticks[1]) && + ticks[1] - ticks[0] < minInterval + ) { currentCount--; ticks = getLinearTicks(start, stop, currentCount, base); } return ticks; } -function isDegenerateDomain(domain: unknown[]): boolean { - return domain.every((v) => v === domain[0]); -} - /** @internal */ -export function limitLogScaleDomain([min, max]: ContinuousDomain, logMinLimit: number) { +export function limitLogScaleDomain([min, max]: ContinuousDomain, logMinLimit: number): [min: number, max: number] { // todo further simplify this const absLimit = Math.abs(logMinLimit); const fallback = absLimit || LOG_MIN_ABS_DOMAIN; @@ -318,7 +333,7 @@ function getPixelPaddedDomain( desiredPixelPadding: number, constrainDomainPadding?: boolean, intercept = 0, -) { +): [number, number] { const inverted = domain[1] < domain[0]; const orderedDomain: [number, number] = inverted ? [domain[1], domain[0]] : domain; const { scaleMultiplier } = screenspaceMarkerScaleCompressor( @@ -372,13 +387,14 @@ type D3ScaleNonTime = ScaleLinear | ScaleL /** * All possible d3 scales + * @internal */ -interface ScaleData { +export interface ScaleData { /** The Type of continuous scale */ type: ScaleContinuousType; /** The data input domain */ - domain: number[]; + domain: [number, number]; /** The data output range */ range: Range; nice?: boolean; diff --git a/packages/charts/src/scales/scale_time.test.ts b/packages/charts/src/scales/scale_time.test.ts index c553435a86..a5566a4746 100644 --- a/packages/charts/src/scales/scale_time.test.ts +++ b/packages/charts/src/scales/scale_time.test.ts @@ -79,7 +79,7 @@ describe('[Scale Time] - timezones', () => { const midTime = DateTime.fromISO('2019-01-02T00:00:00.000').toMillis(); const endTime = DateTime.fromISO('2019-01-03T00:00:00.000').toMillis(); const data = [startTime, midTime, endTime]; - const domain = [startTime, endTime]; + const domain: [number, number] = [startTime, endTime]; const minRange = 0; const maxRange = 99; const minInterval = (endTime - startTime) / 2; @@ -111,7 +111,7 @@ describe('[Scale Time] - timezones', () => { const midTime = DateTime.fromISO('2019-01-02T00:00:00.000Z').toMillis(); const endTime = DateTime.fromISO('2019-01-03T00:00:00.000Z').toMillis(); const data = [startTime, midTime, endTime]; - const domain = [startTime, endTime]; + const domain: [number, number] = [startTime, endTime]; const minRange = 0; const maxRange = 99; const minInterval = (endTime - startTime) / 2; @@ -139,7 +139,7 @@ describe('[Scale Time] - timezones', () => { const midTime = DateTime.fromISO('2019-01-02T00:00:00.000+08:00').toMillis(); const endTime = DateTime.fromISO('2019-01-03T00:00:00.000+08:00').toMillis(); const data = [startTime, midTime, endTime]; - const domain = [startTime, endTime]; + const domain: [number, number] = [startTime, endTime]; const minRange = 0; const maxRange = 99; const minInterval = (endTime - startTime) / 2; @@ -173,7 +173,7 @@ describe('[Scale Time] - timezones', () => { const midTime = DateTime.fromISO('2019-01-02T00:00:00.000-08:00').toMillis(); const endTime = DateTime.fromISO('2019-01-03T00:00:00.000-08:00').toMillis(); const data = [startTime, midTime, endTime]; - const domain = [startTime, endTime]; + const domain: [number, number] = [startTime, endTime]; const minRange = 0; const maxRange = 99; const minInterval = (endTime - startTime) / 2; @@ -212,7 +212,7 @@ describe('[Scale Time] - timezones', () => { const midTime = DateTime.fromISO('2019-01-02T00:00:00.000', { zone: timezone }).toMillis(); const endTime = DateTime.fromISO('2019-01-03T00:00:00.000', { zone: timezone }).toMillis(); const data = [startTime, midTime, endTime]; - const domain = [startTime, endTime]; + const domain: [number, number] = [startTime, endTime]; const minRange = 0; const maxRange = 99; const minInterval = (endTime - startTime) / 2; @@ -235,17 +235,17 @@ describe('[Scale Time] - timezones', () => { expect(scale.tickValues[0]).toEqual(startTime); expect(scale.tickValues[4]).toEqual(midTime); expect(scale.tickValues[8]).toEqual(endTime); - expect(formatFunction(scale.tickValues[0])).toEqual( + expect(formatFunction(scale.tickValues[0] ?? NaN)).toEqual( DateTime.fromISO('2019-01-01T00:00:00.000', { zone: timezone, }).toISO(), ); - expect(formatFunction(scale.tickValues[4])).toEqual( + expect(formatFunction(scale.tickValues[4] ?? NaN)).toEqual( DateTime.fromISO('2019-01-02T00:00:00.000', { zone: timezone, }).toISO(), ); - expect(formatFunction(scale.tickValues[8])).toEqual( + expect(formatFunction(scale.tickValues[8] ?? NaN)).toEqual( DateTime.fromISO('2019-01-03T00:00:00.000', { zone: timezone, }).toISO(), diff --git a/packages/charts/src/scales/scales.test.ts b/packages/charts/src/scales/scales.test.ts index cda5851c2a..aa17379e6e 100644 --- a/packages/charts/src/scales/scales.test.ts +++ b/packages/charts/src/scales/scales.test.ts @@ -32,7 +32,7 @@ describe('Scale Test', () => { expect(scaledValue4).toBe(bandwidth * 3); }); test('Create an linear scale', () => { - const data = [0, 10]; + const data: [number, number] = [0, 10]; const minRange = 0; const maxRange = 100; const linearScale = new ScaleContinuous({ @@ -57,7 +57,7 @@ describe('Scale Test', () => { const date1 = DateTime.fromISO('2019-01-01T00:00:00.000', { zone: 'utc' }).toMillis(); const date2 = DateTime.fromISO('2019-01-01T00:00:00.000', { zone: 'utc' }).plus({ days: 90 }).toMillis(); const date3 = DateTime.fromISO('2019-01-01T00:00:00.000', { zone: 'utc' }).plus({ days: 180 }).toMillis(); - const data = [date1, date3]; + const data: [number, number] = [date1, date3]; const minRange = 0; const maxRange = 100; const timeScale = new ScaleContinuous({ @@ -77,7 +77,7 @@ describe('Scale Test', () => { expect(scaledValue3).toBe(100); }); test('Create an log scale', () => { - const data = [1, 10]; + const data: [number, number] = [1, 10]; const minRange = 0; const maxRange = 100; const logScale = new ScaleContinuous({ @@ -95,7 +95,7 @@ describe('Scale Test', () => { expect(scaledValue3).toBe((Math.log(5) / Math.log(10)) * 100); }); test('Create an log scale starting with 0 as min', () => { - const data = [0, 10]; + const data: [number, number] = [0, 10]; const minRange = 0; const maxRange = 100; const logScale = new ScaleContinuous({ @@ -113,7 +113,7 @@ describe('Scale Test', () => { expect(scaledValue3).toBe((Math.log(5) / Math.log(10)) * 100); }); test('Create an sqrt scale', () => { - const data = [0, 10]; + const data: [number, number] = [0, 10]; const minRange = 0; const maxRange = 100; const sqrtScale = new ScaleContinuous({ @@ -173,7 +173,7 @@ describe('Scale Test', () => { test('compare ordinal scale and linear/band invertWithStep 3 bars', () => { const data = [0, 1, 2]; - const domainLinear = [0, 2]; + const domainLinear: [number, number] = [0, 2]; const domainOrdinal = [0, 1, 2]; const minRange = 0; const maxRange = 120; @@ -192,7 +192,7 @@ describe('Scale Test', () => { expect(linearScale.invertWithStep(81, data)).toEqual({ value: 2, withinBandwidth: true }); }); test('compare ordinal scale and linear/band 2 bars', () => { - const dataLinear = [0, 1]; + const dataLinear: [number, number] = [0, 1]; const dataOrdinal = [0, 1]; const minRange = 0; const maxRange = 100; diff --git a/packages/charts/src/scales/utils.ts b/packages/charts/src/scales/utils.ts index 848991dd1e..8839864a82 100644 --- a/packages/charts/src/scales/utils.ts +++ b/packages/charts/src/scales/utils.ts @@ -18,7 +18,7 @@ export function getLinearNonDenserTicks( ): number[] { let currentCount = count; let ticks = getLinearTicks(start, stop, count, base); - while (ticks.length > 2 && currentCount > 0 && ticks[1] - ticks[0] < minInterval) { + while (ticks.length > 2 && currentCount > 0 && (ticks[1] ?? NaN) - (ticks[0] ?? NaN) < minInterval) { currentCount--; ticks = getLinearTicks(start, stop, currentCount, base); } diff --git a/packages/charts/src/solvers/screenspace_marker_scale_compressor.ts b/packages/charts/src/solvers/screenspace_marker_scale_compressor.ts index 4f1441f61c..8ada38654a 100644 --- a/packages/charts/src/solvers/screenspace_marker_scale_compressor.ts +++ b/packages/charts/src/solvers/screenspace_marker_scale_compressor.ts @@ -36,10 +36,12 @@ export const screenspaceMarkerScaleCompressor = ( const itemCount = Math.min(domainPositions.length, itemWidths.length); for (let left = 0; left < itemCount; left++) { for (let right = 0; right < itemCount; right++) { - if (domainPositions[left] > domainPositions[right]) continue; // must adhere to left <= right + const domainLeft = domainPositions[left] ?? NaN; + const domainRight = domainPositions[right] ?? NaN; + if (domainLeft > domainRight) continue; // must adhere to left <= right - const range = outerWidth - itemWidths[left][0] - itemWidths[right][1]; // negative if not enough room - const domain = domainPositions[right] - domainPositions[left]; // always non-negative and finite + const range = outerWidth - (itemWidths[left]?.[0] ?? NaN) - (itemWidths[right]?.[1] ?? NaN); // negative if not enough room + const domain = domainRight - domainLeft; // always non-negative and finite const scaleMultiplier = range / domain; // may not be finite, and that's OK if (scaleMultiplier < result.scaleMultiplier || Number.isNaN(scaleMultiplier)) { diff --git a/packages/charts/src/state/chart_state.ts b/packages/charts/src/state/chart_state.ts index 9cce41742c..308347ba4b 100644 --- a/packages/charts/src/state/chart_state.ts +++ b/packages/charts/src/state/chart_state.ts @@ -459,7 +459,7 @@ function chartTypeFromSpecs(specs: SpecList): ChartType | null { .map((s) => s.chartType) .filter((type) => type !== ChartType.Global) .filter(keepDistinct); - if (nonGlobalTypes.length !== 1) { + if (!nonGlobalTypes[0]) { Logger.warn(`${nonGlobalTypes.length === 0 ? 'Zero' : 'Multiple'} chart types in the same configuration`); return null; } diff --git a/packages/charts/src/state/reducers/interactions.ts b/packages/charts/src/state/reducers/interactions.ts index 664e148b54..be41a927dd 100644 --- a/packages/charts/src/state/reducers/interactions.ts +++ b/packages/charts/src/state/reducers/interactions.ts @@ -293,5 +293,5 @@ function getDrilldownData(globalState: GlobalChartState) { return []; } const layerValues = getPickedShapesLayerValues(globalState)[0]; - return layerValues ? layerValues[layerValues.length - 1].path.map((n) => n.value) : []; + return layerValues ? layerValues[layerValues.length - 1]?.path.map((n) => n.value) ?? [] : []; } diff --git a/packages/charts/src/state/selectors/get_settings_spec.ts b/packages/charts/src/state/selectors/get_settings_spec.ts index c60b621975..d16a347b06 100644 --- a/packages/charts/src/state/selectors/get_settings_spec.ts +++ b/packages/charts/src/state/selectors/get_settings_spec.ts @@ -24,10 +24,8 @@ export const getSettingsSpecSelector = createCustomCachedSelector([getSpecs], ge function getSettingsSpec(specs: SpecList): SettingsSpec { const settingsSpecs = getSpecsFromStore(specs, ChartType.Global, SpecType.Settings); - if (settingsSpecs.length === 1) { - return handleListenerDebouncing(settingsSpecs[0]); - } - return DEFAULT_SETTINGS_SPEC; + const spec = settingsSpecs[0]; + return spec ? handleListenerDebouncing(spec) : DEFAULT_SETTINGS_SPEC; } function handleListenerDebouncing(settings: SettingsSpec): SettingsSpec { diff --git a/packages/charts/src/state/selectors/get_small_multiples_spec.ts b/packages/charts/src/state/selectors/get_small_multiples_spec.ts index 944d4f836b..f8c2717836 100644 --- a/packages/charts/src/state/selectors/get_small_multiples_spec.ts +++ b/packages/charts/src/state/selectors/get_small_multiples_spec.ts @@ -26,5 +26,5 @@ export const getSmallMultiplesSpecs = createCustomCachedSelector([getSpecs], (sp * @internal */ export const getSmallMultiplesSpec = createCustomCachedSelector([getSpecs], (specs) => - getSpecFromStore(specs, ChartType.Global, SpecType.SmallMultiples), + getSpecFromStore(specs, ChartType.Global, SpecType.SmallMultiples, false), ); diff --git a/packages/charts/src/state/spec_factory.test.tsx b/packages/charts/src/state/spec_factory.test.tsx index 1dc842daca..ba43d3c01b 100644 --- a/packages/charts/src/state/spec_factory.test.tsx +++ b/packages/charts/src/state/spec_factory.test.tsx @@ -73,11 +73,11 @@ describe('Spec factory', () => { expect(debugState.bars).toHaveLength(2); // different id same y - expect(debugState.bars?.[0].name).toBe('spec2'); - expect(debugState.bars?.[0].bars[0].y).toBe(1); + expect(debugState.bars?.[0]?.name).toBe('spec2'); + expect(debugState.bars?.[0]?.bars[0]?.y).toBe(1); // different id same y - expect(debugState.bars?.[1].name).toBe('spec1'); - expect(debugState.bars?.[1].bars[0].y).toBe(2); + expect(debugState.bars?.[1]?.name).toBe('spec1'); + expect(debugState.bars?.[1]?.bars[0]?.y).toBe(2); }); }); diff --git a/packages/charts/src/state/utils.ts b/packages/charts/src/state/utils.ts index ab16cea167..a189316f6a 100644 --- a/packages/charts/src/state/utils.ts +++ b/packages/charts/src/state/utils.ts @@ -21,15 +21,19 @@ export function getSpecsFromStore(specs: SpecList, chartType: Ch /** * Returns first matching spec * @internal + * TODO: Make these generator types automatic */ -export function getSpecFromStore( +export function getSpecFromStore( specs: SpecList, chartType: ChartType, specType: string, -): U | undefined { - return ( - (Object.values(specs).find((spec) => spec.chartType === chartType && spec.specType === specType) as U) ?? undefined - ); + required: R, +): U | RR { + const spec = Object.values(specs).find((spec) => spec.chartType === chartType && spec.specType === specType) as U; + + if (!spec && required) throw new Error(`Unable to find spec [${chartType} = ${specType}]`); + + return spec ?? null; } /** @internal */ diff --git a/packages/charts/src/utils/common.test.ts b/packages/charts/src/utils/common.test.ts index c7e36bcf28..48acbd6eae 100644 --- a/packages/charts/src/utils/common.test.ts +++ b/packages/charts/src/utils/common.test.ts @@ -652,7 +652,7 @@ describe('common utilities', () => { expect(newBase).toEqual({ ...newBase, number: partial.number, - string: partials[0].string, + string: partials[0]?.string, }); }); @@ -666,8 +666,8 @@ describe('common utilities', () => { expect(newBase).toEqual({ ...newBase, number: partial.number, - string: partials[0].string, - boolean: partials[1].boolean, + string: partials[0]?.string, + boolean: partials[1]?.boolean, }); }); @@ -687,7 +687,7 @@ describe('common utilities', () => { const newBase = mergePartial(base, partial, { mergeOptionalPartialValues: false }, partials); expect(newBase).toEqual({ ...newBase, - array1: partials[1].array1, + array1: partials[1]?.array1, }); }); @@ -707,7 +707,7 @@ describe('common utilities', () => { const newBase = mergePartial(base, partial, { mergeOptionalPartialValues: false }, partials); expect(newBase).toEqual({ ...newBase, - array2: partials[0].array2, + array2: partials[0]?.array2, }); }); @@ -717,7 +717,7 @@ describe('common utilities', () => { const newBase = mergePartial(base, partial, { mergeOptionalPartialValues: false }, partials); expect(newBase).toEqual({ ...newBase, - array2: partials[1].array2, + array2: partials[1]?.array2, }); }); @@ -742,7 +742,7 @@ describe('common utilities', () => { ...newBase, nested: { ...newBase.nested, - number: partials[0].nested!.number, + number: partials[0]?.nested!.number, }, }); }); diff --git a/packages/charts/src/utils/data_generators/simple_noise.ts b/packages/charts/src/utils/data_generators/simple_noise.ts index dd2ab43434..197d736b46 100644 --- a/packages/charts/src/utils/data_generators/simple_noise.ts +++ b/packages/charts/src/utils/data_generators/simple_noise.ts @@ -36,12 +36,10 @@ export class Simple1DNoise { const t = scaledX - xFloor; const tRemapSmoothstep = t * t * (3 - 2 * t); - // tslint:disable-next-line:no-bitwise const xMin = xFloor & this.maxVerticesMask; - // tslint:disable-next-line:no-bitwise const xMax = (xMin + 1) & this.maxVerticesMask; - const y = this.lerp(r[xMin], r[xMax], tRemapSmoothstep); + const y = this.lerp(r[xMin] ?? 0, r[xMax] ?? 0, tRemapSmoothstep); return y * this.amplitude; } diff --git a/packages/charts/src/utils/data_samples/test_dataset_kibana.ts b/packages/charts/src/utils/data_samples/test_dataset_kibana.ts index 2fcdc2976d..5bc1ce645c 100644 --- a/packages/charts/src/utils/data_samples/test_dataset_kibana.ts +++ b/packages/charts/src/utils/data_samples/test_dataset_kibana.ts @@ -9,8 +9,11 @@ /** @internal */ export const KIBANA_METRICS = { metrics: { - kibana_os_load: [ - { + kibana_os_load: { + /** + * Variant 1 - Load average over the last minute + */ + v1: { bucket_size: '30 seconds', timeRange: { min: 1551438000000, max: 1551441600000 }, metric: { @@ -146,9 +149,12 @@ export const KIBANA_METRICS = { [1551441510000, 18.640625], [1551441540000, 13.1953125], [1551441570000, 10.1953125], - ], + ] as [number, number][], }, - { + /** + * Variant 2 - Load average over the last 5 minutes + */ + v2: { bucket_size: '30 seconds', timeRange: { min: 1551438000000, max: 1551441600000 }, metric: { @@ -284,9 +290,12 @@ export const KIBANA_METRICS = { [1551441510000, 12.0859375], [1551441540000, 11.375], [1551441570000, 10.84375], - ], + ] as [number, number][], }, - { + /** + * Variant 3 - Load average over the last 15 minutes + */ + v3: { bucket_size: '30 seconds', timeRange: { min: 1551438000000, max: 1551441600000 }, metric: { @@ -422,11 +431,14 @@ export const KIBANA_METRICS = { [1551441510000, 10.9921875], [1551441540000, 10.7890625], [1551441570000, 10.625], - ], + ] as [number, number][], }, - ], - kibana_average_concurrent_connections: [ - { + }, + kibana_average_concurrent_connections: { + /** + * Variant 1 - Total number of open socket connections to the Kibana instance + */ + v1: { bucket_size: '30 seconds', timeRange: { min: 1551438000000, max: 1551441600000 }, metric: { @@ -561,11 +573,14 @@ export const KIBANA_METRICS = { [1551441510000, 16], [1551441540000, 16], [1551441570000, 17], - ], + ] as [number, number][], }, - ], - kibana_process_delay: [ - { + }, + kibana_process_delay: { + /** + * Variant 1 - Delay in Kibana server event loops + */ + v1: { bucket_size: '30 seconds', timeRange: { min: 1551438000000, max: 1551441600000 }, metric: { @@ -700,11 +715,14 @@ export const KIBANA_METRICS = { [1551441510000, 1.8046932220458984], [1551441540000, 1.9574308395385742], [1551441570000, 1.9149093627929688], - ], + ] as [number, number][], }, - ], - kibana_memory: [ - { + }, + kibana_memory: { + /** + * Variant 1 - Limit of memory usage before garbage collection + */ + v1: { bucket_size: '30 seconds', timeRange: { min: 1551438000000, max: 1551441600000 }, metric: { @@ -840,9 +858,12 @@ export const KIBANA_METRICS = { [1551441510000, 1501560832], [1551441540000, 1501560832], [1551441570000, 1501560832], - ], + ] as [number, number][], }, - { + /** + * Variant 2 - Total heap used by Kibana running in Node.js + */ + v2: { bucket_size: '30 seconds', timeRange: { min: 1551438000000, max: 1551441600000 }, metric: { @@ -978,11 +999,14 @@ export const KIBANA_METRICS = { [1551441510000, 666820608], [1551441540000, 662138880], [1551441570000, 651341824], - ], + ] as [number, number][], }, - ], - kibana_response_times: [ - { + }, + kibana_response_times: { + /** + * Variant 1 - Maximum response time for client requests to the Kibana instance + */ + v1: { bucket_size: '30 seconds', timeRange: { min: 1551438000000, max: 1551441600000 }, metric: { @@ -1118,9 +1142,12 @@ export const KIBANA_METRICS = { [1551441510000, 12139], [1551441540000, 11966], [1551441570000, 12051], - ], + ] as [number, number][], }, - { + /** + * Variant 2 - Average response time for client requests to the Kibana instance + */ + v2: { bucket_size: '30 seconds', timeRange: { min: 1551438000000, max: 1551441600000 }, metric: { @@ -1256,11 +1283,14 @@ export const KIBANA_METRICS = { [1551441510000, 564.4000244140625], [1551441540000, 561.0740966796875], [1551441570000, 329.6833190917969], - ], + ] as [number, number][], }, - ], - kibana_requests: [ - { + }, + kibana_requests: { + /** + * Variant 1 - Total number of client requests received by the Kibana instance + */ + v1: { bucket_size: '30 seconds', timeRange: { min: 1551438000000, max: 1551441600000 }, metric: { @@ -1395,9 +1425,9 @@ export const KIBANA_METRICS = { [1551441510000, 81], [1551441540000, 58], [1551441570000, 97], - ], + ] as [number, number][], }, - ], + }, }, kibanaSummary: { uuid: '38cd1f5c-fc29-492e-b5b6-34777df7bdf6', diff --git a/packages/charts/src/utils/fast_deep_equal.ts b/packages/charts/src/utils/fast_deep_equal.ts index 055413a44b..63aa3e6fff 100644 --- a/packages/charts/src/utils/fast_deep_equal.ts +++ b/packages/charts/src/utils/fast_deep_equal.ts @@ -73,11 +73,11 @@ export function deepEqual(a: any, b: any, partial = false): boolean { length = keys.length; if (length !== Object.keys(b).length && !partial) return false; - for (i = length; i-- !== 0; ) if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; + for (i = length; i-- !== 0; ) if (!Object.prototype.hasOwnProperty.call(b, keys[i] ?? '')) return false; for (i = length; i-- !== 0; ) { const key = keys[i]; - if (key === '_owner' && a.$$typeof) { + if (!key || (key === '_owner' && a.$$typeof)) { // React-specific: avoid traversing React elements' _owner. // _owner contains circular references // and is not needed when comparing the actual elements (and not their owners) diff --git a/packages/charts/src/utils/text/wrap.ts b/packages/charts/src/utils/text/wrap.ts index f2d318c4ce..3b6f747934 100644 --- a/packages/charts/src/utils/text/wrap.ts +++ b/packages/charts/src/utils/text/wrap.ts @@ -49,7 +49,7 @@ export function wrapText( } lines.push(...multilineText); currentLineWidth = - multilineText.length > 0 ? measure(multilineText[multilineText.length - 1], font, fontSize).width : 0; + multilineText.length > 0 ? measure(multilineText[multilineText.length - 1] ?? '', font, fontSize).width : 0; } else { const lineIndex = lines.length > 0 ? lines.length - 1 : 0; lines[lineIndex] = (lines[lineIndex] ?? '') + segment.segment; @@ -58,7 +58,7 @@ export function wrapText( } if (lines.length > maxLines) { const lastLineMaxLineWidth = maxLineWidth - ellipsisWidth; - const lastLine = clipTextToWidth(lines[maxLines - 1], font, fontSize, lastLineMaxLineWidth, measure); + const lastLine = clipTextToWidth(lines[maxLines - 1] ?? '', font, fontSize, lastLineMaxLineWidth, measure); if (lastLine.length > 0) { lines.splice(maxLines - 1, Infinity, `${lastLine}${ELLIPSIS}`); } else { @@ -83,7 +83,11 @@ function textSegmenter(locale: string[]): (text: string) => { segment: string; i return text .split(' ') .reduce<{ segment: string; index: number; isWordLike?: boolean }[]>((acc, segment, index, array) => { - const currentSegment = { segment, index: index === 0 ? 0 : acc[acc.length - 1].index + 1, isWordLike: true }; + const currentSegment = { + segment, + index: index === 0 ? 0 : (acc[acc.length - 1]?.index ?? 0) + 1, + isWordLike: true, + }; acc.push(currentSegment); // adding space to simulate the same behaviour of the segmenter in firefox if (index < array.length - 1) { diff --git a/packages/charts/tsconfig.nocomments.json b/packages/charts/tsconfig.nocomments.json index d35b77b18e..c1374b2291 100644 --- a/packages/charts/tsconfig.nocomments.json +++ b/packages/charts/tsconfig.nocomments.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig", + "extends": "./tsconfig.src", "compilerOptions": { "removeComments": true, "declaration": false, diff --git a/packages/charts/tsconfig.json b/packages/charts/tsconfig.src.json similarity index 100% rename from packages/charts/tsconfig.json rename to packages/charts/tsconfig.src.json diff --git a/storybook/stories/annotations/lines/3_x_time.story.tsx b/storybook/stories/annotations/lines/3_x_time.story.tsx index 0f14097660..57fe96ab58 100644 --- a/storybook/stories/annotations/lines/3_x_time.story.tsx +++ b/storybook/stories/annotations/lines/3_x_time.story.tsx @@ -76,7 +76,7 @@ export const Example = () => { yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 20)} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, 20)} /> ); diff --git a/storybook/stories/area/10_stacked_same_naming.story.tsx b/storybook/stories/area/10_stacked_same_naming.story.tsx index 851a9ef7ea..a222cd3b9f 100644 --- a/storybook/stories/area/10_stacked_same_naming.story.tsx +++ b/storybook/stories/area/10_stacked_same_naming.story.tsx @@ -27,7 +27,7 @@ export const Example = () => ( /> Number(d).toFixed(2)} /> @@ -39,7 +39,7 @@ export const Example = () => ( xAccessor={0} yAccessors={[1]} stackAccessors={[0]} - data={KIBANA_METRICS.metrics.kibana_os_load[2].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v3.data} /> ( xAccessor={0} yAccessors={[1]} stackAccessors={[0]} - data={KIBANA_METRICS.metrics.kibana_os_load[1].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v2.data} /> ( xAccessor={0} yAccessors={[1]} stackAccessors={[0]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} /> ); diff --git a/storybook/stories/area/11_test_linear.story.tsx b/storybook/stories/area/11_test_linear.story.tsx index 516122de67..1c55b644a8 100644 --- a/storybook/stories/area/11_test_linear.story.tsx +++ b/storybook/stories/area/11_test_linear.story.tsx @@ -32,7 +32,7 @@ export const Example = () => { Number(d).toFixed(2)} /> diff --git a/storybook/stories/area/12_test_time.story.tsx b/storybook/stories/area/12_test_time.story.tsx index c803c0de29..66660371eb 100644 --- a/storybook/stories/area/12_test_time.story.tsx +++ b/storybook/stories/area/12_test_time.story.tsx @@ -36,7 +36,7 @@ export const Example = () => { Number(d).toFixed(2)} /> diff --git a/storybook/stories/area/13_band_area.story.tsx b/storybook/stories/area/13_band_area.story.tsx index 538b307a94..f3ff8caf53 100644 --- a/storybook/stories/area/13_band_area.story.tsx +++ b/storybook/stories/area/13_band_area.story.tsx @@ -29,12 +29,12 @@ const dateFormatter = timeFormatter('HH:mm'); export const Example = () => { const getRandomNumber = getRandomNumberGenerator(); - const data = KIBANA_METRICS.metrics.kibana_os_load[0].data.map((d) => ({ + const data = KIBANA_METRICS.metrics.kibana_os_load.v1.data.map((d) => ({ x: d[0], max: d[1] + 4 + 4 * getRandomNumber(), min: d[1] - 4 - 4 * getRandomNumber(), })); - const lineData = KIBANA_METRICS.metrics.kibana_os_load[0].data.map((d) => [d[0], d[1]]); + const lineData = KIBANA_METRICS.metrics.kibana_os_load.v1.data.map((d) => [d[0], d[1]]); const fit = boolean('fit Y domain', true); const y0AccessorFormat = text('y0AccessorFormat', ''); const y1AccessorFormat = text('y1AccessorFormat', ''); @@ -55,7 +55,7 @@ export const Example = () => { max: NaN, fit, }} - title={KIBANA_METRICS.metrics.kibana_os_load[0].metric.title} + title={KIBANA_METRICS.metrics.kibana_os_load.v1.metric.title} position={Position.Left} tickFormat={(d) => Number(d).toFixed(2)} /> diff --git a/storybook/stories/area/15_stacked_grouped.story.tsx b/storybook/stories/area/15_stacked_grouped.story.tsx index 10fb3dbfe5..a2ca0d2b96 100644 --- a/storybook/stories/area/15_stacked_grouped.story.tsx +++ b/storybook/stories/area/15_stacked_grouped.story.tsx @@ -53,7 +53,7 @@ export const Example = () => { Number(d).toFixed(2)} /> diff --git a/storybook/stories/area/17_negative.story.tsx b/storybook/stories/area/17_negative.story.tsx index 75a1facff7..8aea0ba6b7 100644 --- a/storybook/stories/area/17_negative.story.tsx +++ b/storybook/stories/area/17_negative.story.tsx @@ -17,9 +17,8 @@ import { customKnobs } from '../utils/knobs'; const dateFormatter = timeFormatter('HH:mm'); -const data = KIBANA_METRICS.metrics.kibana_os_load[0].data.map(([x, y]) => { - return [x, -y]; -}); +const data = KIBANA_METRICS.metrics.kibana_os_load.v1.data.map(([x, y]) => [x, -y]); + export const Example = () => { const yScaleType = customKnobs.enum.scaleType('Y scale', ScaleType.Linear, { include: ['Linear', 'Log'] }); diff --git a/storybook/stories/area/18_negative_positive.story.tsx b/storybook/stories/area/18_negative_positive.story.tsx index be393769f6..194d481666 100644 --- a/storybook/stories/area/18_negative_positive.story.tsx +++ b/storybook/stories/area/18_negative_positive.story.tsx @@ -18,7 +18,7 @@ import { customKnobs } from '../utils/knobs'; const dateFormatter = timeFormatter('HH:mm'); export const Example = () => { - const dataset = KIBANA_METRICS.metrics.kibana_os_load[0]; + const dataset = KIBANA_METRICS.metrics.kibana_os_load.v1; const yScaleType = customKnobs.enum.scaleType('Y scale', ScaleType.Linear, { include: ['Linear', 'Log'] }); return ( diff --git a/storybook/stories/area/1_basic.story.tsx b/storybook/stories/area/1_basic.story.tsx index 5928f665af..4fd2f51c3d 100644 --- a/storybook/stories/area/1_basic.story.tsx +++ b/storybook/stories/area/1_basic.story.tsx @@ -14,7 +14,7 @@ import { KIBANA_METRICS } from '@elastic/charts/src/utils/data_samples/test_data import { useBaseTheme } from '../../use_base_theme'; export const Example = () => { - const { data } = KIBANA_METRICS.metrics.kibana_os_load[0]; + const { data } = KIBANA_METRICS.metrics.kibana_os_load.v1; return ( diff --git a/storybook/stories/area/21_with_time_timeslip.story.tsx b/storybook/stories/area/21_with_time_timeslip.story.tsx index 718c429e8f..ce19d42b89 100644 --- a/storybook/stories/area/21_with_time_timeslip.story.tsx +++ b/storybook/stories/area/21_with_time_timeslip.story.tsx @@ -31,7 +31,7 @@ const tooltipDateFormatter = (d: any) => minute: 'numeric', }).format(d); -const data = KIBANA_METRICS.metrics.kibana_os_load[0].data; +const data = KIBANA_METRICS.metrics.kibana_os_load.v1.data; const t0 = data[0][0]; const t1 = data[data.length - 1][0]; diff --git a/storybook/stories/area/2_with_time.story.tsx b/storybook/stories/area/2_with_time.story.tsx index 9f6c8e4dda..33f1594add 100644 --- a/storybook/stories/area/2_with_time.story.tsx +++ b/storybook/stories/area/2_with_time.story.tsx @@ -46,7 +46,7 @@ export const Example = () => ( /> Number(d).toFixed(2)} /> @@ -56,7 +56,7 @@ export const Example = () => ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} /> ); diff --git a/storybook/stories/area/3_with_linear.story.tsx b/storybook/stories/area/3_with_linear.story.tsx index 077fa382da..63006eb956 100644 --- a/storybook/stories/area/3_with_linear.story.tsx +++ b/storybook/stories/area/3_with_linear.story.tsx @@ -15,8 +15,8 @@ import { KIBANA_METRICS } from '@elastic/charts/src/utils/data_samples/test_data import { useBaseTheme } from '../../use_base_theme'; export const Example = () => { - const start = KIBANA_METRICS.metrics.kibana_os_load[0].data[0][0]; - const data = KIBANA_METRICS.metrics.kibana_os_load[0].data + const start = KIBANA_METRICS.metrics.kibana_os_load.v1.data[0][0]; + const data = KIBANA_METRICS.metrics.kibana_os_load.v1.data .slice(0, 21) .map((d) => [(d[0] - start) / 30000, clamp(d[1], 0, 30)]); return ( @@ -32,7 +32,7 @@ export const Example = () => { /> { - const data = KIBANA_METRICS.metrics.kibana_os_load[0].data.map((d) => (d[1] < 7 ? [d[0], null] : [d[0], d[1] - 10])); + const data = KIBANA_METRICS.metrics.kibana_os_load.v1.data.map((d) => (d[1] < 7 ? [d[0], null] : [d[0], d[1] - 10])); return ( Number(d).toFixed(2)} /> diff --git a/storybook/stories/area/5_with_4_axes.story.tsx b/storybook/stories/area/5_with_4_axes.story.tsx index 865aac7d6f..b9c166d641 100644 --- a/storybook/stories/area/5_with_4_axes.story.tsx +++ b/storybook/stories/area/5_with_4_axes.story.tsx @@ -27,25 +27,25 @@ export const Example = () => ( /> `${Number(d).toFixed(0)}%`} /> `${Number(d).toFixed(0)} %`} /> ); diff --git a/storybook/stories/area/6_with_axis_and_legend.story.tsx b/storybook/stories/area/6_with_axis_and_legend.story.tsx index e1a83797fd..75f6a2a934 100644 --- a/storybook/stories/area/6_with_axis_and_legend.story.tsx +++ b/storybook/stories/area/6_with_axis_and_legend.story.tsx @@ -27,18 +27,18 @@ export const Example = () => ( /> Number(d).toFixed(2)} /> ); diff --git a/storybook/stories/area/7_stacked.story.tsx b/storybook/stories/area/7_stacked.story.tsx index 5d738f3714..04b7259e82 100644 --- a/storybook/stories/area/7_stacked.story.tsx +++ b/storybook/stories/area/7_stacked.story.tsx @@ -16,17 +16,17 @@ import { useBaseTheme } from '../../use_base_theme'; const dateFormatter = timeFormatter('HH:mm'); export const Example = () => { - const data1 = KIBANA_METRICS.metrics.kibana_os_load[0].data.map((d) => [ + const data1 = KIBANA_METRICS.metrics.kibana_os_load.v1.data.map((d) => [ ...d, - KIBANA_METRICS.metrics.kibana_os_load[0].metric.label, + KIBANA_METRICS.metrics.kibana_os_load.v1.metric.label, ]); - const data2 = KIBANA_METRICS.metrics.kibana_os_load[1].data.map((d) => [ + const data2 = KIBANA_METRICS.metrics.kibana_os_load.v2.data.map((d) => [ ...d, - KIBANA_METRICS.metrics.kibana_os_load[1].metric.label, + KIBANA_METRICS.metrics.kibana_os_load.v2.metric.label, ]); - const data3 = KIBANA_METRICS.metrics.kibana_os_load[2].data.map((d) => [ + const data3 = KIBANA_METRICS.metrics.kibana_os_load.v3.data.map((d) => [ ...d, - KIBANA_METRICS.metrics.kibana_os_load[2].metric.label, + KIBANA_METRICS.metrics.kibana_os_load.v3.metric.label, ]); const allMetrics = [...data3, ...data2, ...data1]; return ( @@ -41,12 +41,12 @@ export const Example = () => { /> Number(d).toFixed(2)} /> ( /> Number(d).toFixed(2)} /> ); diff --git a/storybook/stories/axes/12_duplicate_ticks.story.tsx b/storybook/stories/axes/12_duplicate_ticks.story.tsx index 7037657760..afff858576 100644 --- a/storybook/stories/axes/12_duplicate_ticks.story.tsx +++ b/storybook/stories/axes/12_duplicate_ticks.story.tsx @@ -46,7 +46,7 @@ export const Example = () => { `${Number(d).toFixed(1)}`} /> diff --git a/storybook/stories/axes/13_label_formatting.story.tsx b/storybook/stories/axes/13_label_formatting.story.tsx index 2b6e4b4a3d..090ca137b9 100644 --- a/storybook/stories/axes/13_label_formatting.story.tsx +++ b/storybook/stories/axes/13_label_formatting.story.tsx @@ -20,8 +20,8 @@ export const Example = () => { const labelFormatBottom = text('labelFormat bottom', '0.0'); const tickFormatLeft = text('tickFormat left', '$ 0,0[.]00'); const labelFormatLeft = text('labelFormat left', '$ 0,0'); - const start = KIBANA_METRICS.metrics.kibana_os_load[0].data[0][0]; - const data = KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 20).map((d) => [(d[0] - start) / 30000, d[1]]); + const start = KIBANA_METRICS.metrics.kibana_os_load.v1.data[0][0]; + const data = KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, 20).map((d) => [(d[0] - start) / 30000, d[1]]); return ( diff --git a/storybook/stories/axes/14_duplicate_ticks_2.story.tsx b/storybook/stories/axes/14_duplicate_ticks_2.story.tsx index b6e1e1649f..75cff4fd35 100644 --- a/storybook/stories/axes/14_duplicate_ticks_2.story.tsx +++ b/storybook/stories/axes/14_duplicate_ticks_2.story.tsx @@ -46,7 +46,7 @@ export const Example = () => { `${Number(d).toFixed(1)}`} labelFormat={(d) => `${Number(d).toFixed(0)}`} diff --git a/storybook/stories/axes/1_basic.story.tsx b/storybook/stories/axes/1_basic.story.tsx index ccf3ffd850..9a0ef8ecad 100644 --- a/storybook/stories/axes/1_basic.story.tsx +++ b/storybook/stories/axes/1_basic.story.tsx @@ -35,7 +35,7 @@ export const Example = () => { }), }, }; - const data = KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 60); + const data = KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, 60); return ( diff --git a/storybook/stories/bar/15_time_clustered.story.tsx b/storybook/stories/bar/15_time_clustered.story.tsx index 12b015a7c8..65ea469447 100644 --- a/storybook/stories/bar/15_time_clustered.story.tsx +++ b/storybook/stories/bar/15_time_clustered.story.tsx @@ -39,28 +39,28 @@ export const Example = () => { Number(d).toFixed(2)} /> ); diff --git a/storybook/stories/bar/17_time_stacked.story.tsx b/storybook/stories/bar/17_time_stacked.story.tsx index 4de60862c5..ea8434a3e0 100644 --- a/storybook/stories/bar/17_time_stacked.story.tsx +++ b/storybook/stories/bar/17_time_stacked.story.tsx @@ -55,31 +55,31 @@ export const Example = () => { Number(d).toFixed(2)} /> ); diff --git a/storybook/stories/bar/33_band_bar.story.tsx b/storybook/stories/bar/33_band_bar.story.tsx index 774e5bf2f0..1c1aba6694 100644 --- a/storybook/stories/bar/33_band_bar.story.tsx +++ b/storybook/stories/bar/33_band_bar.story.tsx @@ -19,12 +19,12 @@ const dateFormatter = timeFormatter('HH:mm:ss'); export const Example = () => { const getRandomNumber = getRandomNumberGenerator(); - const data = KIBANA_METRICS.metrics.kibana_os_load[0].data.map((d: any) => ({ + const data = KIBANA_METRICS.metrics.kibana_os_load.v1.data.map((d: any) => ({ x: d[0], max: d[1] + 4 + 4 * getRandomNumber(), min: d[1] - 4 - 4 * getRandomNumber(), })); - const lineData = KIBANA_METRICS.metrics.kibana_os_load[0].data.map((d: any) => [d[0], d[1]]); + const lineData = KIBANA_METRICS.metrics.kibana_os_load.v1.data.map((d: any) => [d[0], d[1]]); const fit = boolean('fit Y domain', true); const useFunctions = boolean('use fn accessors', false); return ( @@ -44,7 +44,7 @@ export const Example = () => { max: NaN, fit, }} - title={KIBANA_METRICS.metrics.kibana_os_load[0].metric.title} + title={KIBANA_METRICS.metrics.kibana_os_load.v1.metric.title} position={Position.Left} tickFormat={(d: any) => Number(d).toFixed(2)} /> diff --git a/storybook/stories/bar/34_test_linear.story.tsx b/storybook/stories/bar/34_test_linear.story.tsx index 28d53d6048..6a4cb7ef7a 100644 --- a/storybook/stories/bar/34_test_linear.story.tsx +++ b/storybook/stories/bar/34_test_linear.story.tsx @@ -32,7 +32,7 @@ export const Example = () => { Number(d).toFixed(2)} /> diff --git a/storybook/stories/bar/35_test_time.story.tsx b/storybook/stories/bar/35_test_time.story.tsx index 3a1c055c0d..df8e901d4c 100644 --- a/storybook/stories/bar/35_test_time.story.tsx +++ b/storybook/stories/bar/35_test_time.story.tsx @@ -36,7 +36,7 @@ export const Example = () => { Number(d).toFixed(2)} /> diff --git a/storybook/stories/bar/36_test_linear_clustered.story.tsx b/storybook/stories/bar/36_test_linear_clustered.story.tsx index 6907a52bf5..878115a096 100644 --- a/storybook/stories/bar/36_test_linear_clustered.story.tsx +++ b/storybook/stories/bar/36_test_linear_clustered.story.tsx @@ -32,7 +32,7 @@ export const Example = () => { Number(d).toFixed(2)} /> diff --git a/storybook/stories/bar/37_test_time_clustered.story.tsx b/storybook/stories/bar/37_test_time_clustered.story.tsx index 6ec814136f..20d09c6875 100644 --- a/storybook/stories/bar/37_test_time_clustered.story.tsx +++ b/storybook/stories/bar/37_test_time_clustered.story.tsx @@ -36,7 +36,7 @@ export const Example = () => { Number(d).toFixed(2)} /> diff --git a/storybook/stories/bar/38_test_clustered_null_bars.story.tsx b/storybook/stories/bar/38_test_clustered_null_bars.story.tsx index a742bf56c0..b83cdd86d6 100644 --- a/storybook/stories/bar/38_test_clustered_null_bars.story.tsx +++ b/storybook/stories/bar/38_test_clustered_null_bars.story.tsx @@ -31,7 +31,7 @@ export const Example = () => { Number(d).toFixed(2)} /> diff --git a/storybook/stories/bar/39_test_stacked_null.story.tsx b/storybook/stories/bar/39_test_stacked_null.story.tsx index 87f0cebc5d..33d80443c5 100644 --- a/storybook/stories/bar/39_test_stacked_null.story.tsx +++ b/storybook/stories/bar/39_test_stacked_null.story.tsx @@ -33,7 +33,7 @@ export const Example = () => { Number(d).toFixed(2)} /> diff --git a/storybook/stories/bar/44_test_single_histogram.story.tsx b/storybook/stories/bar/44_test_single_histogram.story.tsx index b2b04d606e..dd3ed6da42 100644 --- a/storybook/stories/bar/44_test_single_histogram.story.tsx +++ b/storybook/stories/bar/44_test_single_histogram.story.tsx @@ -35,8 +35,8 @@ export const Example = () => { const binWidthMs = 60000; const xDomain = { - min: KIBANA_METRICS.metrics.kibana_os_load[0].data[0][0] - (oddDomain ? 217839 : 0), - max: KIBANA_METRICS.metrics.kibana_os_load[0].data[0][0] + (oddDomain ? 200000 : 0 ?? binWidthMs - 1), + min: KIBANA_METRICS.metrics.kibana_os_load.v1.data[0][0] - (oddDomain ? 217839 : 0), + max: KIBANA_METRICS.metrics.kibana_os_load.v1.data[0][0] + (oddDomain ? 200000 : 0 ?? binWidthMs - 1), minInterval: binWidthMs, }; @@ -61,7 +61,7 @@ export const Example = () => { : {} } /> - + { yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 1)} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, 1)} /> ); diff --git a/storybook/stories/bar/47_stacked_only_grouped.story.tsx b/storybook/stories/bar/47_stacked_only_grouped.story.tsx index 19eef30e95..62657f44ad 100644 --- a/storybook/stories/bar/47_stacked_only_grouped.story.tsx +++ b/storybook/stories/bar/47_stacked_only_grouped.story.tsx @@ -75,7 +75,7 @@ export const Example = () => { Number(d).toFixed(2)} domain={{ min: 0, max: 15 }} @@ -83,7 +83,7 @@ export const Example = () => { Number(d).toFixed(2)} hide diff --git a/storybook/stories/bar/7_with_time_xaxis.story.tsx b/storybook/stories/bar/7_with_time_xaxis.story.tsx index a3409a5866..64ffbeae4e 100644 --- a/storybook/stories/bar/7_with_time_xaxis.story.tsx +++ b/storybook/stories/bar/7_with_time_xaxis.story.tsx @@ -44,7 +44,7 @@ export const Example = () => { yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} /> ); diff --git a/storybook/stories/components/tooltip/13_flamegraph.story.tsx b/storybook/stories/components/tooltip/13_flamegraph.story.tsx index e39bd77a59..074022f5f5 100644 --- a/storybook/stories/components/tooltip/13_flamegraph.story.tsx +++ b/storybook/stories/components/tooltip/13_flamegraph.story.tsx @@ -19,6 +19,7 @@ import { PartialTheme, FlameGlobalControl, FlameNodeControl, + ColumnarViewModel, } from '@elastic/charts'; import columnarMock from '@elastic/charts/src/mocks/hierarchical/cpu_profile_tree_mock_columnar.json'; import { getRandomNumberGenerator } from '@elastic/charts/src/mocks/utils'; @@ -45,7 +46,7 @@ const paletteColorBrewerCat12 = [ [255, 237, 111], ]; -const columnarData = { +const columnarData: ColumnarViewModel = { label: columnarMock.label.map((index: number) => columnarMock.dictionary[index]), // reversing the dictionary encoding value: new Float64Array(columnarMock.value), // color: new Float32Array((columnarMock.color.match(/.{2}/g) ?? []).map((hex: string) => Number.parseInt(hex, 16) / 255)), diff --git a/storybook/stories/goal/10_band_in_band.story.tsx b/storybook/stories/goal/10_band_in_band.story.tsx index 443455055b..a60a08a584 100644 --- a/storybook/stories/goal/10_band_in_band.story.tsx +++ b/storybook/stories/goal/10_band_in_band.story.tsx @@ -12,17 +12,15 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 225: 'gray', 300: 'rgb(232,232,232)', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -37,7 +35,7 @@ export const Example = () => ( domain={{ min: 0, max: 300 }} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="225" diff --git a/storybook/stories/goal/11_gaps.story.tsx b/storybook/stories/goal/11_gaps.story.tsx index a2d1f9a8b9..84e49658a4 100644 --- a/storybook/stories/goal/11_gaps.story.tsx +++ b/storybook/stories/goal/11_gaps.story.tsx @@ -13,20 +13,18 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 199: 'rgba(255,0,0,0.5)', 201: 'white', 249: 'lightgrey', 251: 'white', 300: 'rgba(0,255,0,0.5)', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => { const showTarget = boolean('show target', true); @@ -45,7 +43,7 @@ export const Example = () => { bands={[199, 201, 249, 251, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="280" diff --git a/storybook/stories/goal/12_range.story.tsx b/storybook/stories/goal/12_range.story.tsx index b432133d7c..4b41cff928 100644 --- a/storybook/stories/goal/12_range.story.tsx +++ b/storybook/stories/goal/12_range.story.tsx @@ -12,18 +12,16 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 215: 'rgb(232,232,232)', 235: 'gray', 300: 'rgb(232,232,232)', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -38,7 +36,7 @@ export const Example = () => ( domain={{ min: 0, max: 300 }} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="225" diff --git a/storybook/stories/goal/13_confidence_level.story.tsx b/storybook/stories/goal/13_confidence_level.story.tsx index 92909a0f11..56887ff943 100644 --- a/storybook/stories/goal/13_confidence_level.story.tsx +++ b/storybook/stories/goal/13_confidence_level.story.tsx @@ -12,12 +12,12 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 210: 'rgb(232,232,232)', 218: '#66c2a4', 224: '#2ca25f', @@ -25,9 +25,7 @@ const colorMap: { [k: number]: Color } = { 235: '#2ca25f', 243: '#66c2a4', 300: 'rgb(232,232,232)', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -42,7 +40,7 @@ export const Example = () => ( bands={[210, 218, 224, 229, 235, 243, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="" labelMinor="" centralMajor="226.5" diff --git a/storybook/stories/goal/14_one_third.story.tsx b/storybook/stories/goal/14_one_third.story.tsx index 4be3905283..589a755e98 100644 --- a/storybook/stories/goal/14_one_third.story.tsx +++ b/storybook/stories/goal/14_one_third.story.tsx @@ -12,18 +12,16 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: '#fc8d62', 250: 'lightgrey', 300: '#66c2a5', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -38,7 +36,7 @@ export const Example = () => ( bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="" labelMinor="" centralMajor="280 MB/s" diff --git a/storybook/stories/goal/15_half_circle.story.tsx b/storybook/stories/goal/15_half_circle.story.tsx index 6fe7ff39dd..d0b70cff1c 100644 --- a/storybook/stories/goal/15_half_circle.story.tsx +++ b/storybook/stories/goal/15_half_circle.story.tsx @@ -12,18 +12,16 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: '#fc8d62', 250: 'lightgrey', 300: '#66c2a5', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -38,7 +36,7 @@ export const Example = () => ( bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="" labelMinor="" centralMajor="280 MB/s" diff --git a/storybook/stories/goal/16_two_thirds.story.tsx b/storybook/stories/goal/16_two_thirds.story.tsx index 642fdc2363..16d5eadb26 100644 --- a/storybook/stories/goal/16_two_thirds.story.tsx +++ b/storybook/stories/goal/16_two_thirds.story.tsx @@ -12,18 +12,16 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: '#fc8d62', 250: 'lightgrey', 300: '#66c2a5', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -38,7 +36,7 @@ export const Example = () => ( bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="" labelMinor="" centralMajor="280 MB/s" diff --git a/storybook/stories/goal/17_three_quarters.story.tsx b/storybook/stories/goal/17_three_quarters.story.tsx index 7f477704a6..1c300e7177 100644 --- a/storybook/stories/goal/17_three_quarters.story.tsx +++ b/storybook/stories/goal/17_three_quarters.story.tsx @@ -12,18 +12,16 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: '#fc8d62', 250: 'lightgrey', 300: '#66c2a5', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -38,7 +36,7 @@ export const Example = () => ( bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="" labelMinor="" centralMajor="280 MB/s" diff --git a/storybook/stories/goal/17_total_circle.story.tsx b/storybook/stories/goal/17_total_circle.story.tsx index 5c0f883176..0f5260dba6 100644 --- a/storybook/stories/goal/17_total_circle.story.tsx +++ b/storybook/stories/goal/17_total_circle.story.tsx @@ -12,16 +12,14 @@ import React from 'react'; import { Chart, Goal, Settings } from '@elastic/charts'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: '#fc8d62', 250: 'lightgrey', 300: '#66c2a5', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => { const start = number('startAngle (π)', 1.5, { min: -2, max: 2, step: 1 / 8 }); @@ -39,7 +37,7 @@ export const Example = () => { bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 265, 280]} tickValueFormatter={({ value }) => String(value)} - bandFillColor={({ value }) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="" labelMinor="" centralMajor="280 MB/s" diff --git a/storybook/stories/goal/17_very_small_gap.story.tsx b/storybook/stories/goal/17_very_small_gap.story.tsx index caf5402f34..58f333e588 100644 --- a/storybook/stories/goal/17_very_small_gap.story.tsx +++ b/storybook/stories/goal/17_very_small_gap.story.tsx @@ -12,18 +12,16 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: '#fc8d62', 250: 'lightgrey', 300: '#66c2a5', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -38,7 +36,7 @@ export const Example = () => ( bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 265, 280]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="" labelMinor="" centralMajor="280 MB/s" diff --git a/storybook/stories/goal/18_side_gauge.story.tsx b/storybook/stories/goal/18_side_gauge.story.tsx index b4a95abe1f..776286a211 100644 --- a/storybook/stories/goal/18_side_gauge.story.tsx +++ b/storybook/stories/goal/18_side_gauge.story.tsx @@ -8,20 +8,19 @@ import React from 'react'; -import { Chart, Goal, Color, BandFillColorAccessorInput, Settings } from '@elastic/charts'; +import { Chart, Goal, BandFillColorAccessorInput, Settings } from '@elastic/charts'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: '#fc8d62', 250: 'lightgrey', 300: '#66c2a5', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -36,7 +35,7 @@ export const Example = () => ( bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="" labelMinor="" centralMajor="280 MB/s" diff --git a/storybook/stories/goal/18_side_gauge_inverted_angle_relation.story.tsx b/storybook/stories/goal/18_side_gauge_inverted_angle_relation.story.tsx index 7bda47aeab..4455c1b43c 100644 --- a/storybook/stories/goal/18_side_gauge_inverted_angle_relation.story.tsx +++ b/storybook/stories/goal/18_side_gauge_inverted_angle_relation.story.tsx @@ -8,20 +8,19 @@ import React from 'react'; -import { Chart, Goal, Color, BandFillColorAccessorInput, Settings } from '@elastic/charts'; +import { Chart, Goal, BandFillColorAccessorInput, Settings } from '@elastic/charts'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: '#fc8d62', 250: 'lightgrey', 300: '#66c2a5', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -36,7 +35,7 @@ export const Example = () => ( bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="" labelMinor="" centralMajor="280 MB/s" diff --git a/storybook/stories/goal/19_horizontal_negative.story.tsx b/storybook/stories/goal/19_horizontal_negative.story.tsx index 41a4e1c881..59d3e0fa3d 100644 --- a/storybook/stories/goal/19_horizontal_negative.story.tsx +++ b/storybook/stories/goal/19_horizontal_negative.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -21,13 +21,11 @@ const q3 = 255 - 255 * 0.1; const subtype = GoalSubtype.HorizontalBullet; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ '-200': `rgb(${q1},${q1},${q1})`, '-250': `rgb(${q2},${q2},${q2})`, '-300': `rgb(${q3},${q3},${q3})`, -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -42,7 +40,7 @@ export const Example = () => ( bands={[-200, -250, -300]} ticks={[0, -50, -100, -150, -200, -250, -300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="-280" diff --git a/storybook/stories/goal/20_vertical_negative.story.tsx b/storybook/stories/goal/20_vertical_negative.story.tsx index 98a57e0d27..00fc726a5b 100644 --- a/storybook/stories/goal/20_vertical_negative.story.tsx +++ b/storybook/stories/goal/20_vertical_negative.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -21,13 +21,11 @@ const q3 = 255 - 255 * 0.1; const subtype = GoalSubtype.VerticalBullet; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ '-200': `rgb(${q1},${q1},${q1})`, '-250': `rgb(${q2},${q2},${q2})`, '-300': `rgb(${q3},${q3},${q3})`, -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -42,7 +40,7 @@ export const Example = () => ( bands={[-200, -250, -300]} ticks={[0, -50, -100, -150, -200, -250, -300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="-280" diff --git a/storybook/stories/goal/21_goal_negative.story.tsx b/storybook/stories/goal/21_goal_negative.story.tsx index f30db1a373..646035d18a 100644 --- a/storybook/stories/goal/21_goal_negative.story.tsx +++ b/storybook/stories/goal/21_goal_negative.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -21,13 +21,11 @@ const q3 = 255 - 255 * 0.1; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ '-200': `rgb(${q1},${q1},${q1})`, '-250': `rgb(${q2},${q2},${q2})`, '-300': `rgb(${q3},${q3},${q3})`, -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -42,7 +40,7 @@ export const Example = () => ( bands={[-200, -250, -300]} ticks={[0, -50, -100, -150, -200, -250, -300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="-280" diff --git a/storybook/stories/goal/22_horizontal_plusminus.story.tsx b/storybook/stories/goal/22_horizontal_plusminus.story.tsx index 511d34e687..b66000f4ca 100644 --- a/storybook/stories/goal/22_horizontal_plusminus.story.tsx +++ b/storybook/stories/goal/22_horizontal_plusminus.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -21,15 +21,13 @@ const q3 = 255 - 255 * 0.1; const subtype = GoalSubtype.HorizontalBullet; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ '-100': 'lightcoral', 0: 'indianred', 200: `rgb(${q1},${q1},${q1})`, 250: `rgb(${q2},${q2},${q2})`, 300: `rgb(${q3},${q3},${q3})`, -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -44,7 +42,7 @@ export const Example = () => ( bands={[-200, -100, 0, 200, 250, 300]} ticks={[-200, -100, 0, 100, 200, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="-80" diff --git a/storybook/stories/goal/23_vertical_plusminus.story.tsx b/storybook/stories/goal/23_vertical_plusminus.story.tsx index f8257d342c..32a190e511 100644 --- a/storybook/stories/goal/23_vertical_plusminus.story.tsx +++ b/storybook/stories/goal/23_vertical_plusminus.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -21,15 +21,13 @@ const q3 = 255 - 255 * 0.1; const subtype = GoalSubtype.VerticalBullet; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ '-100': 'lightcoral', 0: 'indianred', 200: `rgb(${q1},${q1},${q1})`, 250: `rgb(${q2},${q2},${q2})`, 300: `rgb(${q3},${q3},${q3})`, -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -44,7 +42,7 @@ export const Example = () => ( bands={[-200, -100, 0, 200, 250, 300]} ticks={[-200, -100, 0, 100, 200, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="-80" diff --git a/storybook/stories/goal/24_goal_plusminus.story.tsx b/storybook/stories/goal/24_goal_plusminus.story.tsx index b2b4d6f650..729be667b1 100644 --- a/storybook/stories/goal/24_goal_plusminus.story.tsx +++ b/storybook/stories/goal/24_goal_plusminus.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -21,15 +21,13 @@ const q3 = 255 - 255 * 0.1; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ '-50': 'lightcoral', 0: 'indianred', 200: `rgb(${q1},${q1},${q1})`, 250: `rgb(${q2},${q2},${q2})`, 300: `rgb(${q3},${q3},${q3})`, -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -44,7 +42,7 @@ export const Example = () => ( bands={[-100, -50, 0, 200, 250, 300]} ticks={[-100, 0, 100, 200, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="-80" diff --git a/storybook/stories/goal/25_goal_semantic.story.tsx b/storybook/stories/goal/25_goal_semantic.story.tsx index fec2b5c01e..218afa2e7a 100644 --- a/storybook/stories/goal/25_goal_semantic.story.tsx +++ b/storybook/stories/goal/25_goal_semantic.story.tsx @@ -11,6 +11,8 @@ import React from 'react'; import { Chart, Goal, Color, BandFillColorAccessorInput } from '@elastic/charts'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; +import { getBandFillColorFn } from '../utils/utils'; + const subtype = GoalSubtype.Goal; export const Example = () => { @@ -29,7 +31,7 @@ export const Example = () => { return acc; }, {}); - const bandFillColor = (x: number): Color => colorMap[x]; + const getBandFillColor = getBandFillColorFn(colorMap); return ( @@ -43,7 +45,7 @@ export const Example = () => { bands={bands} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="170" diff --git a/storybook/stories/goal/2_gauge_with_target.story.tsx b/storybook/stories/goal/2_gauge_with_target.story.tsx index 16d30d98e5..b6877f916e 100644 --- a/storybook/stories/goal/2_gauge_with_target.story.tsx +++ b/storybook/stories/goal/2_gauge_with_target.story.tsx @@ -15,6 +15,7 @@ import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/co import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; @@ -38,7 +39,7 @@ export const Example = () => { return acc; }, {}); - const bandFillColor = (x: number): Color => colorMap[x]; + const getBandFillColor = getBandFillColorFn(colorMap); const angleStart = number('angleStart (n * π/8)', 8, { @@ -68,7 +69,7 @@ export const Example = () => { ticks={ticks} domain={{ min: 0, max: 300 }} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={useColors ? ({ value }: BandFillColorAccessorInput) => bandFillColor(value) : undefined} + bandFillColor={useColors ? getBandFillColor : undefined} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor={() => `$${actual}k USD`} diff --git a/storybook/stories/goal/3_horizontal_bullet.story.tsx b/storybook/stories/goal/3_horizontal_bullet.story.tsx index 69382f66ed..bc487c7bbc 100644 --- a/storybook/stories/goal/3_horizontal_bullet.story.tsx +++ b/storybook/stories/goal/3_horizontal_bullet.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -21,13 +21,11 @@ const q3 = 255 - 255 * 0.1; const subtype = GoalSubtype.HorizontalBullet; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: `rgb(${q1},${q1},${q1})`, 250: `rgb(${q2},${q2},${q2})`, 300: `rgb(${q3},${q3},${q3})`, -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -42,7 +40,7 @@ export const Example = () => ( bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="280" diff --git a/storybook/stories/goal/4_vertical_bullet.story.tsx b/storybook/stories/goal/4_vertical_bullet.story.tsx index ec633f6177..b77c819877 100644 --- a/storybook/stories/goal/4_vertical_bullet.story.tsx +++ b/storybook/stories/goal/4_vertical_bullet.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -21,13 +21,11 @@ const q3 = 255 - 255 * 0.1; const subtype = GoalSubtype.VerticalBullet; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: `rgb(${q1},${q1},${q1})`, 250: `rgb(${q2},${q2},${q2})`, 300: `rgb(${q3},${q3},${q3})`, -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -42,7 +40,7 @@ export const Example = () => ( bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="280" diff --git a/storybook/stories/goal/5_minimal.story.tsx b/storybook/stories/goal/5_minimal.story.tsx index bc64cc0a2d..459ee57fc4 100644 --- a/storybook/stories/goal/5_minimal.story.tsx +++ b/storybook/stories/goal/5_minimal.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -23,12 +23,11 @@ const subtype = GoalSubtype.Goal; const colorful = subtype === 'goal'; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: colorful ? 'rgba(255,0,0,0.5)' : `rgb(${q1},${q1},${q1})`, 250: colorful ? 'yellow' : `rgb(${q2},${q2},${q2})`, 300: colorful ? 'rgba(0,255,0,0.5)' : `rgb(${q3},${q3},${q3})`, -}; -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -43,7 +42,7 @@ export const Example = () => ( bands={[]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="280" diff --git a/storybook/stories/goal/6_minimal_horizontal.story.tsx b/storybook/stories/goal/6_minimal_horizontal.story.tsx index 3a9ec2df68..64b5debc81 100644 --- a/storybook/stories/goal/6_minimal_horizontal.story.tsx +++ b/storybook/stories/goal/6_minimal_horizontal.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -21,13 +21,11 @@ const q3 = 255 - 255 * 0.1; const subtype = GoalSubtype.HorizontalBullet; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: `rgb(${q1},${q1},${q1})`, 250: `rgb(${q2},${q2},${q2})`, 300: `rgb(${q3},${q3},${q3})`, -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -42,7 +40,7 @@ export const Example = () => ( bands={[300]} ticks={[0, 100, 200, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="280" diff --git a/storybook/stories/goal/7_horizontal_bar.story.tsx b/storybook/stories/goal/7_horizontal_bar.story.tsx index febbacebf9..6005b4fbd0 100644 --- a/storybook/stories/goal/7_horizontal_bar.story.tsx +++ b/storybook/stories/goal/7_horizontal_bar.story.tsx @@ -12,8 +12,8 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const q1 = 255 - 255 * 0.4; const q2 = 255 - 255 * 0.25; @@ -21,13 +21,11 @@ const q3 = 255 - 255 * 0.1; const subtype = GoalSubtype.HorizontalBullet; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: `rgb(${q1},${q1},${q1})`, 250: `rgb(${q2},${q2},${q2})`, 300: `rgb(${q3},${q3},${q3})`, -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -42,7 +40,7 @@ export const Example = () => ( bands={[]} ticks={[0, 100, 200, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="280" diff --git a/storybook/stories/goal/8_irregular_ticks.story.tsx b/storybook/stories/goal/8_irregular_ticks.story.tsx index da4511e1c8..324db45ad2 100644 --- a/storybook/stories/goal/8_irregular_ticks.story.tsx +++ b/storybook/stories/goal/8_irregular_ticks.story.tsx @@ -12,18 +12,16 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 200: 'rgba(255,0,0,0.5)', 250: 'yellow', 300: 'rgba(0,255,0,0.5)', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -38,7 +36,7 @@ export const Example = () => ( bands={[200, 250, 300]} ticks={[0, 100, 200, 250, 260, 270, 280, 290, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="280" diff --git a/storybook/stories/goal/9_minimal_band.story.tsx b/storybook/stories/goal/9_minimal_band.story.tsx index adda20be2d..08d8270f2b 100644 --- a/storybook/stories/goal/9_minimal_band.story.tsx +++ b/storybook/stories/goal/9_minimal_band.story.tsx @@ -12,16 +12,14 @@ import { Chart, Goal, Settings } from '@elastic/charts'; import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; -import { Color } from '../../../packages/charts/src/common/colors'; import { useBaseTheme } from '../../use_base_theme'; +import { getBandFillColorFn } from '../utils/utils'; const subtype = GoalSubtype.Goal; -const colorMap: { [k: number]: Color } = { +const getBandFillColor = getBandFillColorFn({ 300: 'rgb(232,232,232)', -}; - -const bandFillColor = (x: number): Color => colorMap[x]; +}); export const Example = () => ( @@ -36,7 +34,7 @@ export const Example = () => ( bands={[300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " centralMajor="225" diff --git a/storybook/stories/interactions/14_crosshair_time.story.tsx b/storybook/stories/interactions/14_crosshair_time.story.tsx index 85efffbec5..f92908c353 100644 --- a/storybook/stories/interactions/14_crosshair_time.story.tsx +++ b/storybook/stories/interactions/14_crosshair_time.story.tsx @@ -74,7 +74,7 @@ export const Example = () => { xAccessor={0} yAccessors={[1]} stackAccessors={[0]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 20)} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, 20)} /> { xAccessor={0} yAccessors={[1]} stackAccessors={[0]} - data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 20)} + data={KIBANA_METRICS.metrics.kibana_os_load.v2.data.slice(0, 20)} /> )} @@ -93,7 +93,7 @@ export const Example = () => { yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[2].data.slice(0, 20)} + data={KIBANA_METRICS.metrics.kibana_os_load.v3.data.slice(0, 20)} /> ); diff --git a/storybook/stories/interactions/16_cursor_update_action.story.tsx b/storybook/stories/interactions/16_cursor_update_action.story.tsx index 1562222b4f..a42533b680 100644 --- a/storybook/stories/interactions/16_cursor_update_action.story.tsx +++ b/storybook/stories/interactions/16_cursor_update_action.story.tsx @@ -64,9 +64,9 @@ export const Example = () => { ref2.current.dispatchExternalPointerEvent(event); } }; - const { data } = KIBANA_METRICS.metrics.kibana_os_load[0]; - const data1 = KIBANA_METRICS.metrics.kibana_os_load[0].data; - const data2 = KIBANA_METRICS.metrics.kibana_os_load[1].data; + const { data } = KIBANA_METRICS.metrics.kibana_os_load.v1; + const data1 = KIBANA_METRICS.metrics.kibana_os_load.v1.data; + const data2 = KIBANA_METRICS.metrics.kibana_os_load.v2.data; const group1 = 'Top Chart'; const group2 = 'Bottom Chart'; diff --git a/storybook/stories/interactions/17_png_export.story.tsx b/storybook/stories/interactions/17_png_export.story.tsx index 34c8897a35..1970d3d5a7 100644 --- a/storybook/stories/interactions/17_png_export.story.tsx +++ b/storybook/stories/interactions/17_png_export.story.tsx @@ -21,7 +21,6 @@ import { Datum, Goal, ChartType, - Color, defaultPartitionValueFormatter, BandFillColorAccessorInput, } from '@elastic/charts'; @@ -30,7 +29,7 @@ import { mocks } from '@elastic/charts/src/mocks/hierarchical'; import { KIBANA_METRICS } from '@elastic/charts/src/utils/data_samples/test_dataset_kibana'; import { useBaseTheme } from '../../use_base_theme'; -import { productLookup, indexInterpolatedFillColor, interpolatorCET2s } from '../utils/utils'; +import { productLookup, indexInterpolatedFillColor, interpolatorCET2s, getBandFillColorFn } from '../utils/utils'; export const Example = () => { /** @@ -98,7 +97,7 @@ function renderPartitionChart() { } function renderXYAxisChart() { - const data = KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 100); + const data = KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, 100); return ( <> colorMap[x]; + }); return ( String(value)} - bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} + bandFillColor={getBandFillColor} labelMajor="" labelMinor="" centralMajor="280 MB/s" diff --git a/storybook/stories/interactions/18_null_values.story.tsx b/storybook/stories/interactions/18_null_values.story.tsx index de1a673b23..17d8a27f32 100644 --- a/storybook/stories/interactions/18_null_values.story.tsx +++ b/storybook/stories/interactions/18_null_values.story.tsx @@ -62,9 +62,9 @@ export const Example = () => { ref2.current.dispatchExternalPointerEvent(event); } }; - const { data } = KIBANA_METRICS.metrics.kibana_os_load[0]; - const data1 = KIBANA_METRICS.metrics.kibana_os_load[0].data; - const data2 = KIBANA_METRICS.metrics.kibana_os_load[1].data; + const { data } = KIBANA_METRICS.metrics.kibana_os_load.v1; + const data1 = KIBANA_METRICS.metrics.kibana_os_load.v1.data; + const data2 = KIBANA_METRICS.metrics.kibana_os_load.v2.data; const TopSeries = getSeriesKnob(); const BottomSeries = getSeriesKnob(); diff --git a/storybook/stories/interactions/19_multi_chart_cursor_sync.story.tsx b/storybook/stories/interactions/19_multi_chart_cursor_sync.story.tsx index 831de052f0..bd0767f27d 100644 --- a/storybook/stories/interactions/19_multi_chart_cursor_sync.story.tsx +++ b/storybook/stories/interactions/19_multi_chart_cursor_sync.story.tsx @@ -30,7 +30,7 @@ import { useBaseTheme } from '../../use_base_theme'; const rng = getRandomNumberGenerator('static'); const aggData = [ - ...KIBANA_METRICS.metrics.kibana_os_load[0].data + ...KIBANA_METRICS.metrics.kibana_os_load.v1.data .reduce<{ x: number; y: string; value: number }[]>((acc, [x, y], i) => { if (i % 5 === 0) { acc.push({ x, y: '2fd4e', value: y }); @@ -41,7 +41,7 @@ const aggData = [ return acc; }, []) .map(({ x, y, value }) => (rng() > 0.6 ? { x, y, value: null } : { x, y, value })), - ...KIBANA_METRICS.metrics.kibana_os_load[1].data + ...KIBANA_METRICS.metrics.kibana_os_load.v2.data .reduce<{ x: number; y: string; value: number }[]>((acc, [x, y], i) => { if (i % 5 === 0) { acc.push({ x, y: '3afad', value: y }); @@ -52,7 +52,7 @@ const aggData = [ return acc; }, []) .map(({ x, y, value }) => (rng() > 0.6 ? { x, y, value: null } : { x, y, value })), - ...KIBANA_METRICS.metrics.kibana_os_load[2].data + ...KIBANA_METRICS.metrics.kibana_os_load.v3.data .reduce<{ x: number; y: string; value: number }[]>((acc, [x, y], i) => { if (i % 5 === 0) { acc.push({ x, y: 'f9560', value: y }); @@ -132,7 +132,7 @@ export const Example = () => { yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_response_times[0].data} + data={KIBANA_METRICS.metrics.kibana_response_times.v1.data} yNice color="#343741" /> @@ -181,7 +181,7 @@ export const Example = () => { yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_requests[0].data} + data={KIBANA_METRICS.metrics.kibana_requests.v1.data} color="#343741" yNice /> diff --git a/storybook/stories/legend/13_inside_chart.story.tsx b/storybook/stories/legend/13_inside_chart.story.tsx index ff32a0bbba..935d404833 100644 --- a/storybook/stories/legend/13_inside_chart.story.tsx +++ b/storybook/stories/legend/13_inside_chart.story.tsx @@ -100,12 +100,12 @@ export const Example = () => { Number(d).toFixed(2)} /> [ +const data1 = KIBANA_METRICS.metrics.kibana_os_load.v1.data.map((d) => [ ...d, - KIBANA_METRICS.metrics.kibana_os_load[0].metric.label, + KIBANA_METRICS.metrics.kibana_os_load.v1.metric.label, ]); -const data2 = KIBANA_METRICS.metrics.kibana_os_load[1].data.map((d) => [ +const data2 = KIBANA_METRICS.metrics.kibana_os_load.v2.data.map((d) => [ ...d, - KIBANA_METRICS.metrics.kibana_os_load[1].metric.label, + KIBANA_METRICS.metrics.kibana_os_load.v2.metric.label, ]); -const data3 = KIBANA_METRICS.metrics.kibana_os_load[2].data.map((d) => [ +const data3 = KIBANA_METRICS.metrics.kibana_os_load.v3.data.map((d) => [ ...d, - KIBANA_METRICS.metrics.kibana_os_load[2].metric.label, + KIBANA_METRICS.metrics.kibana_os_load.v3.metric.label, ]); const allMetrics = [...data3, ...data2, ...data1]; @@ -71,7 +71,7 @@ export const Example = () => { Number(d).toFixed(2)} ticks={5} /> { @@ -45,7 +45,7 @@ export const Example = () => { `${Number(d).toFixed(0)}%`} /> diff --git a/storybook/stories/line/1_basic.story.tsx b/storybook/stories/line/1_basic.story.tsx index 45c19c35a2..186f01f051 100644 --- a/storybook/stories/line/1_basic.story.tsx +++ b/storybook/stories/line/1_basic.story.tsx @@ -16,7 +16,7 @@ import { useBaseTheme } from '../../use_base_theme'; export const Example = () => { const toggleSpec = boolean('toggle line spec', true); - const data1 = KIBANA_METRICS.metrics.kibana_os_load[0].data; + const data1 = KIBANA_METRICS.metrics.kibana_os_load.v1.data; const data2 = data1.map((datum) => [datum[0], datum[1] - 1]); const data = toggleSpec ? data1 : data2; const specId = toggleSpec ? 'lines1' : 'lines2'; diff --git a/storybook/stories/line/2_w_axis.story.tsx b/storybook/stories/line/2_w_axis.story.tsx index fe026878af..2ebed1020d 100644 --- a/storybook/stories/line/2_w_axis.story.tsx +++ b/storybook/stories/line/2_w_axis.story.tsx @@ -36,7 +36,7 @@ export const Example = () => ( `${Number(d).toFixed(2)}%`} /> @@ -46,7 +46,7 @@ export const Example = () => ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 5)} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, 5)} /> ); diff --git a/storybook/stories/line/3_ordinal.story.tsx b/storybook/stories/line/3_ordinal.story.tsx index 136357d482..cc34e8c3c0 100644 --- a/storybook/stories/line/3_ordinal.story.tsx +++ b/storybook/stories/line/3_ordinal.story.tsx @@ -31,7 +31,7 @@ export const Example = () => ( `${Number(d).toFixed(2)}%`} /> @@ -41,7 +41,7 @@ export const Example = () => ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 5)} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, 5)} /> ); diff --git a/storybook/stories/line/4_linear.story.tsx b/storybook/stories/line/4_linear.story.tsx index 5d5530b3d0..6a1e09d671 100644 --- a/storybook/stories/line/4_linear.story.tsx +++ b/storybook/stories/line/4_linear.story.tsx @@ -30,7 +30,7 @@ export const Example = () => ( `${Number(d).toFixed(2)}%`} /> @@ -40,7 +40,7 @@ export const Example = () => ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 5)} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, 5)} /> ); diff --git a/storybook/stories/line/5_w_axis_and_legend.story.tsx b/storybook/stories/line/5_w_axis_and_legend.story.tsx index ecbb16b3e6..e22574a1bd 100644 --- a/storybook/stories/line/5_w_axis_and_legend.story.tsx +++ b/storybook/stories/line/5_w_axis_and_legend.story.tsx @@ -30,7 +30,7 @@ export const Example = () => ( `${Number(d).toFixed(0)}%`} /> @@ -40,7 +40,7 @@ export const Example = () => ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} /> ); diff --git a/storybook/stories/line/6_curved.story.tsx b/storybook/stories/line/6_curved.story.tsx index 92ed6b703b..635372efc3 100644 --- a/storybook/stories/line/6_curved.story.tsx +++ b/storybook/stories/line/6_curved.story.tsx @@ -31,7 +31,7 @@ export const Example = () => ( `${Number(d).toFixed(0)}%`} /> @@ -42,7 +42,7 @@ export const Example = () => ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} curve={CurveType.CURVE_MONOTONE_X} /> ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} curve={CurveType.CURVE_BASIS} /> ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} curve={CurveType.CURVE_CARDINAL} /> ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} curve={CurveType.CURVE_CATMULL_ROM} /> ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} curve={CurveType.CURVE_NATURAL} /> ( yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} curve={CurveType.LINEAR} /> diff --git a/storybook/stories/line/7_multiple.story.tsx b/storybook/stories/line/7_multiple.story.tsx index def4a8e4bb..873128c4d2 100644 --- a/storybook/stories/line/7_multiple.story.tsx +++ b/storybook/stories/line/7_multiple.story.tsx @@ -31,36 +31,36 @@ export const Example = () => ( `${Number(d).toFixed(0)}%`} /> diff --git a/storybook/stories/line/8_stacked.story.tsx b/storybook/stories/line/8_stacked.story.tsx index 80b78df209..ff0c09f368 100644 --- a/storybook/stories/line/8_stacked.story.tsx +++ b/storybook/stories/line/8_stacked.story.tsx @@ -31,37 +31,37 @@ export const Example = () => ( `${Number(d).toFixed(0)}%`} /> diff --git a/storybook/stories/line/9_multi_series.story.tsx b/storybook/stories/line/9_multi_series.story.tsx index 1041de21e0..ff2d2af5a0 100644 --- a/storybook/stories/line/9_multi_series.story.tsx +++ b/storybook/stories/line/9_multi_series.story.tsx @@ -32,7 +32,7 @@ export const Example = () => ( `${Number(d).toFixed(0)}%`} /> diff --git a/storybook/stories/metric/1_basic.story.tsx b/storybook/stories/metric/1_basic.story.tsx index 4ef4bf53e4..e6ea18985c 100644 --- a/storybook/stories/metric/1_basic.story.tsx +++ b/storybook/stories/metric/1_basic.story.tsx @@ -82,7 +82,7 @@ export const Example = () => { ...(progressOrTrend === 'bar' ? { domainMax: progressMax, progressBarDirection } : {}), ...(progressOrTrend === 'trend' ? { - trend: KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), + trend: KIBANA_METRICS.metrics.kibana_os_load.v2.data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), trendShape, trendA11yTitle, trendA11yDescription, @@ -95,7 +95,7 @@ export const Example = () => { ...(progressOrTrend === 'bar' ? { domainMax: progressMax, progressBarDirection } : {}), ...(progressOrTrend === 'trend' ? { - trend: KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), + trend: KIBANA_METRICS.metrics.kibana_os_load.v2.data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), trendShape, trendA11yTitle, trendA11yDescription, diff --git a/storybook/stories/metric/2_grid.story.tsx b/storybook/stories/metric/2_grid.story.tsx index fe1cf00f61..54d9191a3e 100644 --- a/storybook/stories/metric/2_grid.story.tsx +++ b/storybook/stories/metric/2_grid.story.tsx @@ -50,7 +50,7 @@ export const Example = () => { icon: getIcon('compute'), value: NaN, valueFormatter: defaultValueFormatter, - trend: KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), + trend: KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), trendShape: 'area', trendA11yTitle: 'Last hour CPU percentage trend', trendA11yDescription: @@ -62,7 +62,7 @@ export const Example = () => { subtitle: 'Overall percentage', value: 33.57, valueFormatter: (d) => `${d} %`, - trend: KIBANA_METRICS.metrics.kibana_memory[0].data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), + trend: KIBANA_METRICS.metrics.kibana_memory.v1.data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), trendShape: 'area', trendA11yTitle: 'Last hour Memory usage trend', trendA11yDescription: @@ -108,7 +108,7 @@ export const Example = () => { subtitle: 'Cluster CPU Usage', value: 24.85, valueFormatter: (d) => `${d}%`, - trend: KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), + trend: KIBANA_METRICS.metrics.kibana_os_load.v2.data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), trendShape: 'area', }, { @@ -136,7 +136,7 @@ export const Example = () => { ), value: 323.57, valueFormatter: (d) => `$ ${d}k`, - trend: KIBANA_METRICS.metrics.kibana_os_load[2].data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), + trend: KIBANA_METRICS.metrics.kibana_os_load.v3.data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), trendShape: 'area', trendA11yTitle: 'Last quarter, daily Cloud Revenue trend', trendA11yDescription: diff --git a/storybook/stories/stylings/26_highlighter_style.story.tsx b/storybook/stories/stylings/26_highlighter_style.story.tsx index 76100a3387..1167a5f9b2 100644 --- a/storybook/stories/stylings/26_highlighter_style.story.tsx +++ b/storybook/stories/stylings/26_highlighter_style.story.tsx @@ -73,14 +73,14 @@ export const Example = () => ( /> ( /> ( /> { yScaleType={ScaleType.Linear} xAccessor={0} yAccessors={[1]} - data={KIBANA_METRICS.metrics.kibana_os_load[0].data} + data={KIBANA_METRICS.metrics.kibana_os_load.v1.data} /> ); diff --git a/storybook/stories/test_cases/8_test_points_outside_of_domain.story.tsx b/storybook/stories/test_cases/8_test_points_outside_of_domain.story.tsx index b3dae0645e..ec5900edd0 100644 --- a/storybook/stories/test_cases/8_test_points_outside_of_domain.story.tsx +++ b/storybook/stories/test_cases/8_test_points_outside_of_domain.story.tsx @@ -35,7 +35,7 @@ export const Example = () => { Number(d).toFixed(2)} domain={{ diff --git a/storybook/stories/utils/utils.ts b/storybook/stories/utils/utils.ts index 8dc9e00fd3..ee413659f8 100644 --- a/storybook/stories/utils/utils.ts +++ b/storybook/stories/utils/utils.ts @@ -9,7 +9,9 @@ import _, { Dictionary, NumericDictionary } from 'lodash'; import seedrandom from 'seedrandom'; +import { BandFillColorAccessorInput } from '@elastic/charts'; import { arrayToLookup, hueInterpolator } from '@elastic/charts/src/common/color_calcs'; +import { Color } from '@elastic/charts/src/common/colors'; import { countryDimension, productDimension, @@ -26,6 +28,11 @@ export const productPriceLookup = arrayToLookup((d: any) => d.products_price, pr type ColorMaker = (x: number) => string; +export const getBandFillColorFn = + (colorMap: { [k: number]: Color }) => + ({ value: x }: BandFillColorAccessorInput): Color => + colorMap[x]; + // interpolation based, cyclical color example export const interpolatorCET2s = (opacity = 0.7) => hueInterpolator(palettes.CET2s.map(([r, g, b]) => [r, g, b, opacity])); diff --git a/storybook/tsconfig.json b/storybook/tsconfig.json index 8e59d55872..e6f1498259 100644 --- a/storybook/tsconfig.json +++ b/storybook/tsconfig.json @@ -1,5 +1,8 @@ { "extends": "../tsconfig", "include": ["../packages/charts/src/**/*", "./**/*"], - "exclude": ["../**/*.test.*"] + "exclude": ["../**/*.test.*"], + "compilerOptions": { + "noUncheckedIndexedAccess": false + } } diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000000..3f451be11b --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["**/tmp", "**/dist", "./playground", "./storybook", "./packages/charts"] +} diff --git a/tsconfig.json b/tsconfig.json index 8c72ba0ff6..7a903903e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "preserveConstEnums": true, "strict": true, "esModuleInterop": true, + "noUncheckedIndexedAccess": true, "module": "CommonJS", "target": "es5", "lib": ["esnext", "dom"],