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

Heatmap small multiples #154434

Merged
merged 23 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0af960c
Update dependency @elastic/charts to v55
renovate[bot] Mar 28, 2023
5c64927
fix type breaking changes
markov00 Apr 3, 2023
86a2ef0
Merge branch 'main' into renovate/main-@elasticcharts
nickofthyme Apr 3, 2023
8e4bdda
fix: remove all heatmap grid height/width constraints
nickofthyme Apr 3, 2023
f0f8c22
fix: add back constant variable
nickofthyme Apr 3, 2023
b8a84fb
Merge branch 'main' into renovate/main-@elasticcharts
markov00 Apr 4, 2023
9161d3e
Merge branch 'main' into renovate/main-@elasticcharts
nickofthyme Apr 4, 2023
1edf1cd
Merge branch 'main' into renovate/main-@elasticcharts
stratoula Apr 5, 2023
e7e8e8a
[Aggbased] Heatmap small multiples
stratoula Apr 5, 2023
f1c6fcc
Fix translations
stratoula Apr 5, 2023
827b70d
Adjust tests
stratoula Apr 5, 2023
aa91d86
Fix some FTs
stratoula Apr 5, 2023
04c0452
Fix FTs
stratoula Apr 6, 2023
b549471
Merge branch 'main' into heatmap-small-multiples
stratoula Apr 6, 2023
04f94fb
Merge branch 'main' into heatmap-small-multiples
stratoula Apr 6, 2023
b7c7552
Merge branch 'main' into heatmap-small-multiples
stratoula Apr 10, 2023
8002ceb
Merge branch 'main' into heatmap-small-multiples
stratoula Apr 10, 2023
cf9a079
Merge branch 'main' into heatmap-small-multiples
stratoula Apr 10, 2023
2721970
Merge branch 'main' into heatmap-small-multiples
stratoula Apr 10, 2023
57fc932
Update src/plugins/chart_expressions/expression_heatmap/public/compon…
stratoula Apr 13, 2023
c1f386a
Merge branch 'main' into heatmap-small-multiples
stratoula Apr 13, 2023
5c1f724
Merge branch 'main' into heatmap-small-multiples
stratoula Apr 18, 2023
fd0e9e6
Merge branch 'main' into heatmap-small-multiples
stratoula Apr 20, 2023
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
stratoula marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -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.
*/

import React from 'react';
import { Accessor, AccessorFn, GroupBy, SmallMultiples, Predicate } from '@elastic/charts';

interface ChartSplitProps {
splitColumnAccessor?: Accessor | AccessorFn;
splitRowAccessor?: Accessor | AccessorFn;
}

const CHART_SPLIT_ID = '__heatmap_chart_split__';
const SMALL_MULTIPLES_ID = '__heatmap_chart_sm__';

export const ChartSplit = ({ splitColumnAccessor, splitRowAccessor }: ChartSplitProps) => {
if (!splitColumnAccessor && !splitRowAccessor) return null;

return (
<>
<GroupBy
id={CHART_SPLIT_ID}
by={(spec, datum) => {
const splitTypeAccessor = splitColumnAccessor || splitRowAccessor;
if (splitTypeAccessor) {
return typeof splitTypeAccessor === 'function'
? splitTypeAccessor(datum)
: datum[splitTypeAccessor];
}
return spec.id;
}}
sort={Predicate.DataIndex}
/>
<SmallMultiples
id={SMALL_MULTIPLES_ID}
splitVertically={splitRowAccessor ? CHART_SPLIT_ID : undefined}
splitHorizontally={splitColumnAccessor ? CHART_SPLIT_ID : undefined}
style={{
verticalPanelPadding: {
outer: 0.1,
inner: 0.1,
},
horizontalPanelPadding: {
outer: 0.1,
inner: 0.1,
},
stratoula marked this conversation as resolved.
Show resolved Hide resolved
}}
/>
</>
);
};
nickofthyme marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import {
LegendColorPickerWrapper,
} from '../utils/get_color_picker';
import { defaultPaletteParams } from '../constants';
import { ChartSplit } from './chart_split';
import { getSplitDimensionAccessor, createSplitPoint } from '../utils/get_split_dimension_utils';
import './index.scss';

declare global {
Expand Down Expand Up @@ -207,14 +209,19 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
() => findMinMaxByColumnId([valueAccessor!], table),
[valueAccessor, table]
);

