From 2445e491b05b14cd1e82ff7eb986b44f922abede Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Wed, 6 Jul 2022 08:01:39 -0700 Subject: [PATCH 1/6] feat: timeslip prototype added --- package.json | 2 +- packages/charts/api/charts.api.md | 28 + packages/charts/package.json | 1 + packages/charts/src/chart_types/index.ts | 1 + .../timeslip/internal_chart_state.ts | 40 + .../timeslip/timeslip/axis_model.js | 49 + .../src/chart_types/timeslip/timeslip/data.js | 32 + .../timeslip/timeslip/domain_tween.js | 31 + .../render/annotations/chart_title.js | 20 + .../render/annotations/time_extent.js | 33 + .../timeslip/render/annotations/time_unit.js | 30 + .../timeslip/timeslip/render/cartesian.js | 84 ++ .../timeslip/timeslip/render/column.js | 208 +++++ .../timeslip/timeslip/render/glyphs/bar.js | 37 + .../timeslip/render/glyphs/boxplot.js | 55 ++ .../timeslip/render/glyphs/debug_box.js | 19 + .../timeslip/timeslip/render/raster.js | 170 ++++ .../timeslip/timeslip/timeslip_render.js | 843 ++++++++++++++++++ .../timeslip/timeslip/translations.js | 220 +++++ .../timeslip/timeslip/utils/dom.js | 37 + .../timeslip/timeslip/utils/generator.js | 22 + .../timeslip/timeslip/utils/math.js | 14 + .../timeslip/timeslip/utils/projection.js | 21 + .../src/chart_types/timeslip/timeslip_api.ts | 62 ++ .../chart_types/timeslip/timeslip_chart.tsx | 158 ++++ packages/charts/src/index.ts | 1 + packages/charts/src/state/chart_state.ts | 2 + .../stories/timeslip/01_timeslip.story.tsx | 81 ++ .../stories/timeslip/timeslip.stories.tsx | 13 + 29 files changed, 2313 insertions(+), 1 deletion(-) create mode 100644 packages/charts/src/chart_types/timeslip/internal_chart_state.ts create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/axis_model.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/data.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/domain_tween.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/render/annotations/chart_title.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_extent.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_unit.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/render/cartesian.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/render/column.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/bar.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/boxplot.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/debug_box.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/render/raster.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/translations.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/utils/dom.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/utils/generator.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/utils/math.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip/utils/projection.js create mode 100644 packages/charts/src/chart_types/timeslip/timeslip_api.ts create mode 100644 packages/charts/src/chart_types/timeslip/timeslip_chart.tsx create mode 100644 storybook/stories/timeslip/01_timeslip.story.tsx create mode 100644 storybook/stories/timeslip/timeslip.stories.tsx diff --git a/package.json b/package.json index bf57b385ae..9192426a04 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,6 @@ "mini-css-extract-plugin": "1.6.2", "moment": "^2.29.1", "moment-timezone": "^0.5.32", - "sass": "^1.49.9", "numeral": "^2.0.6", "postcss": "^8.3.0", "postcss-cli": "^8.3.1", @@ -167,6 +166,7 @@ "react-dom": "^16.13.0", "react-is": "^16.13.0", "redux-devtools-extension": "^2.13.8", + "sass": "^1.49.9", "sass-graph": "^3.0.5", "seedrandom": "^3.0.5", "semantic-release": "^19.0.3", diff --git a/packages/charts/api/charts.api.md b/packages/charts/api/charts.api.md index 75168139b2..54cf7ff09d 100644 --- a/packages/charts/api/charts.api.md +++ b/packages/charts/api/charts.api.md @@ -524,6 +524,7 @@ export const ChartType: Readonly<{ Goal: "goal"; Partition: "partition"; Flame: "flame"; + Timeslip: "timeslip"; XYAxis: "xy_axis"; Heatmap: "heatmap"; Wordcloud: "wordcloud"; @@ -2718,6 +2719,33 @@ export interface TimeScale { type: typeof ScaleType.Time; } +// @public +export const Timeslip: (props: SFProps, "chartType" | "specType", "animation" | "valueAccessor" | "valueFormatter" | "valueGetter", never, "id" | "columnarData" | "controlProviderCallback">) => null; + +// @public +export type TimeslipGlobalControl = () => void; + +// @public +export type TimeslipNodeControl = (nodeIndex: number) => void; + +// @public +export interface TimeslipSpec extends Spec, LegacyAnimationConfig { + // (undocumented) + chartType: typeof ChartType.Timeslip; + // (undocumented) + columnarData: ColumnarViewModel; + // (undocumented) + controlProviderCallback: Partial; + // (undocumented) + specType: typeof SpecType.Series; + // (undocumented) + valueAccessor: ValueAccessor; + // (undocumented) + valueFormatter: ValueFormatter; + // (undocumented) + valueGetter: (datumIndex: number) => number; +} + // @public export function toEntries, S>(array: T[], accessor: keyof T, staticValue: S): Record; diff --git a/packages/charts/package.json b/packages/charts/package.json index 42ed84f1d4..a73c0f6cbc 100644 --- a/packages/charts/package.json +++ b/packages/charts/package.json @@ -36,6 +36,7 @@ "bezier-easing": "^2.1.0", "chroma-js": "^2.1.0", "classnames": "^2.2.6", + "dat.gui": "^0.7.9", "d3-array": "^1.2.4", "d3-cloud": "^1.2.5", "d3-collection": "^1.0.7", diff --git a/packages/charts/src/chart_types/index.ts b/packages/charts/src/chart_types/index.ts index c42acdc11f..2726dc7ce7 100644 --- a/packages/charts/src/chart_types/index.ts +++ b/packages/charts/src/chart_types/index.ts @@ -17,6 +17,7 @@ export const ChartType = Object.freeze({ Goal: 'goal' as const, Partition: 'partition' as const, Flame: 'flame' as const, + Timeslip: 'timeslip' as const, XYAxis: 'xy_axis' as const, Heatmap: 'heatmap' as const, Wordcloud: 'wordcloud' as const, diff --git a/packages/charts/src/chart_types/timeslip/internal_chart_state.ts b/packages/charts/src/chart_types/timeslip/internal_chart_state.ts new file mode 100644 index 0000000000..92d70cbd8b --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/internal_chart_state.ts @@ -0,0 +1,40 @@ +/* + * 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 { DEFAULT_CSS_CURSOR } from '../../common/constants'; +import { LegendItemExtraValues } from '../../common/legend'; +import { SeriesKey } from '../../common/series_id'; +import { InternalChartState } from '../../state/chart_state'; +import { InitStatus } from '../../state/selectors/get_internal_is_intialized'; +import { TimeslipWithTooltip } from './timeslip_chart'; + +/** @internal */ +export class TimeslipState implements InternalChartState { + chartType = ChartType.Timeslip; + getChartTypeDescription = () => 'Timeslip chart'; + chartRenderer = TimeslipWithTooltip; + + // default empty properties, unused in Timeslip + eventCallbacks = () => {}; + isInitialized = () => InitStatus.Initialized; + isBrushAvailable = () => false; + isBrushing = () => false; + isChartEmpty = () => false; + getLegendItemsLabels = () => []; + getLegendItems = () => []; + getLegendExtraValues = () => new Map(); + getPointerCursor = () => DEFAULT_CSS_CURSOR; + getTooltipAnchor = () => ({ x: 0, y: 0, width: 0, height: 0 }); + isTooltipVisible = () => ({ visible: false, isExternal: false }); + getTooltipInfo = () => ({ header: null, values: [] }); + getProjectionContainerArea = () => ({ width: 0, height: 0, top: 0, left: 0 }); + getMainProjectionArea = () => ({ width: 0, height: 0, top: 0, left: 0 }); + getBrushArea = () => null; + getDebugState = () => ({}); +} diff --git a/packages/charts/src/chart_types/timeslip/timeslip/axis_model.js b/packages/charts/src/chart_types/timeslip/timeslip/axis_model.js new file mode 100644 index 0000000000..b168790005 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/axis_model.js @@ -0,0 +1,49 @@ +/* + * 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. + */ + +const getNiceTicksForApproxCount = (domainMin, domainMax, approxDesiredTickCount) => { + const diff = domainMax - domainMin; + const rawPitch = diff / approxDesiredTickCount; + const exponent = Math.floor(Math.log10(rawPitch)); + const orderOfMagnitude = 10 ** exponent; // this represents the order of magnitude eg. 10000, so that... + const mantissa = rawPitch / orderOfMagnitude; // it's always the case that 1 <= mantissa <= 9.99999999999 + const niceMantissa = mantissa > 5 ? 10 : mantissa > 2 ? 5 : mantissa > 1 ? 2 : 1; // snap to 10, 5, 2 or 1 + const tickInterval = niceMantissa * orderOfMagnitude; + if (!isFinite(tickInterval)) { + return []; + } + const result = []; + for (let i = Math.floor(domainMin / tickInterval); i <= Math.ceil(domainMax / tickInterval); i++) { + result.push(i * tickInterval); + } + return result; +}; + +const getNiceTicks = (domainMin, domainMax, maximumTickCount) => { + let bestCandidate = []; + for (let i = 0; i <= maximumTickCount; i++) { + const candidate = getNiceTicksForApproxCount(domainMin, domainMax, maximumTickCount - i); + if (candidate.length <= maximumTickCount && candidate.length > 0) return candidate; + if (bestCandidate.length === 0 || maximumTickCount - candidate.length < maximumTickCount - bestCandidate.length) { + bestCandidate = candidate; + } + } + return bestCandidate.length > maximumTickCount + ? [...(maximumTickCount > 1 ? [bestCandidate[0]] : []), bestCandidate[bestCandidate.length - 1]] + : []; +}; + +/** @internal */ +export const axisModel = (domainLandmarks, desiredTickCount) => { + const domainMin = Math.min(...domainLandmarks); + const domainMax = Math.max(...domainLandmarks); + const niceTicks = getNiceTicks(domainMin, domainMax, desiredTickCount); + 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/data.js b/packages/charts/src/chart_types/timeslip/timeslip/data.js new file mode 100644 index 0000000000..5a8918881f --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/data.js @@ -0,0 +1,32 @@ +/* + * 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. + */ + +/** @internal */ +export const dataSource = Symbol('dataSource'); + +/** @internal */ +export const getEnrichedData = (rows) => { + const stats = rows.reduce( + (p, { epochMs, value }) => { + const { minEpochMs, maxEpochMs, minValue, maxValue } = p; + p.minEpochMs = Math.min(minEpochMs, epochMs); + p.maxEpochMs = Math.max(maxEpochMs, epochMs); + p.minValue = Math.min(minValue, value); + p.maxValue = Math.max(maxValue, value); + return p; + }, + { + minEpochMs: Infinity, + maxEpochMs: -Infinity, + minValue: Infinity, + maxValue: -Infinity, + }, + ); + // console.log({ from: new Date(stats.minEpochMs), to: new Date(stats.maxEpochMs), count: rows.length }) + return { rows, stats }; +}; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/domain_tween.js b/packages/charts/src/chart_types/timeslip/timeslip/domain_tween.js new file mode 100644 index 0000000000..6a127d657f --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/domain_tween.js @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { mix } from './utils/math'; + +const REFERENCE_AF_LENGTH = 16.67; // ms +const REFERENCE_Y_RECURRENCE_ALPHA = 0.1; +const TWEEN_DONE_EPSILON = 0.001; + +/** @internal */ +export const domainTween = (interactionState, deltaT, targetMin, targetMax) => { + const { niceDomainMin: currentMin, niceDomainMax: currentMax } = interactionState; + + // pure logic + const speedExp = Math.pow((currentMax - currentMin) / (targetMax - targetMin), 0.2); // speeds up big decreases + const advance = 1 - (1 - REFERENCE_Y_RECURRENCE_ALPHA) ** ((speedExp * deltaT) / REFERENCE_AF_LENGTH); + const min = Number.isFinite(currentMin) ? mix(currentMin, targetMin, advance) : targetMin; + const max = Number.isFinite(currentMax) ? mix(currentMax, targetMax, advance) : targetMax; + const tweenIncomplete = Math.abs(1 - (max - min) / (targetMax - targetMin)) > TWEEN_DONE_EPSILON; + + // remember + interactionState.niceDomainMin = min; + interactionState.niceDomainMax = max; + + return tweenIncomplete; +}; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/chart_title.js b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/chart_title.js new file mode 100644 index 0000000000..de93b9dbe6 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/chart_title.js @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** @internal */ +export function renderChartTitle(ctx, config, chartWidth, cartesianTop, aggregationFunctionName) { + ctx.save(); + const titleFontSize = 32; // todo move to config + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + ctx.font = `normal normal 200 ${titleFontSize}px Inter, Helvetica, Arial, sans-serif`; // todo move to config + ctx.fillStyle = config.subduedFontColor; + ctx.fillText(config.queryConfig.metricFieldName, chartWidth / 2, cartesianTop / 2 - titleFontSize * 0.5); + ctx.fillText(aggregationFunctionName, chartWidth / 2, cartesianTop / 2 + titleFontSize * 0.5); + ctx.restore(); +} diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_extent.js b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_extent.js new file mode 100644 index 0000000000..c3cc2814b9 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_extent.js @@ -0,0 +1,33 @@ +/* + * 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. + */ + +/** @internal */ +export function renderTimeExtentAnnotation( + ctx, + config, + localeOptions, + timeDomainFrom, + timeDomainTo, + cartesianWidth, + chartTopFontSize, +) { + ctx.save(); + ctx.textBaseline = 'bottom'; + ctx.textAlign = 'right'; + ctx.font = config.monospacedFontShorthand; + ctx.fillStyle = config.subduedFontColor; + // todo switch to new Intl.DateTimeFormat for more performance https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat + ctx.fillText( + `${new Date(timeDomainFrom * 1000).toLocaleString(config.locale, localeOptions)} — ${new Date( + timeDomainTo * 1000, + ).toLocaleString(config.locale, localeOptions)}`, + cartesianWidth, + -chartTopFontSize * 0.5, + ); + ctx.restore(); +} diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_unit.js b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_unit.js new file mode 100644 index 0000000000..1117a0c276 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_unit.js @@ -0,0 +1,30 @@ +/* + * 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 { uiStrings } from '../../translations'; + +/** @internal */ +export function renderTimeUnitAnnotation(ctx, config, binUnitCount, binUnit, chartTopFontSize, unitBarMaxWidthPixels) { + ctx.save(); + ctx.textBaseline = 'bottom'; + ctx.textAlign = 'left'; + ctx.font = config.monospacedFontShorthand; + ctx.fillStyle = config.a11y.contrast === 'low' ? config.subduedFontColor : config.defaultFontColor; + ctx.fillText( + `1 ${uiStrings[config.locale].bar} = ${binUnitCount} ${ + uiStrings[config.locale][binUnit + (binUnitCount !== 1 ? 's' : '')] + }`, + 0, + -chartTopFontSize * 0.5, + ); + const unitBarY = -chartTopFontSize * 2.2; + ctx.fillRect(0, unitBarY, unitBarMaxWidthPixels, 1); + ctx.fillRect(0, unitBarY - 3, 1, 7); + ctx.fillRect(unitBarMaxWidthPixels - 1, unitBarY - 3, 1, 7); + ctx.restore(); +} diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/cartesian.js b/packages/charts/src/chart_types/timeslip/timeslip/render/cartesian.js new file mode 100644 index 0000000000..6b0cc3e64a --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/cartesian.js @@ -0,0 +1,84 @@ +/* + * 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 { renderRaster } from './raster'; + +/** @internal */ +export const renderCartesian = ( + ctx, + config, + dataState, + guiConfig, + defaultMinorTickLabelFormat, + emWidth, + fadeOutPixelWidth, + defaultLabelFormat, + yTickNumberFormatter, + rasterSelector, + cartesianWidth, + cartesianHeight, + { domainFrom, domainTo }, + yUnitScale, + yUnitScaleClamped, + niceTicks, +) => { + ctx.textBaseline = 'top'; + ctx.fillStyle = config.defaultFontColor; + ctx.font = config.cssFontShorthand; + ctx.textAlign = 'left'; + + const timeExtent = domainTo - domainFrom; + + const getPixelX = (timePointSec) => { + const continuousOffset = timePointSec - domainFrom; + const ratio = continuousOffset / timeExtent; + return cartesianWidth * ratio; + }; + + const notTooDense = + (domainFrom, domainTo) => + ({ minimumPixelsPerSecond }) => { + const domainInSeconds = domainTo - domainFrom; + const pixelsPerSecond = cartesianWidth / domainInSeconds; + return pixelsPerSecond > minimumPixelsPerSecond; + }; + + const layers = rasterSelector(notTooDense(domainFrom, domainTo)); + + const loHi = layers.reduce( + renderRaster({ + ctx, + config, + guiConfig, + dataState, + fadeOutPixelWidth, + emWidth, + defaultMinorTickLabelFormat, + defaultLabelFormat, + yTickNumberFormatter, + domainFrom, + domainTo, + getPixelX, + cartesianWidth, + cartesianHeight, + niceTicks, + yUnitScale, + yUnitScaleClamped, + layers, + }), + { lo: null, hi: null, unitBarMaxWidthPixelsSum: 0, unitBarMaxWidthPixelsCount: 0 }, + ); + + return { + lo: loHi.lo, + hi: loHi.hi, + binUnit: layers[0].unit, + binUnitCount: layers[0].unitMultiplier, + unitBarMaxWidthPixels: loHi.unitBarMaxWidthPixelsSum / loHi.unitBarMaxWidthPixelsCount, + }; +}; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/column.js b/packages/charts/src/chart_types/timeslip/timeslip/render/column.js new file mode 100644 index 0000000000..f5217e7b79 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/column.js @@ -0,0 +1,208 @@ +/* + * 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 { renderBarGlyph } from './glyphs/bar'; +import { renderBoxplotGlyph } from './glyphs/boxplot'; + +/** @internal */ +export const renderColumn = ( + { + ctx, + config, + guiConfig, + dataState, + emWidth, + fadeOutPixelWidth, + getPixelX, + labelFormat, + minorLabelFormat, + unitBarMaxWidthPixelsSum, + unitBarMaxWidthPixelsCount, + labeled, + textNestLevel, + textNestLevelRowLimited, + cartesianWidth, + cartesianHeight, + i, + valid, + luma, + lineThickness, + lineNestLevelRowLimited, + halfLineThickness, + domainFrom, + layers, + rows, + yUnitScale, + yUnitScaleClamped, + }, + { fontColor, timePointSec, nextTimePointSec }, + pixelX = getPixelX(timePointSec), + maxWidth = Infinity, +) => { + if (labeled && textNestLevel <= guiConfig.maxLabelRowCount) { + const text = + textNestLevelRowLimited === guiConfig.maxLabelRowCount + ? labelFormat(timePointSec * 1000) + : minorLabelFormat(timePointSec * 1000); + if (text.length > 0) { + const textX = pixelX + config.horizontalPixelOffset; + const y = config.verticalPixelOffset + (textNestLevelRowLimited - 1) * config.rowPixelPitch; + const leftShortening = + maxWidth === Infinity ? 0 : Math.max(0, ctx.measureText(text).width + config.horizontalPixelOffset - maxWidth); + const rightShortening = + textX + Math.min(maxWidth, text.length * emWidth) < cartesianWidth + ? 0 + : Math.max(0, textX + ctx.measureText(text).width - cartesianWidth); + const maxWidthRight = Math.max(0, cartesianWidth - textX); + const clipLeft = config.clipLeft && leftShortening > 0; + const clipRight = config.clipRight && rightShortening > 0; + if (clipLeft) { + ctx.save(); + ctx.beginPath(); + ctx.rect(config.horizontalPixelOffset, y - 0.35 * config.rowPixelPitch, maxWidth, config.rowPixelPitch); + ctx.clip(); + } + if (clipRight) { + ctx.save(); + ctx.beginPath(); + ctx.rect(textX, y - 0.35 * config.rowPixelPitch, maxWidthRight, config.rowPixelPitch); + ctx.clip(); + } + ctx.fillStyle = + fontColor ?? (guiConfig.a11y.contrast === 'low' ? config.subduedFontColor : config.defaultFontColor); + ctx.fillText(text, textX - leftShortening, y); + if (clipRight) { + const { r, g, b } = config.backgroundColor; + const fadeOutRight = ctx.createLinearGradient(textX, 0, textX + maxWidthRight, 0); + fadeOutRight.addColorStop(0, `rgba(${r},${g},${b},0)`); + fadeOutRight.addColorStop( + maxWidthRight === 0 ? 0.5 : Math.max(0, 1 - fadeOutPixelWidth / maxWidthRight), + `rgba(${r},${g},${b},0)`, + ); + fadeOutRight.addColorStop(1, `rgba(${r},${g},${b},1)`); + ctx.fillStyle = fadeOutRight; + ctx.fill(); + ctx.restore(); + } + if (clipLeft) { + const { r, g, b } = config.backgroundColor; + const fadeOutLeft = ctx.createLinearGradient(0, 0, maxWidth, 0); + fadeOutLeft.addColorStop(0, `rgba(${r},${g},${b},1)`); + fadeOutLeft.addColorStop( + maxWidth === 0 ? 0.5 : Math.min(1, fadeOutPixelWidth / maxWidth), + `rgba(${r},${g},${b},0)`, + ); + fadeOutLeft.addColorStop(1, `rgba(${r},${g},${b},0)`); + ctx.fillStyle = fadeOutLeft; + ctx.fill(); + ctx.restore(); + } + } + } + + // draw bars + const barPad = guiConfig.implicit ? halfLineThickness : 0; + const fullBarPixelX = getPixelX(timePointSec); + const barMaxWidthPixels = getPixelX(nextTimePointSec) - fullBarPixelX - 2 * barPad; + if (i === 0) { + unitBarMaxWidthPixelsSum += barMaxWidthPixels; + unitBarMaxWidthPixelsCount++; + } + renderBar: if ( + i === 0 && + valid && + dataState.binUnit === layers[0].unit && + dataState.binUnitCount === layers[0].unitMultiplier + ) { + const foundRow = rows.find((r) => timePointSec * 1000 <= r.epochMs && r.epochMs < nextTimePointSec * 1000); + if (!foundRow) { + break renderBar; // comment it out if the goal is to see zero values where data is missing + } + ctx.save(); + + // left side special case + const leftShortfall = Math.abs(pixelX - fullBarPixelX); + const leftOpacityMultiplier = leftShortfall ? 1 - Math.max(0, Math.min(1, leftShortfall / barMaxWidthPixels)) : 1; + + // right side special case + const barX = pixelX + barPad; + const rightShortfall = Math.max(0, barX + barMaxWidthPixels - cartesianWidth); + + const maxBarHeight = cartesianHeight; + const barWidthPixels = barMaxWidthPixels - rightShortfall; + + const rightOpacityMultiplier = rightShortfall + ? 1 - Math.max(0, Math.min(1, rightShortfall / barMaxWidthPixels)) + : 1; + const { r, g, b } = config.barChroma; + const maxOpacity = config.barFillAlpha; + const opacityMultiplier = leftOpacityMultiplier * rightOpacityMultiplier; + const opacity = maxOpacity * opacityMultiplier; + const opacityDependentLineThickness = opacityMultiplier === 1 ? 1 : Math.sqrt(opacityMultiplier); + if (guiConfig.queryConfig.boxplot && foundRow.boxplot) { + renderBoxplotGlyph( + ctx, + barMaxWidthPixels, + barX, + leftShortfall, + foundRow, + maxBarHeight, + yUnitScaleClamped, + opacityMultiplier, + r, + g, + b, + maxOpacity, + ); + } else { + renderBarGlyph( + ctx, + barWidthPixels, + leftShortfall, + maxBarHeight, + yUnitScale, + foundRow, + yUnitScaleClamped, + r, + g, + b, + opacity, + barX, + opacityDependentLineThickness, + ); + } + ctx.restore(); + } + + // render vertical grid lines + // the measured text width, plus the `config.horizontalPixelOffset` on the left side must fit inside `maxWidth` + if (domainFrom < timePointSec) { + ctx.fillStyle = `rgb(${luma},${luma},${luma})`; + ctx.fillRect( + pixelX - halfLineThickness, + -cartesianHeight, + lineThickness, + cartesianHeight + lineNestLevelRowLimited * config.rowPixelPitch, + ); + if (guiConfig.implicit && lineNestLevelRowLimited > 0) { + const verticalSeparation = 1; // todo config + ctx.fillStyle = 'lightgrey'; // todo config + ctx.fillRect( + pixelX - halfLineThickness, + verticalSeparation, + lineThickness, + lineNestLevelRowLimited * config.rowPixelPitch - verticalSeparation, + ); + } + } + + return { + unitBarMaxWidthPixelsSum, + unitBarMaxWidthPixelsCount, + }; +}; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/bar.js b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/bar.js new file mode 100644 index 0000000000..b620691ef7 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/bar.js @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/** @internal */ +export function renderBarGlyph( + ctx, + barWidthPixels, + leftShortfall, + maxBarHeight, + yUnitScale, + foundRow, + yUnitScaleClamped, + r, + g, + b, + opacity, + barX, + opacityDependentLineThickness, +) { + const renderedBarWidth = Math.max(0, barWidthPixels - leftShortfall); + const barEnd = -maxBarHeight * yUnitScale(foundRow.value); + const clampedBarEnd = -maxBarHeight * yUnitScaleClamped(foundRow.value); + const clampedBarStart = -maxBarHeight * yUnitScaleClamped(0); + const barHeight = Math.abs(clampedBarStart - clampedBarEnd); + const barY = Math.min(clampedBarStart, clampedBarEnd); + ctx.fillStyle = `rgba(${r},${g},${b},${opacity})`; + ctx.fillRect(barX, barY, renderedBarWidth, barHeight); + if (clampedBarEnd === barEnd) { + ctx.fillStyle = `rgba(${r},${g},${b},1)`; + ctx.fillRect(barX, clampedBarEnd, renderedBarWidth, opacityDependentLineThickness); // avoid Math.sqrt + } +} diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/boxplot.js b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/boxplot.js new file mode 100644 index 0000000000..59acb9fb69 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/boxplot.js @@ -0,0 +1,55 @@ +/* + * 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. + */ + +/** @internal */ +export function renderBoxplotGlyph( + ctx, + barMaxWidthPixels, + barX, + leftShortfall, + foundRow, + maxBarHeight, + yUnitScaleClamped, + opacityMultiplier, + r, + g, + b, + maxOpacity, +) { + const goldenRatio = 1.618; // todo move it into constants + const boxplotWidth = barMaxWidthPixels / goldenRatio; // - clamp(rightShortfall etc etc) + const whiskerWidth = boxplotWidth / 2; + const boxplotLeftX = barX + (barMaxWidthPixels - boxplotWidth) / 2 - leftShortfall; + const boxplotCenterX = boxplotLeftX + boxplotWidth / 2; + const { /*min, */ lower, q1, q2, q3, upper /*max */ } = foundRow.boxplot; + const lowerY = maxBarHeight * yUnitScaleClamped(lower); + const q1Y = maxBarHeight * yUnitScaleClamped(q1); + const q2Y = maxBarHeight * yUnitScaleClamped(q2); + const q3Y = maxBarHeight * yUnitScaleClamped(q3); + const upperY = maxBarHeight * yUnitScaleClamped(upper); + // boxplot rectangle body with border + if (lowerY !== upperY && q1Y !== q2Y && q2Y !== q3Y) { + const unitVisibility = opacityMultiplier ** 5; + ctx.beginPath(); + ctx.rect(boxplotLeftX, -q3Y, boxplotWidth, q3Y - q1Y); + ctx.fillStyle = `rgba(${r},${g},${b},${maxOpacity * unitVisibility})`; + ctx.fill(); + ctx.strokeStyle = `rgba(${r},${g},${b},1)`; + ctx.lineWidth = unitVisibility; + //ctx.stroke() + // boxplot whiskers + ctx.fillStyle = `rgba(${r},${g},${b},1)`; + ctx.fillRect(boxplotCenterX - whiskerWidth / 2, -upperY, whiskerWidth, unitVisibility); // upper horizontal + ctx.fillRect(boxplotCenterX - boxplotWidth / 2, -q3Y, boxplotWidth, unitVisibility); // q2 horizontal + ctx.fillRect(boxplotCenterX - boxplotWidth / 2, -q2Y, boxplotWidth, unitVisibility); // q2 horizontal + ctx.fillRect(boxplotCenterX - boxplotWidth / 2, -q1Y, boxplotWidth, unitVisibility); // q2 horizontal + ctx.fillRect(boxplotCenterX - whiskerWidth / 2, -lowerY, whiskerWidth, unitVisibility); // lower horizontal + ctx.fillRect(boxplotCenterX, -upperY, unitVisibility, upperY - q3Y); // top vertical + ctx.fillRect(boxplotCenterX, -q1Y, unitVisibility, q1Y - lowerY); // bottom vertical + } +} diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/debug_box.js b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/debug_box.js new file mode 100644 index 0000000000..f1fcedae30 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/debug_box.js @@ -0,0 +1,19 @@ +/* + * 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. + */ + +/** @internal */ +export function renderDebugBox(ctx, cartesianWidth, cartesianHeight) { + ctx.save(); + ctx.beginPath(); + ctx.rect(0, 0, cartesianWidth, cartesianHeight); + ctx.strokeStyle = 'magenta'; + ctx.setLineDash([5, 5]); + ctx.lineWidth = 1; + ctx.stroke(); + ctx.restore(); +} diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/raster.js b/packages/charts/src/chart_types/timeslip/timeslip/render/raster.js new file mode 100644 index 0000000000..852d0c909f --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/raster.js @@ -0,0 +1,170 @@ +/* + * 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 { clamp } from '../utils/math'; +import { renderColumn } from './column'; + +/** @internal */ +export const renderRaster = + ({ + ctx, + config, + guiConfig, + dataState, + fadeOutPixelWidth, + emWidth, + defaultMinorTickLabelFormat, + defaultLabelFormat, + yTickNumberFormatter, + domainFrom, + domainTo, + getPixelX, + cartesianWidth, + cartesianHeight, + niceTicks, + yUnitScale, + yUnitScaleClamped, + layers, + }) => + (loHi, { labeled, binStarts, minorTickLabelFormat, detailedLabelFormat }, i, a) => { + const { + valid, + dataResponse: { rows }, + } = dataState; + + const minorLabelFormat = minorTickLabelFormat ?? defaultMinorTickLabelFormat; + const labelFormat = detailedLabelFormat ?? minorLabelFormat ?? defaultLabelFormat; + const textNestLevel = a.slice(0, i + 1).filter((layer) => layer.labeled).length; + const lineNestLevel = a[i] === a[0] ? 0 : textNestLevel; + const textNestLevelRowLimited = Math.min(guiConfig.maxLabelRowCount, textNestLevel); // max. N rows + const lineNestLevelRowLimited = Math.min(guiConfig.maxLabelRowCount, lineNestLevel); + const lineThickness = config.lineThicknessSteps[i]; + const luma = + config.lumaSteps[i] * + (guiConfig.darkMode + ? guiConfig.a11y.contrast === 'low' + ? 0.5 + : 1 + : guiConfig.a11y.contrast === 'low' + ? 1.5 + : 1); + const halfLineThickness = lineThickness / 2; + + // render all bins that start in the visible domain + let firstInsideBinStart; + let precedingBinStart; + + const columnProps = { + ctx, + config, + guiConfig, + dataState, + fadeOutPixelWidth, + emWidth, + getPixelX, + labelFormat, + minorLabelFormat, + unitBarMaxWidthPixelsSum: loHi.unitBarMaxWidthPixelsSum, + unitBarMaxWidthPixelsCount: loHi.unitBarMaxWidthPixelsCount, + labeled, + textNestLevel, + textNestLevelRowLimited, + cartesianWidth, + cartesianHeight, + i, + valid, + luma, + lineThickness, + halfLineThickness, + lineNestLevelRowLimited, + domainFrom, + layers, + rows, + yUnitScale, + yUnitScaleClamped, + }; + + for (const binStart of binStarts(domainFrom, domainTo)) { + const { timePointSec } = binStart; + if (domainFrom > timePointSec) { + precedingBinStart = binStart; + continue; + } + if (timePointSec > domainTo) { + break; + } + + if (i === 0) { + loHi.lo = loHi.lo || binStart; + loHi.hi = binStart; + } + + if (!firstInsideBinStart) { + firstInsideBinStart = binStart; + } + const { unitBarMaxWidthPixelsSum, unitBarMaxWidthPixelsCount } = renderColumn(columnProps, binStart); + loHi.unitBarMaxWidthPixelsSum = unitBarMaxWidthPixelsSum; + loHi.unitBarMaxWidthPixelsCount = unitBarMaxWidthPixelsCount; + } + + // render specially the tick that just precedes the domain, therefore may insert into it (eg. intentionally, via needing to see tick texts) + if (precedingBinStart) { + if (i === 0) { + // condition necessary, otherwise it'll be the binStart of some temporally coarser bin + loHi.lo = precedingBinStart; // partial bin on the left + } + const { unitBarMaxWidthPixelsSum, unitBarMaxWidthPixelsCount } = renderColumn( + columnProps, + precedingBinStart, + 0, + firstInsideBinStart + ? Math.max(0, getPixelX(firstInsideBinStart.timePointSec) - config.horizontalPixelOffset) + : Infinity, + ); + loHi.unitBarMaxWidthPixelsSum = unitBarMaxWidthPixelsSum; + loHi.unitBarMaxWidthPixelsCount = unitBarMaxWidthPixelsCount; + } + + // render horizontal grids + const horizontalGrids = true; + if (horizontalGrids) { + ctx.save(); + const { r, g, b } = config.backgroundColor; + const lineStyle = guiConfig.implicit + ? `rgb(${r},${g},${b})` + : `rgba(128,128,128,${guiConfig.a11y.contrast === 'low' ? 0.5 : 1})`; + ctx.textBaseline = 'middle'; + ctx.font = config.cssFontShorthand; + const overhang = 8; // todo put it in config + const gap = 8; // todo put it in config + for (const gridDomainValueY of niceTicks) { + const yUnit = yUnitScale(gridDomainValueY); + if (yUnit !== clamp(yUnit, -0.01, 1.01)) { + // todo set it back to 0 and 1 if recurrence relation of transitioning can reach 1 in finite time + continue; + } + const y = -cartesianHeight * yUnit; + const text = yTickNumberFormatter.format(gridDomainValueY); + ctx.fillStyle = gridDomainValueY === 0 ? config.defaultFontColor : lineStyle; + ctx.fillRect( + -overhang, + y, + cartesianWidth + 2 * overhang, + gridDomainValueY === 0 ? 0.5 : guiConfig.implicit ? 0.2 : 0.1, + ); + ctx.fillStyle = config.subduedFontColor; + ctx.textAlign = 'left'; + ctx.fillText(text, cartesianWidth + overhang + gap, y); + ctx.textAlign = 'right'; + ctx.fillText(text, -overhang - gap, y); + } + ctx.restore(); + } + + return loHi; + }; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.js b/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.js new file mode 100644 index 0000000000..22538343fd --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.js @@ -0,0 +1,843 @@ +/* + * 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 * as dat from 'dat.gui'; + +import { cachedZonedDateTimeFrom, timeProp } from '../../xy_chart/axes/timeslip/chrono/cached_chrono'; +import { rasters } from '../../xy_chart/axes/timeslip/rasters'; +import { axisModel } from './axis_model'; +import { dataSource, getEnrichedData } from './data'; +import { domainTween } from './domain_tween'; +import { renderChartTitle } from './render/annotations/chart_title'; +import { renderTimeExtentAnnotation } from './render/annotations/time_extent'; +import { renderTimeUnitAnnotation } from './render/annotations/time_unit'; +import { renderCartesian } from './render/cartesian'; +import { renderDebugBox } from './render/glyphs/debug_box'; +import { elementSizes, zoomSafePointerX, zoomSafePointerY } from './utils/dom'; +import { observe, toCallbackFn } from './utils/generator'; +import { clamp, mix, unitClamp } from './utils/math'; +import { axisScale, getDesiredTickCount } from './utils/projection'; + +const panOngoing = (interactionState) => Number.isFinite(interactionState.dragStartX); + +/** + * noinspection JSUnusedGlobalSymbols + * @internal + */ +export const timeslipRender = (canvas /*: HTMLCanvasElement*/, ctx /*: CanvasRenderingContext2D*/, getData) => { + const processAction = toCallbackFn(handleEvents()); + + const initialDarkMode = false; + const drawCartesianBox = false; + + const singleValuedMetricsAggregationFunctionNames = { + sum: 'value', + min: 'minimum', + max: 'maximum', + avg: 'average', + cardinality: 'cardinality', + median_absolute_deviation: 'med abs dev', + rate: 'rate', + value_count: 'value count', + }; + + const aggregationFunctionNames = { + ...singleValuedMetricsAggregationFunctionNames, + }; + + const metricFieldNames = ['machine.ram', 'bytes', 'memory']; + + const minZoom = 0; + const maxZoom = 33; + + // these are hand tweaked constants that fulfill various design constraints, let's discuss before changing them + const lineThicknessSteps = [/*0,*/ 0.5, 0.75, 1, 1, 1, 1.25, 1.25, 1.5, 1.5, 1.75, 1.75, 2, 2, 2, 2, 2]; + const lumaSteps = [/*255,*/ 192, 72, 32, 16, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0]; + + const smallFontSize = 12; + const timeZone = 'Europe/Zurich'; + + const themeLight = { + defaultFontColor: 'black', + subduedFontColor: '#393939', + offHourFontColor: 'black', + weekendFontColor: 'darkred', + backgroundColor: { r: 255, g: 255, b: 255 }, + lumaSteps, + }; + + const themeDark = { + defaultFontColor: 'white', + subduedFontColor: 'darkgrey', + offHourFontColor: 'white', + weekendFontColor: 'indianred', + backgroundColor: { r: 0, g: 0, b: 0 }, + lumaSteps: lumaSteps.map((l) => 255 - l), + }; + + const gui = new dat.gui.GUI({ width: 200 }); + gui.domElement.parentElement.style['z-index'] = '1000000'; // might need a setTimeout in the future + gui.close(); + const config = { + darkMode: initialDarkMode, + sparse: false, + implicit: false, + maxLabelRowCount: 3, // can be 1, 2, 3 + queryConfig: { + metricFieldName: metricFieldNames[0], + aggregation: 'value_count', + boxplot: false, + window: 0, + alpha: 0.4, + beta: 0.2, + gamma: 0.2, + period: 1, + multiplicative: false, + binOffset: 0, + }, + a11y: { + shortcuts: true, + contrast: 'medium', + animation: true, + sonification: false, + }, + locale: 'en-US', + numUnit: 'short', + ...(initialDarkMode && themeDark), + ...(!initialDarkMode && themeLight), + barChroma: { r: 96, g: 146, b: 192 }, + barFillAlpha: 0.3, + lineThicknessSteps, + domainFrom: cachedZonedDateTimeFrom({ timeZone, year: 2012, month: 1, day: 1 })[timeProp.epochSeconds], + domainTo: cachedZonedDateTimeFrom({ timeZone, year: 2022, month: 1, day: 1 })[timeProp.epochSeconds], + minBinWidth: 'day', + maxBinWidth: 'year', + pixelRangeFrom: 100, + pixelRangeTo: 500, + tickLabelMaxProtrusionLeft: 0, // constraining not used yet + tickLabelMaxProtrusionRight: 0, // constraining not used yet + protrudeAxisLeft: true, // constraining not used yet + protrudeAxisRight: true, // constraining not used yet + smallFontSize, + cssFontShorthand: `normal normal 100 ${smallFontSize}px Inter, Helvetica, Arial, sans-serif`, + monospacedFontShorthand: `normal normal 100 ${smallFontSize}px "Roboto Mono", Consolas, Menlo, Courier, monospace`, + rowPixelPitch: 16, + horizontalPixelOffset: 4, + verticalPixelOffset: 6, + minimumTickPixelDistance: 24, + workHourMin: 6, + workHourMax: 21, + clipLeft: true, + clipRight: true, + }; + + const dpi = window.devicePixelRatio; + + const horizontalCartesianAreaPad = [0.04, 0.04]; + const verticalCartesianAreaPad = [0.12, 0.12]; + + const interactionState = { + // current zoom and pan level + zoom: 5.248, + pan: 0.961, + + // remembering touch points for zoom/pam + multitouch: [], + + // zoom/pan + dragStartX: NaN, + zoomStart: NaN, + panStart: NaN, + + // kinetic pan + lastDragX: NaN, + dragVelocity: NaN, + flyVelocity: NaN, + + // Y domain + niceDomainMin: NaN, + niceDomainMax: NaN, + + // other + screenDimensions: elementSizes(canvas, horizontalCartesianAreaPad, verticalCartesianAreaPad), + searchText: '', + }; + + const localeOptions = { + hour12: false, + year: 'numeric', + month: 'short', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }; + + const dataState = { + valid: false, + pending: false, + lo: { year: Infinity, month: 12, day: 31, hour: 23, minute: 59, second: 59 }, + hi: { year: -Infinity, month: 1, day: 1, hour: 0, minute: 0, second: 0 }, + binUnit: '', + binUnitCount: NaN, + queryConfig: {}, + dataResponse: { stats: {}, rows: [] }, + }; + + /** + * dat.gui event handlers + */ + + const updateGridAesthetics = ({ darkMode, sparse, implicit }) => { + // make implicit + const ls0 = implicit ? lumaSteps.map(() => 255) : lumaSteps; + const lst0 = implicit ? [1, ...lineThicknessSteps.slice(1)] : lineThicknessSteps; + + // sparsify + const ls1 = sparse ? [0, ...ls0] : ls0; + const lst1 = sparse ? [0, ...lst0] : lst0; + + // dark mode + const ls2 = darkMode ? ls1.map((d) => 255 - d) : ls1; + const lst2 = lst1; + + config.lumaSteps = ls2; + config.lineThicknessSteps = lst2; + }; + + const guiFolders = { + a11y: gui.addFolder('Accessibility'), + theme: gui.addFolder('User prefs'), + navigation: gui.addFolder('Navigation'), + query: gui.addFolder('Query'), + queryParams: gui.addFolder('Query params'), + i18n: gui.addFolder('Internationalization'), + }; + + guiFolders.query.open(); + + guiFolders.navigation + .add(interactionState, 'zoom') + .min(minZoom) + .max(maxZoom) + .step(0.001) + .onChange(scheduleChartRender) + .listen(); + guiFolders.navigation + .add(interactionState, 'pan') + .min(0) + .max(1) + .step(0.000001) + .onChange(scheduleChartRender) + .listen(); + + guiFolders.theme.add(config, 'darkMode').onChange(() => { + Object.assign(config, config.darkMode ? themeDark : themeLight); + updateGridAesthetics(config); + fullRender(); + }); + guiFolders.theme.add(config, 'sparse').onChange(() => { + updateGridAesthetics(config); + scheduleChartRender(); + }); + guiFolders.theme.add(config, 'implicit').onChange(() => { + updateGridAesthetics(config); + scheduleChartRender(); + }); + guiFolders.theme.add(config, 'maxLabelRowCount').min(2).max(3).step(1).onChange(scheduleChartRender); + + guiFolders.query.add(config.queryConfig, 'metricFieldName', metricFieldNames).onChange(scheduleChartRender); + guiFolders.query + .add( + config.queryConfig, + 'aggregation', + Object.fromEntries(Object.entries(aggregationFunctionNames).map(([k, v]) => [v, k])), + ) + .onChange(scheduleChartRender); + guiFolders.query + .add(config.queryConfig, 'boxplot') + .onChange((newVal) => { + if (newVal && config.queryConfig.window !== 0) { + config.queryConfig.window = 0; + } + scheduleChartRender(); + }) + .listen(); + guiFolders.queryParams + .add(config.queryConfig, 'window') + .min(0) + .max(10) + .step(1) + .onChange((newVal) => { + if (newVal > 0 && config.queryConfig.boxplot) { + config.queryConfig.boxplot = false; + } + scheduleChartRender(); + }) + .listen(); + guiFolders.queryParams.add(config.queryConfig, 'alpha').min(0).max(1).step(0.1).onChange(scheduleChartRender); + guiFolders.queryParams.add(config.queryConfig, 'beta').min(0).max(1).step(0.1).onChange(scheduleChartRender); + guiFolders.queryParams.add(config.queryConfig, 'gamma').min(0).max(1).step(0.1).onChange(scheduleChartRender); + guiFolders.queryParams.add(config.queryConfig, 'period').min(0).max(25).step(1).onChange(scheduleChartRender); + guiFolders.queryParams.add(config.queryConfig, 'multiplicative').onChange(scheduleChartRender); + guiFolders.queryParams.add(config.queryConfig, 'binOffset').min(-30).max(30).step(1).onChange(scheduleChartRender); + + let rasterSelector = rasters(config, timeZone); + + guiFolders.i18n + .add(config, 'locale', { + Arabic: 'ar-TN', + German: 'de-CH', + French: 'fr-FR', + 'US English': 'en-US', + Greek: 'el-GR', + Hungarian: 'hu-HU', + Hebrew: 'he-IL', + Hindi: 'hi-IN', + Italian: 'it-IT', + Japanese: 'ja-JA', + Russian: 'ru-RU', + }) + .onChange((value) => { + canvas.setAttribute('dir', ['he-IL', 'ar-TN'].includes(value) ? 'rtl' : 'ltr'); + rasterSelector = rasters(config, timeZone); + scheduleChartRender(); + }); + + guiFolders.i18n.add(config, 'numUnit', ['none', 'short', 'long']).onChange(scheduleChartRender); + guiFolders.a11y.add(config.a11y, 'shortcuts').onChange(scheduleChartRender); + guiFolders.a11y.add(config.a11y, 'contrast', ['low', 'medium', 'high']).onChange(scheduleChartRender); + guiFolders.a11y.add(config.a11y, 'animation').onChange(scheduleChartRender); + guiFolders.a11y.add(config.a11y, 'sonification').onChange(scheduleChartRender); + + // todo this may need an update with locale change + const defaultLabelFormat = new Intl.DateTimeFormat(config.locale, { + weekday: 'short', + hour: 'numeric', + minute: 'numeric', + timeZone, + }).format; + + // todo this may need an update with locale change + const defaultMinorTickLabelFormat = new Intl.DateTimeFormat(config.locale, { + weekday: 'short', + hour: 'numeric', + minute: 'numeric', + timeZone, + }).format; + + const fadeOutPixelWidth = 12; // todo add to config + + const invalid = (dataDemand) => { + return ( + !dataState.valid || + dataState.binUnit !== dataDemand.binUnit || + dataState.binUnitCount !== dataDemand.binUnitCount || + dataDemand.lo.timePointSec < dataState.lo.timePointSec || + dataDemand.hi.timePointSec > dataState.hi.timePointSec || + dataState.queryConfig !== JSON.stringify(config.queryConfig) || + dataState.searchText !== interactionState.searchText + ); + }; + + const updateDataState = (dataDemand, config, dataResponse, interactionState) => { + dataState.pending = false; + dataState.valid = true; + dataState.lo = dataDemand.lo; + dataState.hi = dataDemand.hi; + dataState.binUnit = dataDemand.binUnit; + dataState.binUnitCount = dataDemand.binUnitCount; + dataState.queryConfig = JSON.stringify(config.queryConfig); + dataState.dataResponse = dataResponse; + dataState.searchText = interactionState.searchText; + }; + + const yTickNumberFormatter = new Intl.NumberFormat( + config.locale, + config.numUnit === 'none' + ? {} + : { + notation: 'compact', + compactDisplay: config.numUnit, + }, + ); + + // constants for Y + const ZERO_Y_BASE = true; + + const emWidth = ctx.measureText('mmmmmmmmmm').width / 10; // approx width to avoid too many measurements + + let canvasWidth = NaN; + let canvasHeight = NaN; + + const fromSec = config.domainFrom; + const toSec = config.domainTo; + const fullTimeExtent = toSec - fromSec; + + const zoomMultiplier = () => 2 ** interactionState.zoom; + + let rAF = -1; + let prevT = 0; + + const timedRender = (t) => { + const deltaT = t - prevT; + prevT = t; + chartWithTime(ctx, config, interactionState, deltaT); + }; + + function scheduleChartRender() { + window.cancelAnimationFrame(rAF); + rAF = window.requestAnimationFrame(timedRender); + } + + function doCartesian( + ctx, + cartesianHeight, + config, + interactionState, + deltaT, + cartesianWidth, + timeDomainFrom, + timeDomainTo, + ) { + ctx.save(); + ctx.translate(0, cartesianHeight); + + const domainLandmarks = [ + dataState.dataResponse.stats.minValue, + dataState.dataResponse.stats.maxValue, + ...(ZERO_Y_BASE ? [0] : []), + ]; + const desiredTickCount = getDesiredTickCount(cartesianHeight, config.smallFontSize, config.sparse); + const { niceDomainMin, niceDomainMax, niceTicks } = axisModel(domainLandmarks, desiredTickCount); + const yTweenOngoing = domainTween(interactionState, deltaT, niceDomainMin, niceDomainMax); // updates interactionState + // const panTweenOngoing = interactionState; + const yUnitScale = axisScale(interactionState.niceDomainMin, interactionState.niceDomainMax); + const yUnitScaleClamped = (d) => unitClamp(yUnitScale(d)); + + const dataDemand = renderCartesian( + ctx, + config, + dataState, + config, + defaultMinorTickLabelFormat, + emWidth, + fadeOutPixelWidth, + defaultLabelFormat, + yTickNumberFormatter, + rasterSelector, + cartesianWidth, + cartesianHeight, + { + domainFrom: timeDomainFrom, + domainTo: timeDomainTo, + }, + yUnitScale, + yUnitScaleClamped, + niceTicks, + ); + + ctx.restore(); + + return { yTweenOngoing, dataDemand }; + } + + function getTimeDomain() { + const { pan } = interactionState; + const zoomedTimeExtent = fullTimeExtent / zoomMultiplier(); + const leeway = fullTimeExtent - zoomedTimeExtent; + const timeDomainFrom = fromSec + pan * leeway; + const timeDomainTo = toSec - (1 - pan) * leeway; + return { timeDomainFrom, timeDomainTo }; + } + + function ensureCanvasElementSize(newCanvasWidth, newCanvasHeight) { + if (newCanvasWidth !== canvasWidth) { + canvas.setAttribute('width', String(newCanvasWidth)); + canvasWidth = newCanvasWidth; + } + if (newCanvasHeight !== canvasHeight) { + canvas.setAttribute('height', String(newCanvasHeight)); + canvasHeight = newCanvasHeight; + } + } + + function renderChartWithTime( + ctx, + backgroundFillStyle, + newCanvasWidth, + newCanvasHeight, + config, + chartWidth, + cartesianTop, + aggregationFunctionName, + cartesianLeft, + cartesianHeight, + interactionState, + deltaT, + cartesianWidth, + timeDomainFrom, + timeDomainTo, + drawCartesianBox, + chartTopFontSize, + ) { + ctx.save(); + ctx.scale(dpi, dpi); + ctx.fillStyle = backgroundFillStyle; + // clearRect is not enough, as browser image copy ignores canvas background color + ctx.fillRect(0, 0, newCanvasWidth, newCanvasHeight); + + // chart title + renderChartTitle(ctx, config, chartWidth, cartesianTop, aggregationFunctionName); + + ctx.translate(cartesianLeft, cartesianTop); + + // cartesian + const { yTweenOngoing, dataDemand } = doCartesian( + ctx, + cartesianHeight, + config, + interactionState, + deltaT, + cartesianWidth, + timeDomainFrom, + timeDomainTo, + ); + + // cartesian area box + if (drawCartesianBox) { + renderDebugBox(ctx, cartesianWidth, cartesianHeight); + } + + // chart time unit info + renderTimeUnitAnnotation( + ctx, + config, + dataDemand.binUnitCount, + dataDemand.binUnit, + chartTopFontSize, + dataDemand.unitBarMaxWidthPixels, + ); + + // chart time from/to extent info + renderTimeExtentAnnotation( + ctx, + config, + localeOptions, + timeDomainFrom, + timeDomainTo, + cartesianWidth, + chartTopFontSize, + ); + + ctx.restore(); + return { yTweenOngoing, dataDemand }; + } + + const dataArrived = ({ dataDemand, dataResponse }) => { + updateDataState(dataDemand, config, dataResponse, interactionState); + scheduleChartRender(); + }; + + function chartWithTime(ctx, config, interactionState, deltaT) { + const { + outerWidth: chartWidth, + outerHeight: chartHeight, + innerLeft: cartesianLeft, + innerWidth: cartesianWidth, + innerTop: cartesianTop, + innerHeight: cartesianHeight, + } = interactionState.screenDimensions; + + const { timeDomainFrom, timeDomainTo } = getTimeDomain(); + + const qc = config.queryConfig; + const aggregationFunctionName = aggregationFunctionNames[qc.aggregation]; + const chartTopFontSize = config.smallFontSize + 2; // todo move to config + const background = config.backgroundColor; + const backgroundFillStyle = `rgba(${background.r},${background.g},${background.b},1)`; + + // resize if needed + const newCanvasWidth = dpi * chartWidth; + const newCanvasHeight = dpi * chartHeight; + ensureCanvasElementSize(newCanvasWidth, newCanvasHeight); + + // render chart + const { yTweenOngoing, dataDemand } = renderChartWithTime( + ctx, + backgroundFillStyle, + newCanvasWidth, + newCanvasHeight, + config, + chartWidth, + cartesianTop, + aggregationFunctionName, + cartesianLeft, + cartesianHeight, + interactionState, + deltaT, + cartesianWidth, + timeDomainFrom, + timeDomainTo, + drawCartesianBox, + chartTopFontSize, + ); + + if ( + !dataState.pending && + invalid(dataDemand) && + dataDemand.lo && + dataDemand.hi && + dataDemand.binUnit && + dataDemand.binUnitCount + ) { + dataState.pending = true; + processAction({ + dataDemand, + target: dataSource, + type: 'dataArrived', + dataResponse: getEnrichedData(getData(dataDemand)), + }); + } else if (yTweenOngoing) { + scheduleChartRender(); + } + } + + const setDomElements = () => { + const chartSizeInfo = elementSizes(canvas, horizontalCartesianAreaPad, verticalCartesianAreaPad); + interactionState.screenDimensions = chartSizeInfo; + const { r, g, b } = config.backgroundColor; + const backgroundColorCSS = `rgb(${r},${g},${b})`; + document.body.style.backgroundColor = backgroundColorCSS; + }; + + const fullRender = () => { + setDomElements(); + scheduleChartRender(); + }; + + fullRender(); + + /** + * event listener utils + */ + + const getPanDeltaPerDragPixel = () => 1 / ((zoomMultiplier() - 1) * interactionState.screenDimensions.innerWidth); + + const panFromDeltaPixel = (panStart, delta) => { + const panDeltaPerDragPixel = getPanDeltaPerDragPixel(); + interactionState.pan = Math.max(0, Math.min(1, panStart - panDeltaPerDragPixel * delta)) || 0; + }; + + const inCartesianBand = (e) => { + const y = zoomSafePointerY(e); + const { innerTop: cartesianTop, innerBottom: cartesianBottom } = interactionState.screenDimensions; + return cartesianTop <= y && y <= cartesianBottom; + }; + + const inCartesianArea = (e) => { + const x = zoomSafePointerX(e); + const y = zoomSafePointerY(e); + const { innerTop, innerBottom, innerLeft, innerRight } = interactionState.screenDimensions; + return innerLeft <= x && x <= innerRight && innerTop <= y && y <= innerBottom; + }; + + /** + * event handlers + */ + + const zoom = (pointerUnitLocation, newZoom, panDelta = 0) => { + const oldInvisibleFraction = 1 - 1 / zoomMultiplier(); + interactionState.zoom = clamp(newZoom, minZoom, maxZoom); + const newInvisibleFraction = 1 - 1 / zoomMultiplier(); + interactionState.pan = + unitClamp( + mix(pointerUnitLocation + panDelta, interactionState.pan, oldInvisibleFraction / newInvisibleFraction), + ) || 0; + }; + + const zoomAroundX = (centerX, newZoom, panDelta = 0) => { + const { innerWidth: cartesianWidth, innerLeft: cartesianLeft } = interactionState.screenDimensions; + const unitZoomCenter = Math.max(0, Math.min(cartesianWidth, centerX - cartesianLeft)) / cartesianWidth; + zoom(unitZoomCenter, newZoom, panDelta); + }; + + const pan = (normalizedDeltaPan) => { + const deltaPan = normalizedDeltaPan / 2 ** interactionState.zoom; + interactionState.pan = unitClamp(interactionState.pan + deltaPan) || 0; + }; + + // these two change together: the kinetic friction deceleration from a click drag, and from a wheel drag should match + // currently, the narrower the chart, the higher the deceleration, which is perhaps better than width invariant slowing + const dragVelocityAttenuation = 0.92; + const wheelPanVelocityDivisor = 1000; + + const wheelZoomVelocityDivisor = 250; + const keyZoomVelocityDivisor = 2; // 1 means, on each up/down keypress, double/halve the visible time domain + const keyPanVelocityDivisor = 10; // 1 means, on each left/right keypress, move the whole of current visible time domain + + const wheel = (e) => { + if (!inCartesianBand(e)) return; + + if (e.metaKey) { + pan(-e.deltaY / wheelPanVelocityDivisor); + } else { + const centerX = zoomSafePointerX(e); + const newZoom = interactionState.zoom - e.deltaY / wheelZoomVelocityDivisor; + zoomAroundX(centerX, newZoom); + } + + scheduleChartRender(); + }; + + const dragStartAtX = (startingX) => { + interactionState.dragStartX = startingX; + interactionState.lastDragX = startingX; + interactionState.dragVelocity = NaN; + interactionState.flyVelocity = NaN; + interactionState.panStart = interactionState.pan; + }; + + const dragStart = (e) => dragStartAtX(zoomSafePointerX(e)); + + const kineticDragHandler = (t) => { + const velocity = interactionState.flyVelocity; + if (Math.abs(velocity) > 0.01) { + panFromDeltaPixel(interactionState.pan, velocity); + interactionState.flyVelocity *= dragVelocityAttenuation; + timedRender(t); + window.requestAnimationFrame(kineticDragHandler); + } else { + interactionState.flyVelocity = NaN; + } + }; + + const dragEnd = () => { + interactionState.flyVelocity = interactionState.dragVelocity; + interactionState.dragVelocity = NaN; + interactionState.dragStartX = NaN; + interactionState.panStart = NaN; + window.requestAnimationFrame(kineticDragHandler); + }; + + const panFromX = (currentX) => { + const deltaX = currentX - interactionState.lastDragX; + const { dragVelocity } = interactionState; + interactionState.dragVelocity = + deltaX * dragVelocity > 0 && Math.abs(deltaX) < Math.abs(dragVelocity) + ? dragVelocity // mix(dragVelocity, deltaX, 0.04) + : deltaX; + interactionState.lastDragX = currentX; + const delta = currentX - interactionState.dragStartX; + panFromDeltaPixel(interactionState.panStart, delta); + return delta; + }; + + const touchMidpoint = (multitouch) => (multitouch[0].x + multitouch[1].x) / 2; + + const touchmove = (e) => { + const multitouch = [...(e.touches ?? [])] + .map((t) => ({ + id: t.identifier, + x: zoomSafePointerX(t), + })) + .sort(({ x: a }, { x: b }) => a - b); + + if (interactionState.multitouch.length === 0 && multitouch.length === 2) { + interactionState.multitouch = multitouch; + interactionState.zoomStart = interactionState.zoom; + const centerX = touchMidpoint(multitouch); + dragStartAtX(centerX); + } else if ( + multitouch.length !== 2 || + [...multitouch, ...interactionState.multitouch].filter((t, i, a) => a.findIndex((tt) => tt.id === t.id) === i) + .length !== 2 + ) { + interactionState.multitouch = []; + interactionState.zoomStart = NaN; + /* + interactionState.dragStartX = NaN + interactionState.lastDragX = NaN + // interactionState.dragVelocity = NaN + // interactionState.flyVelocity = NaN + interactionState.panStart = NaN + */ + } + if (interactionState.multitouch.length === 2) { + const centerX = touchMidpoint(multitouch); + const zoomMultiplier = + (multitouch[1].x - multitouch[0].x) / (interactionState.multitouch[1].x - interactionState.multitouch[0].x); + const panDelta = 0; // panFromX(centerX) + zoomAroundX(centerX, interactionState.zoomStart + Math.log2(zoomMultiplier), panDelta); + scheduleChartRender(); + } else if (inCartesianArea(e) || Number.isFinite(interactionState.panStart)) { + if (!panOngoing(interactionState)) { + dragStart(e); + } else { + const currentX = zoomSafePointerX(e); + panFromX(currentX); + scheduleChartRender(); + } + } + }; + + const touchstart = (e) => inCartesianArea(e) && dragStart(e); + const touchend = dragEnd; + const mousedown = touchstart; + const mousemove = (e) => e.buttons === 1 && touchmove(e); + const mouseup = touchend; + const touchcancel = touchend; + + const chartKeydown = (e) => { + const panDirection = { ArrowLeft: -1, ArrowRight: 1 }[e.code]; + const zoomDirection = { ArrowUp: -1, ArrowDown: 1 }[e.code]; + if (panDirection || zoomDirection) { + if (panDirection) pan(panDirection / keyPanVelocityDivisor); + if (zoomDirection) zoom(0.5, interactionState.zoom + zoomDirection / keyZoomVelocityDivisor); + e.preventDefault(); // preventDefault needed because otherwise a right arrow key takes the user to the next element + scheduleChartRender(); + } + }; + + const resize = () => fullRender(); + + /** + * attaching event handlers + */ + + const eventHandlersForWindow = { resize }; + const eventHandlersForCanvas = { + wheel, + mousemove, + mousedown, + mouseup, + touchmove, + touchstart, + touchend, + touchcancel, + keydown: chartKeydown, + }; + const eventHandlersForData = { dataArrived }; + + const eventHandlers = new Map([ + [window, eventHandlersForWindow], + [canvas, eventHandlersForCanvas], + [dataSource, eventHandlersForData], + ]); + + function* handleEvents() { + for (;;) { + const e = yield; + const handler = eventHandlers.get(e.target)[e.type]; + if (handler) handler(e); + } + } + + observe(window, processAction, eventHandlersForWindow); + observe(canvas, processAction, eventHandlersForCanvas); +}; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/translations.js b/packages/charts/src/chart_types/timeslip/timeslip/translations.js new file mode 100644 index 0000000000..f0d526ae48 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/translations.js @@ -0,0 +1,220 @@ +/* + * 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. + */ + +/** @internal */ +export const uiStrings = { + 'ar-TN': { + bar: 'حاجز', + year: 'سنة', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + hour: 'ساعة', + minute: 'دقيقة', + second: 'ثانية', + millisecond: 'مللي ثانية', + years: 'سنوات', + months: 'اشهر', + weeks: 'أسابيع', + days: 'أيام', + hours: 'ساعات', + minutes: 'دقائق', + seconds: 'ثواني', + milliseconds: 'مللي ثانية', + }, + 'de-CH': { + bar: 'Balken', + year: 'Jahr', + month: 'Monat', + week: 'Woche', + day: 'Tag', + hour: 'Stunde', + minute: 'Minute', + second: 'Sekunde', + millisecond: 'Millisekunde', + years: 'Jahre', + months: 'Monate', + weeks: 'Wochen', + days: 'Tage', + hours: 'Stunden', + minutes: 'Minuten', + seconds: 'Sekunden', + milliseconds: 'Millisekunden', + }, + 'fr-FR': { + bar: 'barre', + year: 'année', + month: 'mois', + week: 'semaine', + day: 'jour', + hour: 'heure', + minute: 'minute', + second: 'seconde', + millisecond: 'milliseconde', + years: 'ans', + months: 'mois', + weeks: 'semaines', + days: 'jours', + hours: 'heures', + minutes: 'minutes', + seconds: 'secondes', + milliseconds: 'millisecondes', + }, + 'en-US': { + bar: 'bar', + year: 'year', + month: 'month', + week: 'week', + day: 'day', + hour: 'hour', + minute: 'minute', + second: 'second', + millisecond: 'millisecond', + years: 'years', + months: 'months', + weeks: 'weeks', + days: 'days', + hours: 'hours', + minutes: 'minutes', + seconds: 'seconds', + milliseconds: 'milliseconds', + }, + 'el-GR': { + bar: 'γραμμή', + year: 'χρόνος', + month: 'μήνα', + week: 'εβδομάδα', + day: 'μέρα', + hour: 'ώρα', + minute: 'λεπτό', + second: 'δευτερόλεπτο', + millisecond: 'χιλιοστό του δευτερολέπτου', + years: 'μήνες', + months: 'months', + weeks: 'εβδομάδες', + days: 'ημέρες', + hours: 'ώρες', + minutes: 'λεπτά', + seconds: 'δευτερόλεπτα', + milliseconds: 'χιλιοστά του δευτερολέπτου', + }, + 'hu-HU': { + bar: 'oszlop', + year: 'év', + month: 'hónap', + week: 'hét', + day: 'nap', + hour: 'óra', + minute: 'perc', + second: 'másodperc', + millisecond: 'ezredmásodperc', + years: 'év', + months: 'hónap', + weeks: 'hét', + days: 'nap', + hours: 'óra', + minutes: 'perc', + seconds: 'másodperc', + milliseconds: 'ezredmásodperc', + }, + 'he-IL': { + bar: 'עַמוּדָה', + year: 'שנה', + month: 'חודש', + week: 'שבוע', + day: 'יום', + hour: 'שעה', + minute: 'דקות', + second: 'השני', + millisecond: 'אלפית השנייה', + years: 'years', + months: 'חודשים', + weeks: 'שבועות', + days: 'ימים', + hours: 'שעות', + minutes: 'דקות', + seconds: 'שניות', + milliseconds: 'אלפיות השנייה', + }, + 'hi-IN': { + bar: 'बार', + year: 'वर्ष', + month: 'महीना', + week: 'सप्ताह', + day: 'दिन', + hour: 'घंटा', + minute: 'मिनट', + second: 'सेकंड', + millisecond: 'मिलीसेकंड', + years: 'साल', + months: 'महीने', + weeks: 'सप्ताह', + days: 'दिन', + hours: 'घंटे', + minutes: 'मिनट', + seconds: 'सेकंड', + milliseconds: 'मिलीसेकेंड', + }, + 'it-IT': { + bar: 'barra', + year: 'anno', + month: 'mese', + week: 'settimana', + day: 'giorno', + hour: 'ora', + minute: 'minuto', + second: 'secondo', + millisecond: 'millisecondo', + years: 'anni', + months: 'mesi', + weeks: 'settimane', + days: 'giorni', + hours: 'ore', + minutes: 'minuti', + seconds: 'secondi', + milliseconds: 'millisecondi', + }, + 'ja-JA': { + bar: '棒', + year: '年', + month: 'ヶ月', + week: '週間', + day: '日', + hour: '時間', + minute: '分', + second: '秒', + millisecond: 'ミリ秒', + years: '年間', + months: 'ヵ月', + weeks: '週間', + days: '日間', + hours: '時間', + minutes: '分間', + seconds: '秒間', + milliseconds: 'ミリ秒', + }, + 'ru-RU': { + bar: 'полоса', + year: 'год', + month: 'месяц', + week: 'неделя', + day: 'день', + hour: 'час', + minute: 'минута', + second: 'секунда', + millisecond: 'миллисекунда', + years: 'лет', + months: 'месяцев', + weeks: 'недель', + days: 'дней', + hours: 'часов', + minutes: 'минут', + seconds: 'секунд', + milliseconds: 'миллисекунд', + }, +}; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/utils/dom.js b/packages/charts/src/chart_types/timeslip/timeslip/utils/dom.js new file mode 100644 index 0000000000..9528559a5f --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/utils/dom.js @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/** @internal */ +export const zoomSafePointerX = (e) => e.layerX ?? e.clientX; // robust against Chrome, Safari, Firefox menu zooms and/or pinch zoom (FF needs zero margin) + +/** @internal */ +export const zoomSafePointerY = (e) => e.layerY ?? e.clientX; // robust against Chrome, Safari, Firefox menu zooms and/or pinch zoom (FF needs zero margin) + +/** @internal */ +export const elementSizes = (canvas, horizontalPad, verticalPad) => { + const { width: outerWidth, height: outerHeight } = canvas.getBoundingClientRect(); + + const innerLeft = outerWidth * horizontalPad[0]; + const innerWidth = outerWidth * (1 - horizontalPad.reduce((p, n) => p + n)); + const innerRight = innerLeft + innerWidth; + + const innerTop = outerHeight * verticalPad[0]; + const innerHeight = outerHeight * (1 - verticalPad.reduce((p, n) => p + n)); + const innerBottom = innerTop + innerHeight; + + return { + outerWidth: outerWidth, + outerHeight, + innerLeft, + innerRight, + innerWidth, + innerTop, + innerBottom, + innerHeight, + }; +}; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/utils/generator.js b/packages/charts/src/chart_types/timeslip/timeslip/utils/generator.js new file mode 100644 index 0000000000..7575310216 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/utils/generator.js @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** @internal */ +export const toCallbackFn = (generatorObject) => { + generatorObject.next(); // this starts the generator object, eg. resulting in initial render without any events + return (event) => generatorObject.next(event); +}; + +/** @internal */ +export const observe = (eventTarget, commonHandler, handlers) => { + for (const eventName in handlers) eventTarget.addEventListener(eventName, commonHandler, { passive: false }); + // the returned function allows the removal of the event listeners if needed + return () => { + for (const eventName in handlers) eventTarget.removeEventListener(eventName, commonHandler); + }; +}; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/utils/math.js b/packages/charts/src/chart_types/timeslip/timeslip/utils/math.js new file mode 100644 index 0000000000..44bec1e232 --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/utils/math.js @@ -0,0 +1,14 @@ +/* + * 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. + */ + +/** @internal */ +export const mix = (start, end, a) => start * (1 - a) + end * a; // like the glsl function +/** @internal */ +export const clamp = (n, lo, hi) => (n < lo ? lo : n > hi ? hi : n); +/** @internal */ +export const unitClamp = (n) => (n < 0 ? 0 : n > 1 ? 1 : n); diff --git a/packages/charts/src/chart_types/timeslip/timeslip/utils/projection.js b/packages/charts/src/chart_types/timeslip/timeslip/utils/projection.js new file mode 100644 index 0000000000..14efdfaf4f --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip/utils/projection.js @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** @internal */ +export const getDesiredTickCount = (cartesianHeight, fontSize, sparse) => { + const desiredMaxTickCount = Math.floor(cartesianHeight / (3 * fontSize)); + return sparse ? 1 + Math.ceil(Math.pow(desiredMaxTickCount, 0.25)) : 1 + Math.ceil(Math.sqrt(desiredMaxTickCount)); +}; + +/** @internal */ +export const axisScale = (niceDomainMin, niceDomainMax) => { + const niceDomainExtent = niceDomainMax - niceDomainMin; + const yScaleMultiplier = 1 / (niceDomainExtent || 1); + const offset = -niceDomainMin * yScaleMultiplier; + return (d) => offset + d * yScaleMultiplier; +}; diff --git a/packages/charts/src/chart_types/timeslip/timeslip_api.ts b/packages/charts/src/chart_types/timeslip/timeslip_api.ts new file mode 100644 index 0000000000..78d242fbca --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip_api.ts @@ -0,0 +1,62 @@ +/* + * 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 { Spec } from '../../specs'; +import { SpecType } from '../../specs/constants'; // kept as long-winded import on separate line otherwise import circularity emerges +import { buildSFProps, SFProps, useSpecFactory } from '../../state/spec_factory'; +import { stripUndefined } from '../../utils/common'; +import { TimeBin, TimeRaster } from '../xy_chart/axes/timeslip/rasters'; + +/** + * data getter function + * @public + */ +export type GetData = (dataDemand: { + lo: TimeBin; // iirc TimeBin is enough, and the other TimeRaster etc. props aren't needed + hi: TimeBin; // iirc TimeBin is enough, and the other TimeRaster etc. props aren't needed + binUnit: TimeRaster['unit']; // as of the initial commit, it's just a string + binUnitCount: number; + unitBarMaxWidthPixels: number; +}) => Array<{ epochMs: number; value: number }>; + +/** + * Specifies the timeslip chart + * @public + */ +export interface TimeslipSpec extends Spec { + specType: typeof SpecType.Series; + chartType: typeof ChartType.Timeslip; + getData: GetData; +} + +const buildProps = buildSFProps()( + { + chartType: ChartType.Timeslip, + specType: SpecType.Series, + }, + {}, +); + +/** + * Adds timeslip spec to chart specs + * @public + */ +export const Timeslip = ( + props: SFProps< + TimeslipSpec, + keyof typeof buildProps['overrides'], + keyof typeof buildProps['defaults'], + keyof typeof buildProps['optionals'], + keyof typeof buildProps['requires'] + >, +) => { + const { defaults, overrides } = buildProps; + useSpecFactory({ ...defaults, ...stripUndefined(props), ...overrides }); + return null; +}; diff --git a/packages/charts/src/chart_types/timeslip/timeslip_chart.tsx b/packages/charts/src/chart_types/timeslip/timeslip_chart.tsx new file mode 100644 index 0000000000..610349cc5a --- /dev/null +++ b/packages/charts/src/chart_types/timeslip/timeslip_chart.tsx @@ -0,0 +1,158 @@ +/* + * 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 React, { CSSProperties, RefObject } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; + +import { ChartType } from '..'; +import { DEFAULT_CSS_CURSOR } from '../../common/constants'; +import { SettingsSpec, SpecType, TooltipType } from '../../specs'; +import { onChartRendered } from '../../state/actions/chart'; +import { BackwardRef, GlobalChartState } from '../../state/chart_state'; +import { getA11ySettingsSelector } from '../../state/selectors/get_accessibility_config'; +import { getSettingsSpecSelector } from '../../state/selectors/get_settings_spec'; +import { getTooltipSpecSelector } from '../../state/selectors/get_tooltip_spec'; +import { getSpecsFromStore } from '../../state/utils'; +import { Size } from '../../utils/dimensions'; +import { roundUpSize } from '../flame_chart/render/common'; +// @ts-ignore until it becomes TS +import { timeslipRender } from './timeslip/timeslip_render'; +import { TimeslipSpec, GetData } from './timeslip_api'; + +interface StateProps { + getData: GetData; + chartDimensions: Size; + a11ySettings: ReturnType; + tooltipRequired: boolean; + onElementOver: NonNullable; + onElementClick: NonNullable; + onElementOut: NonNullable; + onRenderChange: NonNullable; +} + +interface DispatchProps { + onChartRendered: typeof onChartRendered; +} + +interface OwnProps { + containerRef: BackwardRef; + forwardStageRef: RefObject; +} + +type TimeslipProps = StateProps & DispatchProps & OwnProps; + +class TimeslipComponent extends React.Component { + static displayName = 'Timeslip'; + + // DOM API Canvas2d and WebGL resources + private ctx: CanvasRenderingContext2D | null = null; + + componentDidMount = () => { + /* + * the DOM element has just been appended, and getContext('2d') is always non-null, + * so we could use a couple of ! non-null assertions but no big plus + */ + this.tryCanvasContext(); + this.drawCanvas(); + this.props.onChartRendered(); + this.props.containerRef().current?.addEventListener('wheel', (e) => e.preventDefault(), { passive: false }); + + timeslipRender(this.props.forwardStageRef.current, this.ctx, this.props.getData); + }; + + componentWillUnmount() { + this.props.containerRef().current?.removeEventListener('wheel', (e) => e.preventDefault()); + } + + componentDidUpdate = () => { + if (!this.ctx) this.tryCanvasContext(); + }; + + render = () => { + const { + forwardStageRef, + chartDimensions: { width: requestedWidth, height: requestedHeight }, + a11ySettings, + } = this.props; + const width = roundUpSize(requestedWidth); + const height = roundUpSize(requestedHeight); + const style: CSSProperties = { + width, + height, + top: 0, + left: 0, + padding: 0, + margin: 0, + border: 0, + position: 'absolute', + cursor: DEFAULT_CSS_CURSOR, + }; + const dpr = window.devicePixelRatio; /* * this.pinchZoomScale */ + const canvasWidth = width * dpr; + const canvasHeight = height * dpr; + return ( + <> +
+ +
+ + ); + }; + + private drawCanvas = () => { + if (!this.ctx) return; + this.props.onRenderChange(true); // emit API callback + }; + + private tryCanvasContext = () => { + const canvas = this.props.forwardStageRef.current; + this.ctx = canvas && canvas.getContext('2d'); + }; +} + +const mapStateToProps = (state: GlobalChartState): StateProps => { + const timeslipSpec = getSpecsFromStore(state.specs, ChartType.Timeslip, SpecType.Series)[0]; + const settingsSpec = getSettingsSpecSelector(state); + return { + getData: timeslipSpec?.getData ?? (() => []), + chartDimensions: state.parentDimensions, + a11ySettings: getA11ySettingsSelector(state), + tooltipRequired: getTooltipSpecSelector(state).type !== TooltipType.None, + + // mandatory charts API protocol; todo extract these mappings once there are other charts like Timeslip + onElementOver: settingsSpec.onElementOver ?? (() => {}), + onElementClick: settingsSpec.onElementClick ?? (() => {}), + onElementOut: settingsSpec.onElementOut ?? (() => {}), + onRenderChange: settingsSpec.onRenderChange ?? (() => {}), // todo eventually also update data props on a local .echChartStatus element: data-ech-render-complete={rendered} data-ech-render-count={renderedCount} data-ech-debug-state={debugStateString} + }; +}; + +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => + bindActionCreators( + { + onChartRendered, + }, + dispatch, + ); + +const TimeslipChartLayers = connect(mapStateToProps, mapDispatchToProps)(TimeslipComponent); + +/** @internal */ +export const TimeslipWithTooltip = (containerRef: BackwardRef, forwardStageRef: RefObject) => ( + +); diff --git a/packages/charts/src/index.ts b/packages/charts/src/index.ts index 3c7590c22f..bb94735a4f 100644 --- a/packages/charts/src/index.ts +++ b/packages/charts/src/index.ts @@ -137,4 +137,5 @@ export { GroupKeysOrKeyFn, GroupByKeyFn } from './chart_types/xy_chart/utils/gro export { computeRatioByGroups } from './utils/data/data_processing'; export { TimeFunction } from './utils/time_functions'; export * from './chart_types/flame_chart/flame_api'; +export * from './chart_types/timeslip/timeslip_api'; export { LegacyAnimationConfig } from './common/animation'; diff --git a/packages/charts/src/state/chart_state.ts b/packages/charts/src/state/chart_state.ts index 29d8b93bfb..d03549293d 100644 --- a/packages/charts/src/state/chart_state.ts +++ b/packages/charts/src/state/chart_state.ts @@ -14,6 +14,7 @@ import { GoalState } from '../chart_types/goal_chart/state/chart_state'; import { HeatmapState } from '../chart_types/heatmap/state/chart_state'; import { MetricState } from '../chart_types/metric/state/chart_state'; import { PartitionState } from '../chart_types/partition_chart/state/chart_state'; +import { TimeslipState } from '../chart_types/timeslip/internal_chart_state'; import { WordcloudState } from '../chart_types/wordcloud/state/chart_state'; import { XYAxisChartState } from '../chart_types/xy_chart/state/chart_state'; import { CategoryKey } from '../common/category'; @@ -430,6 +431,7 @@ const constructors: Record InternalChartState | null> = { [ChartType.Goal]: () => new GoalState(), [ChartType.Partition]: () => new PartitionState(), [ChartType.Flame]: () => new FlameState(), + [ChartType.Timeslip]: () => new TimeslipState(), [ChartType.XYAxis]: () => new XYAxisChartState(), [ChartType.Heatmap]: () => new HeatmapState(), [ChartType.Wordcloud]: () => new WordcloudState(), diff --git a/storybook/stories/timeslip/01_timeslip.story.tsx b/storybook/stories/timeslip/01_timeslip.story.tsx new file mode 100644 index 0000000000..ad6a54976c --- /dev/null +++ b/storybook/stories/timeslip/01_timeslip.story.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { action } from '@storybook/addon-actions'; +import { button } from '@storybook/addon-knobs'; +import React from 'react'; + +import { + Chart, + Timeslip, + Settings, + PartialTheme, + FlameGlobalControl, + FlameNodeControl, + GetData, +} from '@elastic/charts'; + +import { useBaseTheme } from '../../use_base_theme'; + +const getData = (dataDemand: Parameters[0]) => { + const from = dataDemand.lo.timePointSec; + const to = dataDemand.hi.nextTimePointSec; + const binWidth = dataDemand.lo.nextTimePointSec - from; + const result = []; + let time = from; + while (time < to) { + result.push({ + epochMs: (time + 0.5 * binWidth) * 1000, + value: + 8 * Math.sin(time / 100000000) + + 4 * Math.sin(time / 1000000) + + 2 * Math.sin(time / 10000) + + Math.sin(time / 100) + + 0.5 * Math.sin(time) + + 0.25 * Math.sin(time * 100) + + 0.125 * Math.sin(time * 10000), + }); + time += binWidth; + } + return result; +}; + +const noop = () => {}; + +export const Example = () => { + const resetFocusControl: FlameGlobalControl = noop; // initial value + const focusOnNodeControl: FlameNodeControl = noop; // initial value + + const onElementListeners = { + onElementClick: action('onElementClick'), + onElementOver: action('onElementOver'), + onElementOut: action('onElementOut'), + }; + const theme: PartialTheme = { + chartMargins: { top: 0, left: 0, bottom: 0, right: 0 }, + chartPaddings: { left: 0, right: 0, top: 0, bottom: 0 }, + }; + button('Reset focus', () => { + resetFocusControl(); + }); + button('Set focus on random node', () => { + focusOnNodeControl(Math.floor(20 * Math.random())); + }); + + // fixing width and height at multiples of 256 for now + return ( + + + + + ); +}; + +Example.parameters = { + background: { default: 'white' }, +}; diff --git a/storybook/stories/timeslip/timeslip.stories.tsx b/storybook/stories/timeslip/timeslip.stories.tsx new file mode 100644 index 0000000000..465183bc84 --- /dev/null +++ b/storybook/stories/timeslip/timeslip.stories.tsx @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export default { + title: 'Timeslip (@alpha)', +}; + +export { Example as timeslipPrototype } from './01_timeslip.story'; From f6783b230bbf136734d0e029df07856a46c4b6ca Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Sun, 31 Jul 2022 10:02:37 +0200 Subject: [PATCH 2/6] chore: api update --- packages/charts/api/charts.api.md | 38 ++++++++++--------- .../src/chart_types/timeslip/timeslip_api.ts | 4 +- .../xy_chart/axes/timeslip/rasters.ts | 2 +- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/charts/api/charts.api.md b/packages/charts/api/charts.api.md index 54cf7ff09d..8ae568e347 100644 --- a/packages/charts/api/charts.api.md +++ b/packages/charts/api/charts.api.md @@ -1083,6 +1083,18 @@ export interface GeometryValue { y: any; } +// @public +export type GetData = (dataDemand: { + lo: TimeBin; + hi: TimeBin; + binUnit: string; + binUnitCount: number; + unitBarMaxWidthPixels: number; +}) => Array<{ + epochMs: number; + value: number; +}>; + // @public (undocumented) export function getNodeName(node: ArrayNode): string; @@ -2719,31 +2731,19 @@ export interface TimeScale { type: typeof ScaleType.Time; } +// Warning: (ae-forgotten-export) The symbol "buildProps" needs to be exported by the entry point index.d.ts +// // @public -export const Timeslip: (props: SFProps, "chartType" | "specType", "animation" | "valueAccessor" | "valueFormatter" | "valueGetter", never, "id" | "columnarData" | "controlProviderCallback">) => null; - -// @public -export type TimeslipGlobalControl = () => void; - -// @public -export type TimeslipNodeControl = (nodeIndex: number) => void; +export const Timeslip: (props: SFProps) => null; // @public -export interface TimeslipSpec extends Spec, LegacyAnimationConfig { +export interface TimeslipSpec extends Spec { // (undocumented) chartType: typeof ChartType.Timeslip; // (undocumented) - columnarData: ColumnarViewModel; - // (undocumented) - controlProviderCallback: Partial; + getData: GetData; // (undocumented) specType: typeof SpecType.Series; - // (undocumented) - valueAccessor: ValueAccessor; - // (undocumented) - valueFormatter: ValueFormatter; - // (undocumented) - valueGetter: (datumIndex: number) => number; } // @public @@ -3114,6 +3114,10 @@ export interface YDomainBase { // @public (undocumented) export type YDomainRange = YDomainBase & DomainRange & LogScaleOptions; +// Warnings were encountered during analysis: +// +// src/chart_types/timeslip/timeslip_api.ts:21:3 - (ae-forgotten-export) The symbol "TimeBin" needs to be exported by the entry point index.d.ts + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/charts/src/chart_types/timeslip/timeslip_api.ts b/packages/charts/src/chart_types/timeslip/timeslip_api.ts index 78d242fbca..ef2d880c9e 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip_api.ts +++ b/packages/charts/src/chart_types/timeslip/timeslip_api.ts @@ -11,7 +11,7 @@ import { Spec } from '../../specs'; import { SpecType } from '../../specs/constants'; // kept as long-winded import on separate line otherwise import circularity emerges import { buildSFProps, SFProps, useSpecFactory } from '../../state/spec_factory'; import { stripUndefined } from '../../utils/common'; -import { TimeBin, TimeRaster } from '../xy_chart/axes/timeslip/rasters'; +import { TimeBin } from '../xy_chart/axes/timeslip/rasters'; /** * data getter function @@ -20,7 +20,7 @@ import { TimeBin, TimeRaster } from '../xy_chart/axes/timeslip/rasters'; export type GetData = (dataDemand: { lo: TimeBin; // iirc TimeBin is enough, and the other TimeRaster etc. props aren't needed hi: TimeBin; // iirc TimeBin is enough, and the other TimeRaster etc. props aren't needed - binUnit: TimeRaster['unit']; // as of the initial commit, it's just a string + binUnit: string; // TimeRaster['unit']; // as of the initial commit, it's just a string binUnitCount: number; unitBarMaxWidthPixels: number; }) => Array<{ epochMs: number; value: number }>; diff --git a/packages/charts/src/chart_types/xy_chart/axes/timeslip/rasters.ts b/packages/charts/src/chart_types/xy_chart/axes/timeslip/rasters.ts index bd4406407f..250f5c1151 100644 --- a/packages/charts/src/chart_types/xy_chart/axes/timeslip/rasters.ts +++ b/packages/charts/src/chart_types/xy_chart/axes/timeslip/rasters.ts @@ -24,7 +24,7 @@ const approxWidthsInSeconds: Record = { millisecond: 0.001, }; -/** @internal */ +/** @public */ export interface TimeBin { timePointSec: number; nextTimePointSec: number; From b8032d1571e8d6018442f5144d15f48d70e0ad4b Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Mon, 1 Aug 2022 07:21:05 -0700 Subject: [PATCH 3/6] chore: refactor js files to ts --- .../timeslip/timeslip/{axis_model.js => axis_model.ts} | 2 ++ .../src/chart_types/timeslip/timeslip/{data.js => data.ts} | 2 ++ .../timeslip/timeslip/{domain_tween.js => domain_tween.ts} | 2 ++ .../render/annotations/{chart_title.js => chart_title.ts} | 2 ++ .../render/annotations/{time_extent.js => time_extent.ts} | 2 ++ .../timeslip/render/annotations/{time_unit.js => time_unit.ts} | 2 ++ .../timeslip/timeslip/render/{cartesian.js => cartesian.ts} | 2 ++ .../timeslip/timeslip/render/{column.js => column.ts} | 2 ++ .../timeslip/timeslip/render/glyphs/{bar.js => bar.ts} | 2 ++ .../timeslip/timeslip/render/glyphs/{boxplot.js => boxplot.ts} | 2 ++ .../timeslip/render/glyphs/{debug_box.js => debug_box.ts} | 2 ++ .../timeslip/timeslip/render/{raster.js => raster.ts} | 2 ++ .../timeslip/{timeslip_render.js => timeslip_render.ts} | 2 ++ .../timeslip/timeslip/{translations.js => translations.ts} | 2 ++ .../src/chart_types/timeslip/timeslip/utils/{dom.js => dom.ts} | 2 ++ .../timeslip/timeslip/utils/{generator.js => generator.ts} | 2 ++ .../chart_types/timeslip/timeslip/utils/{math.js => math.ts} | 2 ++ .../timeslip/timeslip/utils/{projection.js => projection.ts} | 2 ++ 18 files changed, 36 insertions(+) rename packages/charts/src/chart_types/timeslip/timeslip/{axis_model.js => axis_model.ts} (99%) rename packages/charts/src/chart_types/timeslip/timeslip/{data.js => data.ts} (98%) rename packages/charts/src/chart_types/timeslip/timeslip/{domain_tween.js => domain_tween.ts} (98%) rename packages/charts/src/chart_types/timeslip/timeslip/render/annotations/{chart_title.js => chart_title.ts} (98%) rename packages/charts/src/chart_types/timeslip/timeslip/render/annotations/{time_extent.js => time_extent.ts} (98%) rename packages/charts/src/chart_types/timeslip/timeslip/render/annotations/{time_unit.js => time_unit.ts} (98%) rename packages/charts/src/chart_types/timeslip/timeslip/render/{cartesian.js => cartesian.ts} (99%) rename packages/charts/src/chart_types/timeslip/timeslip/render/{column.js => column.ts} (99%) rename packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/{bar.js => bar.ts} (98%) rename packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/{boxplot.js => boxplot.ts} (99%) rename packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/{debug_box.js => debug_box.ts} (97%) rename packages/charts/src/chart_types/timeslip/timeslip/render/{raster.js => raster.ts} (99%) rename packages/charts/src/chart_types/timeslip/timeslip/{timeslip_render.js => timeslip_render.ts} (99%) rename packages/charts/src/chart_types/timeslip/timeslip/{translations.js => translations.ts} (99%) rename packages/charts/src/chart_types/timeslip/timeslip/utils/{dom.js => dom.ts} (98%) rename packages/charts/src/chart_types/timeslip/timeslip/utils/{generator.js => generator.ts} (98%) rename packages/charts/src/chart_types/timeslip/timeslip/utils/{math.js => math.ts} (97%) rename packages/charts/src/chart_types/timeslip/timeslip/utils/{projection.js => projection.ts} (98%) diff --git a/packages/charts/src/chart_types/timeslip/timeslip/axis_model.js b/packages/charts/src/chart_types/timeslip/timeslip/axis_model.ts similarity index 99% rename from packages/charts/src/chart_types/timeslip/timeslip/axis_model.js rename to packages/charts/src/chart_types/timeslip/timeslip/axis_model.ts index b168790005..8bc417703e 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/axis_model.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/axis_model.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + const getNiceTicksForApproxCount = (domainMin, domainMax, approxDesiredTickCount) => { const diff = domainMax - domainMin; const rawPitch = diff / approxDesiredTickCount; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/data.js b/packages/charts/src/chart_types/timeslip/timeslip/data.ts similarity index 98% rename from packages/charts/src/chart_types/timeslip/timeslip/data.js rename to packages/charts/src/chart_types/timeslip/timeslip/data.ts index 5a8918881f..733885602b 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/data.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/data.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export const dataSource = Symbol('dataSource'); diff --git a/packages/charts/src/chart_types/timeslip/timeslip/domain_tween.js b/packages/charts/src/chart_types/timeslip/timeslip/domain_tween.ts similarity index 98% rename from packages/charts/src/chart_types/timeslip/timeslip/domain_tween.js rename to packages/charts/src/chart_types/timeslip/timeslip/domain_tween.ts index 6a127d657f..5757ceecd5 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/domain_tween.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/domain_tween.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + import { mix } from './utils/math'; const REFERENCE_AF_LENGTH = 16.67; // ms diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/chart_title.js b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/chart_title.ts similarity index 98% rename from packages/charts/src/chart_types/timeslip/timeslip/render/annotations/chart_title.js rename to packages/charts/src/chart_types/timeslip/timeslip/render/annotations/chart_title.ts index de93b9dbe6..e23b1a4590 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/chart_title.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/chart_title.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export function renderChartTitle(ctx, config, chartWidth, cartesianTop, aggregationFunctionName) { ctx.save(); diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_extent.js b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_extent.ts similarity index 98% rename from packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_extent.js rename to packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_extent.ts index c3cc2814b9..9de1fdf7ce 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_extent.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_extent.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export function renderTimeExtentAnnotation( ctx, diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_unit.js b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_unit.ts similarity index 98% rename from packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_unit.js rename to packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_unit.ts index 1117a0c276..84015139ed 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_unit.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/annotations/time_unit.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + import { uiStrings } from '../../translations'; /** @internal */ diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/cartesian.js b/packages/charts/src/chart_types/timeslip/timeslip/render/cartesian.ts similarity index 99% rename from packages/charts/src/chart_types/timeslip/timeslip/render/cartesian.js rename to packages/charts/src/chart_types/timeslip/timeslip/render/cartesian.ts index 6b0cc3e64a..3fe6f2a885 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/render/cartesian.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/cartesian.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + import { renderRaster } from './raster'; /** @internal */ diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/column.js b/packages/charts/src/chart_types/timeslip/timeslip/render/column.ts similarity index 99% rename from packages/charts/src/chart_types/timeslip/timeslip/render/column.js rename to packages/charts/src/chart_types/timeslip/timeslip/render/column.ts index f5217e7b79..c4ae860edd 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/render/column.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/column.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + import { renderBarGlyph } from './glyphs/bar'; import { renderBoxplotGlyph } from './glyphs/boxplot'; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/bar.js b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/bar.ts similarity index 98% rename from packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/bar.js rename to packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/bar.ts index b620691ef7..6641478308 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/bar.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/bar.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export function renderBarGlyph( ctx, diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/boxplot.js b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/boxplot.ts similarity index 99% rename from packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/boxplot.js rename to packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/boxplot.ts index 59acb9fb69..591188ae73 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/boxplot.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/boxplot.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export function renderBoxplotGlyph( ctx, diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/debug_box.js b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/debug_box.ts similarity index 97% rename from packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/debug_box.js rename to packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/debug_box.ts index f1fcedae30..716edff484 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/debug_box.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/glyphs/debug_box.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export function renderDebugBox(ctx, cartesianWidth, cartesianHeight) { ctx.save(); diff --git a/packages/charts/src/chart_types/timeslip/timeslip/render/raster.js b/packages/charts/src/chart_types/timeslip/timeslip/render/raster.ts similarity index 99% rename from packages/charts/src/chart_types/timeslip/timeslip/render/raster.js rename to packages/charts/src/chart_types/timeslip/timeslip/render/raster.ts index 852d0c909f..8b448b0570 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/render/raster.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/render/raster.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + import { clamp } from '../utils/math'; import { renderColumn } from './column'; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.js b/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.ts similarity index 99% rename from packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.js rename to packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.ts index 22538343fd..89f9a6ecad 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + import * as dat from 'dat.gui'; import { cachedZonedDateTimeFrom, timeProp } from '../../xy_chart/axes/timeslip/chrono/cached_chrono'; diff --git a/packages/charts/src/chart_types/timeslip/timeslip/translations.js b/packages/charts/src/chart_types/timeslip/timeslip/translations.ts similarity index 99% rename from packages/charts/src/chart_types/timeslip/timeslip/translations.js rename to packages/charts/src/chart_types/timeslip/timeslip/translations.ts index f0d526ae48..b9c25e962b 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/translations.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/translations.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export const uiStrings = { 'ar-TN': { diff --git a/packages/charts/src/chart_types/timeslip/timeslip/utils/dom.js b/packages/charts/src/chart_types/timeslip/timeslip/utils/dom.ts similarity index 98% rename from packages/charts/src/chart_types/timeslip/timeslip/utils/dom.js rename to packages/charts/src/chart_types/timeslip/timeslip/utils/dom.ts index 9528559a5f..e800c2fc0a 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/utils/dom.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/utils/dom.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export const zoomSafePointerX = (e) => e.layerX ?? e.clientX; // robust against Chrome, Safari, Firefox menu zooms and/or pinch zoom (FF needs zero margin) diff --git a/packages/charts/src/chart_types/timeslip/timeslip/utils/generator.js b/packages/charts/src/chart_types/timeslip/timeslip/utils/generator.ts similarity index 98% rename from packages/charts/src/chart_types/timeslip/timeslip/utils/generator.js rename to packages/charts/src/chart_types/timeslip/timeslip/utils/generator.ts index 7575310216..175ae8de78 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/utils/generator.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/utils/generator.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export const toCallbackFn = (generatorObject) => { generatorObject.next(); // this starts the generator object, eg. resulting in initial render without any events diff --git a/packages/charts/src/chart_types/timeslip/timeslip/utils/math.js b/packages/charts/src/chart_types/timeslip/timeslip/utils/math.ts similarity index 97% rename from packages/charts/src/chart_types/timeslip/timeslip/utils/math.js rename to packages/charts/src/chart_types/timeslip/timeslip/utils/math.ts index 44bec1e232..6dbb9edc33 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/utils/math.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/utils/math.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export const mix = (start, end, a) => start * (1 - a) + end * a; // like the glsl function /** @internal */ diff --git a/packages/charts/src/chart_types/timeslip/timeslip/utils/projection.js b/packages/charts/src/chart_types/timeslip/timeslip/utils/projection.ts similarity index 98% rename from packages/charts/src/chart_types/timeslip/timeslip/utils/projection.js rename to packages/charts/src/chart_types/timeslip/timeslip/utils/projection.ts index 14efdfaf4f..821e943830 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/utils/projection.js +++ b/packages/charts/src/chart_types/timeslip/timeslip/utils/projection.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +// @ts-noCheck + /** @internal */ export const getDesiredTickCount = (cartesianHeight, fontSize, sparse) => { const desiredMaxTickCount = Math.floor(cartesianHeight / (3 * fontSize)); From 6f5883bcf3c0b4b2d31bbb99d766f5c5cb2be09c Mon Sep 17 00:00:00 2001 From: "elastic-datavis[bot]" <98618603+elastic-datavis[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 15:12:53 +0000 Subject: [PATCH 4/6] test(vrt): update screenshots [skip ci] --- .../timeslip-prototype-chrome-linux.png | Bin 0 -> 14388 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 e2e/screenshots/all.test.ts-snapshots/baselines/timeslip-alpha/timeslip-prototype-chrome-linux.png diff --git a/e2e/screenshots/all.test.ts-snapshots/baselines/timeslip-alpha/timeslip-prototype-chrome-linux.png b/e2e/screenshots/all.test.ts-snapshots/baselines/timeslip-alpha/timeslip-prototype-chrome-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..df6d0d222e3c23484db82f1f65c93c6dbbb42ae5 GIT binary patch literal 14388 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 Date: Tue, 2 Aug 2022 13:17:05 +0200 Subject: [PATCH 5/6] chore: remove dat.gui --- packages/charts/package.json | 1 - .../timeslip/timeslip/timeslip_render.ts | 131 +----------------- 2 files changed, 1 insertion(+), 131 deletions(-) diff --git a/packages/charts/package.json b/packages/charts/package.json index a73c0f6cbc..42ed84f1d4 100644 --- a/packages/charts/package.json +++ b/packages/charts/package.json @@ -36,7 +36,6 @@ "bezier-easing": "^2.1.0", "chroma-js": "^2.1.0", "classnames": "^2.2.6", - "dat.gui": "^0.7.9", "d3-array": "^1.2.4", "d3-cloud": "^1.2.5", "d3-collection": "^1.0.7", diff --git a/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.ts b/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.ts index 89f9a6ecad..11bb9203ab 100644 --- a/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.ts +++ b/packages/charts/src/chart_types/timeslip/timeslip/timeslip_render.ts @@ -8,8 +8,6 @@ // @ts-noCheck -import * as dat from 'dat.gui'; - import { cachedZonedDateTimeFrom, timeProp } from '../../xy_chart/axes/timeslip/chrono/cached_chrono'; import { rasters } from '../../xy_chart/axes/timeslip/rasters'; import { axisModel } from './axis_model'; @@ -82,9 +80,6 @@ export const timeslipRender = (canvas /*: HTMLCanvasElement*/, ctx /*: CanvasRen lumaSteps: lumaSteps.map((l) => 255 - l), }; - const gui = new dat.gui.GUI({ width: 200 }); - gui.domElement.parentElement.style['z-index'] = '1000000'; // might need a setTimeout in the future - gui.close(); const config = { darkMode: initialDarkMode, sparse: false, @@ -191,131 +186,7 @@ export const timeslipRender = (canvas /*: HTMLCanvasElement*/, ctx /*: CanvasRen dataResponse: { stats: {}, rows: [] }, }; - /** - * dat.gui event handlers - */ - - const updateGridAesthetics = ({ darkMode, sparse, implicit }) => { - // make implicit - const ls0 = implicit ? lumaSteps.map(() => 255) : lumaSteps; - const lst0 = implicit ? [1, ...lineThicknessSteps.slice(1)] : lineThicknessSteps; - - // sparsify - const ls1 = sparse ? [0, ...ls0] : ls0; - const lst1 = sparse ? [0, ...lst0] : lst0; - - // dark mode - const ls2 = darkMode ? ls1.map((d) => 255 - d) : ls1; - const lst2 = lst1; - - config.lumaSteps = ls2; - config.lineThicknessSteps = lst2; - }; - - const guiFolders = { - a11y: gui.addFolder('Accessibility'), - theme: gui.addFolder('User prefs'), - navigation: gui.addFolder('Navigation'), - query: gui.addFolder('Query'), - queryParams: gui.addFolder('Query params'), - i18n: gui.addFolder('Internationalization'), - }; - - guiFolders.query.open(); - - guiFolders.navigation - .add(interactionState, 'zoom') - .min(minZoom) - .max(maxZoom) - .step(0.001) - .onChange(scheduleChartRender) - .listen(); - guiFolders.navigation - .add(interactionState, 'pan') - .min(0) - .max(1) - .step(0.000001) - .onChange(scheduleChartRender) - .listen(); - - guiFolders.theme.add(config, 'darkMode').onChange(() => { - Object.assign(config, config.darkMode ? themeDark : themeLight); - updateGridAesthetics(config); - fullRender(); - }); - guiFolders.theme.add(config, 'sparse').onChange(() => { - updateGridAesthetics(config); - scheduleChartRender(); - }); - guiFolders.theme.add(config, 'implicit').onChange(() => { - updateGridAesthetics(config); - scheduleChartRender(); - }); - guiFolders.theme.add(config, 'maxLabelRowCount').min(2).max(3).step(1).onChange(scheduleChartRender); - - guiFolders.query.add(config.queryConfig, 'metricFieldName', metricFieldNames).onChange(scheduleChartRender); - guiFolders.query - .add( - config.queryConfig, - 'aggregation', - Object.fromEntries(Object.entries(aggregationFunctionNames).map(([k, v]) => [v, k])), - ) - .onChange(scheduleChartRender); - guiFolders.query - .add(config.queryConfig, 'boxplot') - .onChange((newVal) => { - if (newVal && config.queryConfig.window !== 0) { - config.queryConfig.window = 0; - } - scheduleChartRender(); - }) - .listen(); - guiFolders.queryParams - .add(config.queryConfig, 'window') - .min(0) - .max(10) - .step(1) - .onChange((newVal) => { - if (newVal > 0 && config.queryConfig.boxplot) { - config.queryConfig.boxplot = false; - } - scheduleChartRender(); - }) - .listen(); - guiFolders.queryParams.add(config.queryConfig, 'alpha').min(0).max(1).step(0.1).onChange(scheduleChartRender); - guiFolders.queryParams.add(config.queryConfig, 'beta').min(0).max(1).step(0.1).onChange(scheduleChartRender); - guiFolders.queryParams.add(config.queryConfig, 'gamma').min(0).max(1).step(0.1).onChange(scheduleChartRender); - guiFolders.queryParams.add(config.queryConfig, 'period').min(0).max(25).step(1).onChange(scheduleChartRender); - guiFolders.queryParams.add(config.queryConfig, 'multiplicative').onChange(scheduleChartRender); - guiFolders.queryParams.add(config.queryConfig, 'binOffset').min(-30).max(30).step(1).onChange(scheduleChartRender); - - let rasterSelector = rasters(config, timeZone); - - guiFolders.i18n - .add(config, 'locale', { - Arabic: 'ar-TN', - German: 'de-CH', - French: 'fr-FR', - 'US English': 'en-US', - Greek: 'el-GR', - Hungarian: 'hu-HU', - Hebrew: 'he-IL', - Hindi: 'hi-IN', - Italian: 'it-IT', - Japanese: 'ja-JA', - Russian: 'ru-RU', - }) - .onChange((value) => { - canvas.setAttribute('dir', ['he-IL', 'ar-TN'].includes(value) ? 'rtl' : 'ltr'); - rasterSelector = rasters(config, timeZone); - scheduleChartRender(); - }); - - guiFolders.i18n.add(config, 'numUnit', ['none', 'short', 'long']).onChange(scheduleChartRender); - guiFolders.a11y.add(config.a11y, 'shortcuts').onChange(scheduleChartRender); - guiFolders.a11y.add(config.a11y, 'contrast', ['low', 'medium', 'high']).onChange(scheduleChartRender); - guiFolders.a11y.add(config.a11y, 'animation').onChange(scheduleChartRender); - guiFolders.a11y.add(config.a11y, 'sonification').onChange(scheduleChartRender); + const rasterSelector = rasters(config, timeZone); // todo this may need an update with locale change const defaultLabelFormat = new Intl.DateTimeFormat(config.locale, { From 4047b5e6410bceb8bac01b5c3f5482bc33d1861c Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Tue, 2 Aug 2022 13:23:58 +0200 Subject: [PATCH 6/6] test: story update --- .../stories/timeslip/01_timeslip.story.tsx | 30 ++----------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/storybook/stories/timeslip/01_timeslip.story.tsx b/storybook/stories/timeslip/01_timeslip.story.tsx index ad6a54976c..e65ee0fb9d 100644 --- a/storybook/stories/timeslip/01_timeslip.story.tsx +++ b/storybook/stories/timeslip/01_timeslip.story.tsx @@ -6,19 +6,9 @@ * Side Public License, v 1. */ -import { action } from '@storybook/addon-actions'; -import { button } from '@storybook/addon-knobs'; import React from 'react'; -import { - Chart, - Timeslip, - Settings, - PartialTheme, - FlameGlobalControl, - FlameNodeControl, - GetData, -} from '@elastic/charts'; +import { Chart, Timeslip, Settings, PartialTheme, GetData } from '@elastic/charts'; import { useBaseTheme } from '../../use_base_theme'; @@ -45,32 +35,16 @@ const getData = (dataDemand: Parameters[0]) => { return result; }; -const noop = () => {}; - export const Example = () => { - const resetFocusControl: FlameGlobalControl = noop; // initial value - const focusOnNodeControl: FlameNodeControl = noop; // initial value - - const onElementListeners = { - onElementClick: action('onElementClick'), - onElementOver: action('onElementOver'), - onElementOut: action('onElementOut'), - }; const theme: PartialTheme = { chartMargins: { top: 0, left: 0, bottom: 0, right: 0 }, chartPaddings: { left: 0, right: 0, top: 0, bottom: 0 }, }; - button('Reset focus', () => { - resetFocusControl(); - }); - button('Set focus on random node', () => { - focusOnNodeControl(Math.floor(20 * Math.random())); - }); // fixing width and height at multiples of 256 for now return ( - + );