Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: timeslip prototype added #1767

Merged
merged 6 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
32 changes: 32 additions & 0 deletions packages/charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ export const ChartType: Readonly<{
Goal: "goal";
Partition: "partition";
Flame: "flame";
Timeslip: "timeslip";
XYAxis: "xy_axis";
Heatmap: "heatmap";
Wordcloud: "wordcloud";
Expand Down Expand Up @@ -1082,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;

Expand Down Expand Up @@ -2718,6 +2731,21 @@ 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<TimeslipSpec, keyof (typeof buildProps_2)['overrides'], keyof (typeof buildProps_2)['defaults'], keyof (typeof buildProps_2)['optionals'], keyof (typeof buildProps_2)['requires']>) => null;

// @public
export interface TimeslipSpec extends Spec {
// (undocumented)
chartType: typeof ChartType.Timeslip;
// (undocumented)
getData: GetData;
// (undocumented)
specType: typeof SpecType.Series;
}

// @public
export function toEntries<T extends Record<string, string>, S>(array: T[], accessor: keyof T, staticValue: S): Record<string, S>;

Expand Down Expand Up @@ -3086,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)

```
1 change: 1 addition & 0 deletions packages/charts/src/chart_types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
40 changes: 40 additions & 0 deletions packages/charts/src/chart_types/timeslip/internal_chart_state.ts
Original file line number Diff line number Diff line change
@@ -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<SeriesKey, LegendItemExtraValues>();
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 = () => ({});
}
51 changes: 51 additions & 0 deletions packages/charts/src/chart_types/timeslip/timeslip/axis_model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.
*/

// @ts-noCheck

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 };
};
34 changes: 34 additions & 0 deletions packages/charts/src/chart_types/timeslip/timeslip/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.
*/

// @ts-noCheck

/** @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 };
};
33 changes: 33 additions & 0 deletions packages/charts/src/chart_types/timeslip/timeslip/domain_tween.ts
Original file line number Diff line number Diff line change
@@ -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.
*/

// @ts-noCheck

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;
};
Original file line number Diff line number Diff line change
@@ -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.
*/

// @ts-noCheck

/** @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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.
*/

// @ts-noCheck

/** @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();
}
Original file line number Diff line number Diff line change
@@ -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.
*/

// @ts-noCheck

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();
}
Loading