[ML] Data Frame Analytics: ensure map view supports dark theme (#85886)

* use current eui theme in map

* ensure labels do not overlap. add job type legend

* reduce padding in legend popover
This commit is contained in:
Melissa Alvarez 2020-12-15 13:17:53 -05:00 committed by GitHub
parent 013c1761ee
commit 11713acc7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 168 additions and 105 deletions

View file

@ -12,4 +12,5 @@ export {
COLOR_RANGE,
COLOR_RANGE_SCALE,
useCurrentEuiTheme,
EuiThemeType,
} from './use_color_range';

View file

@ -187,6 +187,8 @@ export const useColorRange = (
return scaleTypes[colorRangeScale];
};
export type EuiThemeType = typeof euiThemeLight | typeof euiThemeDark;
export function useCurrentEuiTheme() {
const uiSettings = useUiSettings();
return useMemo(

View file

@ -28,19 +28,10 @@
display: 'inline-block';
}
.mlJobMapLegend__trainedModel {
width: 0;
height: 0;
border-left: $euiSizeS solid $euiColorGhost;
border-right: $euiSizeS solid $euiColorGhost;
border-bottom: $euiSizeM solid $euiColorVis3;
display: 'inline-block';
}
.mlJobMapLegend__sourceNode {
height: $euiSizeM;
width: $euiSizeM;
background-color: $euiColorLightShade;
background-color: $euiColorWarning;
border: $euiBorderThin;
border-radius: $euiBorderRadius;
display: 'inline-block';

View file

@ -16,7 +16,8 @@ import React, {
import cytoscape from 'cytoscape';
// @ts-ignore no declaration file
import dagre from 'cytoscape-dagre';
import { cytoscapeOptions } from './cytoscape_options';
import { EuiThemeType } from '../../../../components/color_range_legend';
import { getCytoscapeOptions } from './cytoscape_options';
cytoscape.use(dagre);
@ -25,6 +26,7 @@ export const CytoscapeContext = createContext<cytoscape.Core | undefined>(undefi
interface CytoscapeProps {
children?: ReactNode;
elements: cytoscape.ElementDefinition[];
theme: EuiThemeType;
height: number;
itemsDeleted: boolean;
resetCy: boolean;
@ -59,8 +61,8 @@ function getLayoutOptions(width: number, height: number) {
name: 'dagre',
rankDir: 'LR',
fit: true,
padding: 30,
spacingFactor: 0.85,
padding: 20,
spacingFactor: 0.95,
boundingBox: { x1: 0, y1: 0, w: width, h: height },
};
}
@ -68,6 +70,7 @@ function getLayoutOptions(width: number, height: number) {
export function Cytoscape({
children,
elements,
theme,
height,
itemsDeleted,
resetCy,
@ -75,7 +78,7 @@ export function Cytoscape({
width,
}: CytoscapeProps) {
const [ref, cy] = useCytoscape({
...cytoscapeOptions,
...getCytoscapeOptions(theme),
elements,
});

View file

@ -5,11 +5,11 @@
*/
import cytoscape from 'cytoscape';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import {
ANALYSIS_CONFIG_TYPE,
JOB_MAP_NODE_TYPES,
} from '../../../../../../common/constants/data_frame_analytics';
import { EuiThemeType } from '../../../../components/color_range_legend';
import classificationJobIcon from './icons/ml_classification_job.svg';
import outlierDetectionJobIcon from './icons/ml_outlier_detection_job.svg';
import regressionJobIcon from './icons/ml_regression_job.svg';
@ -24,7 +24,7 @@ const MAP_SHAPES = {
} as const;
type MapShapes = typeof MAP_SHAPES[keyof typeof MAP_SHAPES];
function shapeForNode(el: cytoscape.NodeSingular): MapShapes {
function shapeForNode(el: cytoscape.NodeSingular, theme: EuiThemeType): MapShapes {
const type = el.data('type');
switch (type) {
case JOB_MAP_NODE_TYPES.ANALYTICS:
@ -55,7 +55,7 @@ function iconForNode(el: cytoscape.NodeSingular) {
}
}
function borderColorForNode(el: cytoscape.NodeSingular) {
function borderColorForNode(el: cytoscape.NodeSingular, theme: EuiThemeType) {
if (el.selected()) {
return theme.euiColorPrimary;
}
@ -76,7 +76,7 @@ function borderColorForNode(el: cytoscape.NodeSingular) {
}
}
export const cytoscapeOptions: cytoscape.CytoscapeOptions = {
export const getCytoscapeOptions = (theme: EuiThemeType): cytoscape.CytoscapeOptions => ({
autoungrabify: true,
boxSelectionEnabled: false,
maxZoom: 3,
@ -86,10 +86,10 @@ export const cytoscapeOptions: cytoscape.CytoscapeOptions = {
selector: 'node',
style: {
'background-color': (el: cytoscape.NodeSingular) =>
el.data('isRoot') ? theme.euiColorLightShade : theme.euiColorGhost,
el.data('isRoot') ? theme.euiColorWarning : theme.euiColorGhost,
'background-height': '60%',
'background-width': '60%',
'border-color': (el: cytoscape.NodeSingular) => borderColorForNode(el),
'border-color': (el: cytoscape.NodeSingular) => borderColorForNode(el, theme),
'border-style': 'solid',
// @ts-ignore
'background-image': (el: cytoscape.NodeSingular) => iconForNode(el),
@ -100,7 +100,7 @@ export const cytoscapeOptions: cytoscape.CytoscapeOptions = {
'font-size': theme.euiFontSizeXS,
'min-zoomed-font-size': parseInt(theme.euiSizeL, 10),
label: 'data(label)',
shape: (el: cytoscape.NodeSingular) => shapeForNode(el),
shape: (el: cytoscape.NodeSingular) => shapeForNode(el, theme),
'text-background-color': theme.euiColorLightestShade,
'text-background-opacity': 0,
'text-background-padding': theme.paddingSizes.xs,
@ -128,4 +128,4 @@ export const cytoscapeOptions: cytoscape.CytoscapeOptions = {
},
},
],
};
});

View file

@ -4,84 +4,145 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FC } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import React, { FC, useState } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiListGroupItem,
EuiListGroup,
EuiPopover,
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { JOB_MAP_NODE_TYPES } from '../../../../../../common/constants/data_frame_analytics';
import { EuiThemeType } from '../../../../components/color_range_legend';
export const JobMapLegend: FC = () => (
<EuiFlexGroup className="mlJobMapLegend__container" alignItems="center">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__indexPattern" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.indexLabel"
defaultMessage="index"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__transform" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
{JOB_MAP_NODE_TYPES.TRANSFORM}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__analytics" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.analyticsJobLabel"
defaultMessage="analytics job"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__trainedModel" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.trainedModelLabel"
defaultMessage="trained model"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__sourceNode" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.rootNodeLabel"
defaultMessage="source node"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
const getJobTypeList = () => (
<>
<EuiListGroup flush>
<EuiListGroupItem iconType="outlierDetectionJob" label="Outlier detection" size="xs" />
<EuiListGroupItem iconType="regressionJob" label="Regression" size="xs" />
<EuiListGroupItem iconType="classificationJob" label="Classification" size="xs" />
</EuiListGroup>
</>
);
export const JobMapLegend: FC<{ theme: EuiThemeType }> = ({ theme }) => {
const [showJobTypes, setShowJobTypes] = useState<boolean>(false);
return (
<EuiFlexGroup className="mlJobMapLegend__container" alignItems="center">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__sourceNode" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.rootNodeLabel"
defaultMessage="source node"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__indexPattern" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.indexLabel"
defaultMessage="index"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__transform" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
{JOB_MAP_NODE_TYPES.TRANSFORM}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span
style={{
display: 'inline-block',
width: '0px',
height: '0px',
borderLeft: `${theme.euiSizeS} solid ${theme.euiPageBackgroundColor}`,
borderRight: `${theme.euiSizeS} solid ${theme.euiPageBackgroundColor}`,
borderBottom: `${theme.euiSizeM} solid ${theme.euiColorVis3}`,
}}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.trainedModelLabel"
defaultMessage="trained model"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__analytics" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.analyticsJobLabel"
defaultMessage="analytics jobs"
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
ownFocus
button={
<EuiButtonIcon
iconSize="s"
onClick={() => setShowJobTypes(!showJobTypes)}
iconType={showJobTypes ? 'arrowUp' : 'arrowDown'}
aria-label={i18n.translate(
'xpack.ml.dataframe.analyticsMap.legend.showJobTypesAriaLabel',
{
defaultMessage: 'Show job types',
}
)}
/>
}
isOpen={showJobTypes}
closePopover={() => setShowJobTypes(false)}
>
{getJobTypeList()}
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -5,7 +5,6 @@
*/
import React, { FC, useEffect, useState } from 'react';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
@ -13,11 +12,12 @@ import { Cytoscape, Controls, JobMapLegend } from './components';
import { useMlKibana, useMlUrlGenerator } from '../../../contexts/kibana';
import { JOB_MAP_NODE_TYPES } from '../../../../../common/constants/data_frame_analytics';
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { useCurrentEuiTheme, EuiThemeType } from '../../../components/color_range_legend';
import { useRefDimensions } from './components/use_ref_dimensions';
import { useFetchAnalyticsMapData } from './use_fetch_analytics_map_data';
import { JobMapTitle } from './job_map_title';
const cytoscapeDivStyle = {
const getCytoscapeDivStyle = (theme: EuiThemeType) => ({
background: `linear-gradient(
90deg,
${theme.euiPageBackgroundColor}
@ -35,7 +35,7 @@ ${theme.euiColorLightShade}`,
backgroundSize: `${theme.euiSizeL} ${theme.euiSizeL}`,
margin: `-${theme.gutterTypes.gutterLarge}`,
marginTop: 0,
};
});
interface Props {
analyticsId?: string;
@ -64,6 +64,7 @@ export const JobMap: FC<Props> = ({ analyticsId, modelId }) => {
},
} = useMlKibana();
const urlGenerator = useMlUrlGenerator();
const { euiTheme } = useCurrentEuiTheme();
const redirectToAnalyticsManagementPage = async () => {
const url = await urlGenerator.createUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE });
@ -139,7 +140,7 @@ export const JobMap: FC<Props> = ({ analyticsId, modelId }) => {
<JobMapTitle analyticsId={analyticsId} modelId={modelId} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<JobMapLegend />
<JobMapLegend theme={euiTheme} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
@ -174,12 +175,16 @@ export const JobMap: FC<Props> = ({ analyticsId, modelId }) => {
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<div style={{ height: height - parseInt(theme.gutterTypes.gutterLarge, 10) - 20 }} ref={ref}>
<div
style={{ height: height - parseInt(euiTheme.gutterTypes.gutterLarge, 10) - 20 }}
ref={ref}
>
<Cytoscape
theme={euiTheme}
height={height - 20}
elements={elements}
width={width}
style={cytoscapeDivStyle}
style={getCytoscapeDivStyle(euiTheme)}
itemsDeleted={itemsDeleted}
resetCy={resetCyToggle}
>