const paletteParams = args.palette?.params;
const xAccessor = args.xAccessor
? getAccessorByDimension(args.xAccessor, table.columns)
: undefined;
const yAccessor = args.yAccessor
? getAccessorByDimension(args.yAccessor, table.columns)
: undefined;
const splitChartRowAccessor = args.splitRowAccessor
? getSplitDimensionAccessor(data.columns, args.splitRowAccessor, formatFactory)
: undefined;
const splitChartColumnAccessor = args.splitColumnAccessor
? getSplitDimensionAccessor(data.columns, args.splitColumnAccessor, formatFactory)
: undefined;

const xAxisColumnIndex = table.columns.findIndex((v) => v.id === xAccessor);
const yAxisColumnIndex = table.columns.findIndex((v) => v.id === yAccessor);
Expand Down Expand Up @@ -252,7 +259,7 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
const onElementClick = useCallback(
(e: HeatmapElementEvent[]) => {
const cell = e[0][0];
const { x, y } = cell.datum;
const { x, y, smVerticalAccessorValue, smHorizontalAccessorValue } = cell.datum;

const points = [
{
Expand Down Expand Up @@ -284,6 +291,28 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
: []),
];

if (smHorizontalAccessorValue && args.splitColumnAccessor) {
const point = createSplitPoint(
args.splitColumnAccessor,
smHorizontalAccessorValue,
formatFactory,
table
);
if (point) {
points.push(point);
}
}
if (smVerticalAccessorValue && args.splitRowAccessor) {
const point = createSplitPoint(
args.splitRowAccessor,
smVerticalAccessorValue,
formatFactory,
table
);
if (point) {
points.push(point);
}
}
const context: FilterEvent['data'] = {
data: points.map((point) => ({
row: point.row,
Expand All @@ -295,6 +324,9 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
onClickValue(context);
},
[
args.splitColumnAccessor,
args.splitRowAccessor,
formatFactory,
formattedTable.formattedColumns,
onClickValue,
table,
Expand Down Expand Up @@ -579,6 +611,10 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = memo(
}}
>
<Chart ref={chartRef}>
<ChartSplit
splitColumnAccessor={splitChartColumnAccessor}
splitRowAccessor={splitChartRowAccessor}
/>
<Settings
onRenderChange={onRenderChange}
noResults={
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* 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 { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import type { DatatableColumn, Datatable } from '@kbn/expressions-plugin/public';
import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
import { getSplitDimensionAccessor, createSplitPoint } from './get_split_dimension_utils';

const data: Datatable = {
type: 'datatable',
rows: [
{ 'col-0-1': 0, 'col-1-2': 'a', 'col-2-3': 'd' },
{ 'col-0-1': 148, 'col-1-2': 'b', 'col-2-3': 'c' },
],
columns: [
{ id: 'col-0-1', name: 'Count', meta: { type: 'number' } },
{ id: 'col-1-2', name: 'Dest', meta: { type: 'string' } },
{
id: 'col-2-3',
name: 'Test',
meta: {
type: 'number',
params: {
id: 'number',
},
},
},
],
};

describe('getSplitDimensionAccessor', () => {
const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args));

beforeEach(() => {
defaultFormatter.mockClear();
});

const splitDimension: ExpressionValueVisDimension = {
type: 'vis_dimension',
accessor: {
id: data.columns[2].id,
name: data.columns[2].name,
meta: data.columns[2].meta,
},
format: {
params: {},
},
};

it('returns accessor which is using formatter, if meta.params are present at accessing column', () => {
const accessor = getSplitDimensionAccessor(data.columns, splitDimension, defaultFormatter);

expect(defaultFormatter).toHaveBeenCalledTimes(1);
expect(typeof accessor).toBe('function');
accessor(data.rows[0]);
});

it('returns accessor which is using default formatter, if meta.params and format are not present', () => {
const column: Partial<DatatableColumn> = {
...data.columns[2],
meta: { type: 'number' },
};
const columns = [data.columns[0], column, data.columns[2]] as DatatableColumn[];
const defaultFormatterReturnedVal = fieldFormatsMock.deserialize();
const spyOnDefaultFormatterConvert = jest.spyOn(defaultFormatterReturnedVal, 'convert');

defaultFormatter.mockReturnValueOnce(defaultFormatterReturnedVal);
const accessor = getSplitDimensionAccessor(columns, splitDimension, defaultFormatter);

expect(defaultFormatter).toHaveBeenCalledTimes(1);

expect(typeof accessor).toBe('function');
accessor(data.rows[0]);
expect(spyOnDefaultFormatterConvert).toHaveBeenCalledTimes(1);
});

it('returns accessor which returns undefined, if such column is not present', () => {
const accessor1 = getSplitDimensionAccessor(data.columns, splitDimension, defaultFormatter);

expect(typeof accessor1).toBe('function');
const result1 = accessor1({});
expect(result1).toBeUndefined();

const column2: Partial<DatatableColumn> = {
...data.columns[2],
meta: { type: 'string' },
};
const columns2 = [data.columns[0], data.columns[1], column2] as DatatableColumn[];
const accessor2 = getSplitDimensionAccessor(columns2, splitDimension, defaultFormatter);

expect(typeof accessor2).toBe('function');
const result2 = accessor1({});
expect(result2).toBeUndefined();

const column3 = {
...data.columns[2],
meta: { type: 'string' },
format: {
id: 'string',
params: {},
},
};
const columns3 = [data.columns[0], data.columns[1], column3] as DatatableColumn[];

const accessor3 = getSplitDimensionAccessor(columns3, splitDimension, defaultFormatter);
expect(typeof accessor3).toBe('function');
const result3 = accessor3({});
expect(result3).toBeUndefined();
});
});

describe('createSplitPoint', () => {
const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args));

beforeEach(() => {
defaultFormatter.mockClear();
});

const splitDimension: ExpressionValueVisDimension = {
type: 'vis_dimension',
accessor: {
id: data.columns[2].id,
name: data.columns[2].name,
meta: data.columns[2].meta,
},
format: {
params: {},
},
};

it('returns point if value is found in the table', () => {
const point = createSplitPoint(splitDimension, 'c', defaultFormatter, data);

expect(defaultFormatter).toHaveBeenCalledTimes(1);
expect(point).toStrictEqual({ column: 2, row: 1, value: 'c' });
});

it('returns undefined if value is not found in the table', () => {
const point = createSplitPoint(splitDimension, 'test', defaultFormatter, data);

expect(defaultFormatter).toHaveBeenCalledTimes(1);
expect(point).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 type { AccessorFn } from '@elastic/charts';
import type { DatatableColumn, Datatable } from '@kbn/expressions-plugin/public';
import type { FormatFactory } from '@kbn/field-formats-plugin/common';
import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';

export const getSplitDimensionAccessor = (
columns: DatatableColumn[],
splitDimension: ExpressionValueVisDimension | string,
formatFactory: FormatFactory
): AccessorFn => {
const splitChartColumn = getColumnByAccessor(splitDimension, columns)!;
const accessor = splitChartColumn.id;

const formatter = formatFactory(splitChartColumn.meta?.params);
const fn: AccessorFn = (d) => {
const v = d[accessor];
if (v === undefined) {
return;
}

const f = formatter.convert(v);
return f;
};

return fn;
};

export function createSplitPoint(
splitDimension: ExpressionValueVisDimension | string,
value: string | number,
formatFactory: FormatFactory,
table: Datatable
) {
const splitChartColumn = getColumnByAccessor(splitDimension, table.columns)!;
const accessor = splitChartColumn.id;

const formatter = formatFactory(splitChartColumn.meta?.params);
const splitPointRowIndex = table.rows.findIndex((row) => {
return formatter.convert(row[accessor]) === value;
});
if (splitPointRowIndex !== -1) {
return {
row: splitPointRowIndex,
column: table.columns.findIndex((column) => column.id === accessor),
value: table.rows[splitPointRowIndex][accessor],
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { Position } from '@elastic/charts';

Expand All @@ -15,7 +14,6 @@ import { VIS_EVENT_TO_TRIGGER, VisTypeDefinition } from '@kbn/visualizations-plu
import { HeatmapTypeProps, HeatmapVisParams, AxisType, ScaleType } from '../types';
import { toExpressionAst } from '../to_ast';
import { getHeatmapOptions } from '../editor/components';
import { SplitTooltip } from './split_tooltip';
import { convertToLens } from '../convert_to_lens';

export const getHeatmapVisTypeDefinition = ({
Expand Down Expand Up @@ -129,11 +127,6 @@ export const getHeatmapVisTypeDefinition = ({
{
group: AggGroupNames.Buckets,
name: 'split',
// TODO: Remove when split chart aggs are supported
...(showElasticChartsOptions && {
disabled: true,
tooltip: <SplitTooltip />,
}),
title: i18n.translate('visTypeHeatmap.heatmap.splitTitle', {
defaultMessage: 'Split chart',
}),
Expand Down
19 changes: 0 additions & 19 deletions src/plugins/vis_types/heatmap/public/vis_type/split_tooltip.tsx

This file was deleted.

Loading