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: debug state for the heatmap chart #976

Merged
merged 9 commits into from
Mar 11, 2021
2 changes: 2 additions & 0 deletions api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,8 @@ export interface DebugState {
//
// (undocumented)
bars?: DebugStateBar[];
// Warning: (ae-forgotten-export) The symbol "HeatmapDebugState" needs to be exported by the entry point index.d.ts
heatmap?: HeatmapDebugState;
// Warning: (ae-forgotten-export) The symbol "DebugStateLegend" needs to be exported by the entry point index.d.ts
//
// (undocumented)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"scripts": {
"autoprefix:css": "echo 'Autoprefixing...' && yarn postcss dist/*.css --no-map --use autoprefixer -d dist",
"api:check": "yarn build:ts && yarn api:extract",
"api:check:local": "yarn api:check --local",
"api:extract": "yarn api-extractor run --verbose",
"backport": "backport",
"build": "yarn build:ts && yarn build:css",
Expand Down
7 changes: 3 additions & 4 deletions src/chart_types/heatmap/state/chart_state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ import { Tooltip } from '../../../components/tooltip';
import { InternalChartState, GlobalChartState, BackwardRef } from '../../../state/chart_state';
import { getChartContainerDimensionsSelector } from '../../../state/selectors/get_chart_container_dimensions';
import { InitStatus } from '../../../state/selectors/get_internal_is_intialized';
import { DebugState } from '../../../state/types';
import { Dimensions } from '../../../utils/dimensions';
import { Heatmap } from '../renderer/canvas/connected_component';
import { HighlighterFromBrush } from '../renderer/dom/highlighter_brush';
import { computeChartDimensionsSelector } from './selectors/compute_chart_dimensions';
import { computeLegendSelector } from './selectors/compute_legend';
import { getBrushAreaSelector } from './selectors/get_brush_area';
import { getPointerCursorSelector } from './selectors/get_cursor_pointer';
import { getDebugStateSelector } from './selectors/get_debug_state';
import { getLegendItemsLabelsSelector } from './selectors/get_legend_items_labels';
import { getTooltipAnchorSelector } from './selectors/get_tooltip_anchor';
import { getSpecOrNull } from './selectors/heatmap_spec';
Expand Down Expand Up @@ -126,9 +126,8 @@ export class HeatmapState implements InternalChartState {
return getBrushAreaSelector(globalState);
}

// TODO
getDebugState(): DebugState {
return {};
getDebugState(globalState: GlobalChartState) {
return getDebugStateSelector(globalState);
}

eventCallbacks(globalState: GlobalChartState) {
Expand Down
87 changes: 87 additions & 0 deletions src/chart_types/heatmap/state/selectors/get_debug_state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import createCachedSelector from 're-reselect';

import { RGBtoString } from '../../../../common/color_library_wrappers';
import { LegendItem } from '../../../../common/legend';
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { DebugState, DebugStateLegend } from '../../../../state/types';
import { Position } from '../../../../utils/common';
import { computeLegendSelector } from './compute_legend';
import { geometries } from './geometries';
import { getHighlightedAreaSelector } from './get_highlighted_area';

/**
* Returns a stringified version of the `debugState`
* @internal
*/
export const getDebugStateSelector = createCachedSelector(
[geometries, computeLegendSelector, getHighlightedAreaSelector],
(geoms, legend, pickedArea): DebugState => {
return {
// Common debug state
legend: getLegendState(legend),
axes: {
x: [
{
id: 'x',
position: Position.Left,
labels: geoms.heatmapViewModel.xValues.map(({ text }) => text),
values: geoms.heatmapViewModel.xValues.map(({ value }) => value),
// vertical lines
gridlines: geoms.heatmapViewModel.gridLines.x.map((line) => ({ x: line.x1, y: line.y2 })),
},
],
y: [
{
id: 'y',
position: Position.Bottom,
labels: geoms.heatmapViewModel.yValues.map(({ text }) => text),
values: geoms.heatmapViewModel.yValues.map(({ value }) => value),
// horizontal lines
gridlines: geoms.heatmapViewModel.gridLines.y.map((line) => ({ x: line.x2, y: line.y1 })),
},
],
},
// Heatmap debug state
heatmap: {
cells: geoms.heatmapViewModel.cells.map(({ x, y, fill, formatted, value }) => ({
x,
y,
fill: RGBtoString(fill.color),
formatted,
value,
})),
selectedArea: pickedArea,
},
};
},
)(getChartIdSelector);

function getLegendState(legendItems: LegendItem[]): DebugStateLegend {
const items = legendItems
.filter(({ isSeriesHidden }) => !isSeriesHidden)
.map(({ label: name, color, seriesIdentifiers: [{ key }] }) => ({
key,
name,
color,
}));

return { items };
}
14 changes: 12 additions & 2 deletions src/state/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
* under the License.
*/

import { Position } from '../utils/common';
import { GeometryValue } from '../utils/geometry';
import type { Cell } from '../chart_types/heatmap/layout/types/viewmodel_types';
import type { Position } from '../utils/common';
import type { GeometryValue } from '../utils/geometry';

export interface DebugStateAxis {
id: string;
Expand Down Expand Up @@ -78,6 +79,13 @@ export type DebugStateBar = DebugStateBase & {
labels: any[];
};

type CellDebug = Pick<Cell, 'value' | 'formatted' | 'x' | 'y'> & { fill: string };

type HeatmapDebugState = {
cells: CellDebug[];
selectedArea: { x: number; y: number; width: number; height: number } | null;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼


/**
* Describes _visible_ chart state for use in functional tests
*
Expand All @@ -89,4 +97,6 @@ export interface DebugState {
areas?: DebugStateArea[];
lines?: DebugStateLine[];
bars?: DebugStateBar[];
/** Heatmap chart debug state */
heatmap?: HeatmapDebugState;
}
106 changes: 76 additions & 30 deletions stories/heatmap/1_basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,54 +17,99 @@
* under the License.
*/
import { action } from '@storybook/addon-actions';
import React from 'react';
import { boolean, button } from '@storybook/addon-knobs';
import React, { useCallback, useMemo, useState } from 'react';
import { debounce } from 'ts-debounce';

import { Chart, Heatmap, niceTimeFormatter, RecursivePartial, ScaleType, Settings } from '../../src';
import {
Chart,
DebugState,
Heatmap,
HeatmapBrushEvent,
niceTimeFormatter,
RecursivePartial,
ScaleType,
Settings,
} from '../../src';
import { Config } from '../../src/chart_types/heatmap/layout/types/config_types';
import { SWIM_LANE_DATA } from '../../src/utils/data_samples/test_anomaly_swim_lane';

export const Example = () => {
const config: RecursivePartial<Config> = {
grid: {
cellHeight: {
min: 20,
},
stroke: {
width: 1,
color: '#D3DAE6',
const [selection, setSelection] = useState<HeatmapBrushEvent | undefined>();

const persistCellsSelection = boolean('Persist cells selection', true);
const debugState = boolean('Enable debug state', true);
const dataStateAction = action('DataState');

const handler = useCallback(() => {
setSelection(undefined);
}, []);

button('Clear cells selection', handler);

const config: RecursivePartial<Config> = useMemo(
() => ({
grid: {
cellHeight: {
min: 20,
},
stroke: {
width: 1,
color: '#D3DAE6',
},
},
},
cell: {
maxWidth: 'fill',
maxHeight: 3,
label: {
visible: false,
cell: {
maxWidth: 'fill',
maxHeight: 3,
label: {
visible: false,
},
border: {
stroke: '#D3DAE6',
strokeWidth: 0,
},
},
border: {
stroke: '#D3DAE6',
strokeWidth: 0,
yAxisLabel: {
visible: true,
width: 'auto',
padding: { left: 10, right: 10 },
},
},
yAxisLabel: {
visible: true,
width: 'auto',
padding: { left: 10, right: 10 },
},
xAxisLabel: {
formatter: (value: string | number) => {
return niceTimeFormatter([1572825600000, 1572912000000])(value, { timeZone: 'UTC' });
xAxisLabel: {
formatter: (value: string | number) => {
return niceTimeFormatter([1572825600000, 1572912000000])(value, { timeZone: 'UTC' });
},
},
},
};
onBrushEnd: ((e) => {
setSelection(e);
}) as Config['onBrushEnd'],
}),
[],
);

const logDebugstate = debounce(() => {
if (!debugState) return;

const statusEl = document.querySelector<HTMLDivElement>('.echChartStatus');

if (statusEl) {
const dataState = statusEl.dataset.echDebugState
? (JSON.parse(statusEl.dataset.echDebugState) as DebugState)
: null;
dataStateAction(dataState);
}
}, 100);

return (
<Chart className="story-chart">
<Settings
onElementClick={action('onElementClick')}
onRenderChange={logDebugstate}
showLegend
legendPosition="top"
onBrushEnd={action('onBrushEnd')}
brushAxis="both"
xDomain={{ min: 1572825600000, max: 1572912000000, minInterval: 1800000 }}
debugState={debugState}
/>
<Heatmap
id="heatmap1"
Expand All @@ -80,6 +125,7 @@ export const Example = () => {
ySortPredicate="numAsc"
xScaleType={ScaleType.Time}
config={config}
highlightedData={persistCellsSelection ? selection : undefined}
/>
</Chart>
);
Expand Down