Skip to content

Commit d744eae

Browse files
authored
[ML] Data Frame Analytics: Scatterplot Matrix Fixes (#86357)
- use ml API service from the Kibana context - adds jest tests for the Vega Lite Spec generator - fix chart layout overflow with too many fields selected
1 parent 34803ed commit d744eae

File tree

4 files changed

+188
-11
lines changed

4 files changed

+188
-11
lines changed

x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
.mlScatterplotMatrix {
2+
overflow-x: auto;
3+
24
.vega-bind span {
35
font-size: $euiFontSizeXS;
46
padding: 0 $euiSizeXS;

x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { i18n } from '@kbn/i18n';
3030

3131
import type { SearchResponse7 } from '../../../../common/types/es_client';
3232

33-
import { ml } from '../../services/ml_api_service';
33+
import { useMlApiContext } from '../../contexts/kibana';
3434

3535
import { getProcessedFields } from '../data_grid';
3636
import { useCurrentEuiTheme } from '../color_range_legend';
@@ -72,6 +72,8 @@ export const ScatterplotMatrix: FC<ScatterplotMatrixProps> = ({
7272
color,
7373
legendType,
7474
}) => {
75+
const { esSearch } = useMlApiContext();
76+
7577
// dynamicSize is optionally used for outlier charts where the scatterplot marks
7678
// are sized according to outlier_score
7779
const [dynamicSize, setDynamicSize] = useState<boolean>(false);
@@ -147,7 +149,7 @@ export const ScatterplotMatrix: FC<ScatterplotMatrixProps> = ({
147149
}
148150
: { match_all: {} };
149151

150-
const resp: SearchResponse7 = await ml.esSearch({
152+
const resp: SearchResponse7 = await esSearch({
151153
index,
152154
body: {
153155
fields: queryFields,
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
// @ts-ignore
8+
import { compile } from 'vega-lite/build-es5/vega-lite';
9+
10+
import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json';
11+
12+
import {
13+
getColorSpec,
14+
getScatterplotMatrixVegaLiteSpec,
15+
COLOR_OUTLIER,
16+
COLOR_RANGE_NOMINAL,
17+
DEFAULT_COLOR,
18+
LEGEND_TYPES,
19+
} from './scatterplot_matrix_vega_lite_spec';
20+
21+
describe('getColorSpec()', () => {
22+
it('should return the default color for non-outlier specs', () => {
23+
const colorSpec = getColorSpec(euiThemeLight, false);
24+
25+
expect(colorSpec).toEqual({ value: DEFAULT_COLOR });
26+
});
27+
28+
it('should return a conditional spec for outliers', () => {
29+
const colorSpec = getColorSpec(euiThemeLight, true);
30+
31+
expect(colorSpec).toEqual({
32+
condition: {
33+
test: "(datum['outlier_score'] >= mlOutlierScoreThreshold.cutoff)",
34+
value: COLOR_OUTLIER,
35+
},
36+
value: euiThemeLight.euiColorMediumShade,
37+
});
38+
});
39+
40+
it('should return a field based spec for non-outlier specs with legendType supplied', () => {
41+
const colorName = 'the-color-field';
42+
43+
const colorSpec = getColorSpec(euiThemeLight, false, colorName, LEGEND_TYPES.NOMINAL);
44+
45+
expect(colorSpec).toEqual({
46+
field: colorName,
47+
scale: {
48+
range: COLOR_RANGE_NOMINAL,
49+
},
50+
type: 'nominal',
51+
});
52+
});
53+
});
54+
55+
describe('getScatterplotMatrixVegaLiteSpec()', () => {
56+
it('should return the default spec for non-outliers without a legend', () => {
57+
const data = [{ x: 1, y: 1 }];
58+
59+
const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec(data, ['x', 'y'], euiThemeLight);
60+
61+
// A valid Vega Lite spec shouldn't throw an error when compiled.
62+
expect(() => compile(vegaLiteSpec)).not.toThrow();
63+
64+
expect(vegaLiteSpec.repeat).toEqual({
65+
column: ['x', 'y'],
66+
row: ['y', 'x'],
67+
});
68+
expect(vegaLiteSpec.spec.transform).toEqual([
69+
{ as: 'x', calculate: "datum['x']" },
70+
{ as: 'y', calculate: "datum['y']" },
71+
]);
72+
expect(vegaLiteSpec.spec.data.values).toEqual(data);
73+
expect(vegaLiteSpec.spec.mark).toEqual({
74+
opacity: 0.75,
75+
size: 8,
76+
type: 'circle',
77+
});
78+
expect(vegaLiteSpec.spec.encoding.color).toEqual({ value: DEFAULT_COLOR });
79+
expect(vegaLiteSpec.spec.encoding.tooltip).toEqual([
80+
{ field: 'x', type: 'quantitative' },
81+
{ field: 'y', type: 'quantitative' },
82+
]);
83+
});
84+
85+
it('should return the spec for outliers', () => {
86+
const data = [{ x: 1, y: 1 }];
87+
88+
const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec(data, ['x', 'y'], euiThemeLight, 'ml');
89+
90+
// A valid Vega Lite spec shouldn't throw an error when compiled.
91+
expect(() => compile(vegaLiteSpec)).not.toThrow();
92+
93+
expect(vegaLiteSpec.repeat).toEqual({
94+
column: ['x', 'y'],
95+
row: ['y', 'x'],
96+
});
97+
expect(vegaLiteSpec.spec.transform).toEqual([
98+
{ as: 'x', calculate: "datum['x']" },
99+
{ as: 'y', calculate: "datum['y']" },
100+
{
101+
as: 'outlier_score',
102+
calculate: "datum['ml.outlier_score']",
103+
},
104+
]);
105+
expect(vegaLiteSpec.spec.data.values).toEqual(data);
106+
expect(vegaLiteSpec.spec.mark).toEqual({
107+
opacity: 0.75,
108+
size: 8,
109+
type: 'circle',
110+
});
111+
expect(vegaLiteSpec.spec.encoding.color).toEqual({
112+
condition: {
113+
test: "(datum['outlier_score'] >= mlOutlierScoreThreshold.cutoff)",
114+
value: COLOR_OUTLIER,
115+
},
116+
value: euiThemeLight.euiColorMediumShade,
117+
});
118+
expect(vegaLiteSpec.spec.encoding.tooltip).toEqual([
119+
{ field: 'x', type: 'quantitative' },
120+
{ field: 'y', type: 'quantitative' },
121+
{
122+
field: 'outlier_score',
123+
format: '.3f',
124+
type: 'quantitative',
125+
},
126+
]);
127+
});
128+
129+
it('should return the spec for classification', () => {
130+
const data = [{ x: 1, y: 1 }];
131+
132+
const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec(
133+
data,
134+
['x', 'y'],
135+
euiThemeLight,
136+
undefined,
137+
'the-color-field',
138+
LEGEND_TYPES.NOMINAL
139+
);
140+
141+
// A valid Vega Lite spec shouldn't throw an error when compiled.
142+
expect(() => compile(vegaLiteSpec)).not.toThrow();
143+
144+
expect(vegaLiteSpec.repeat).toEqual({
145+
column: ['x', 'y'],
146+
row: ['y', 'x'],
147+
});
148+
expect(vegaLiteSpec.spec.transform).toEqual([
149+
{ as: 'x', calculate: "datum['x']" },
150+
{ as: 'y', calculate: "datum['y']" },
151+
]);
152+
expect(vegaLiteSpec.spec.data.values).toEqual(data);
153+
expect(vegaLiteSpec.spec.mark).toEqual({
154+
opacity: 0.75,
155+
size: 8,
156+
type: 'circle',
157+
});
158+
expect(vegaLiteSpec.spec.encoding.color).toEqual({
159+
field: 'the-color-field',
160+
scale: {
161+
range: COLOR_RANGE_NOMINAL,
162+
},
163+
type: 'nominal',
164+
});
165+
expect(vegaLiteSpec.spec.encoding.tooltip).toEqual([
166+
{ field: 'x', type: 'quantitative' },
167+
{ field: 'y', type: 'quantitative' },
168+
]);
169+
});
170+
});

x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ export const OUTLIER_SCORE_FIELD = 'outlier_score';
2424

2525
const SCATTERPLOT_SIZE = 125;
2626

27-
const DEFAULT_COLOR = euiPaletteColorBlind()[0];
28-
const COLOR_OUTLIER = euiPaletteNegative(2)[1];
29-
const COLOR_RANGE_NOMINAL = euiPaletteColorBlind({ rotations: 2 });
30-
const COLOR_RANGE_QUANTITATIVE = euiPalettePositive(5);
27+
export const DEFAULT_COLOR = euiPaletteColorBlind()[0];
28+
export const COLOR_OUTLIER = euiPaletteNegative(2)[1];
29+
export const COLOR_RANGE_NOMINAL = euiPaletteColorBlind({ rotations: 2 });
30+
export const COLOR_RANGE_QUANTITATIVE = euiPalettePositive(5);
3131

32-
const getColorSpec = (
32+
export const getColorSpec = (
3333
euiTheme: typeof euiThemeLight,
3434
outliers = true,
3535
color?: string,
@@ -72,10 +72,13 @@ export const getScatterplotMatrixVegaLiteSpec = (
7272
calculate: `datum['${column}']`,
7373
as: column,
7474
}));
75-
transform.push({
76-
calculate: `datum['${resultsField}.${OUTLIER_SCORE_FIELD}']`,
77-
as: OUTLIER_SCORE_FIELD,
78-
});
75+
76+
if (resultsField !== undefined) {
77+
transform.push({
78+
calculate: `datum['${resultsField}.${OUTLIER_SCORE_FIELD}']`,
79+
as: OUTLIER_SCORE_FIELD,
80+
});
81+
}
7982

8083
return {
8184
$schema: 'https://vega.github.io/schema/vega-lite/v4.17.0.json',

0 commit comments

Comments
 (0)