[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:
parent
013c1761ee
commit
11713acc7c
|
@ -12,4 +12,5 @@ export {
|
|||
COLOR_RANGE,
|
||||
COLOR_RANGE_SCALE,
|
||||
useCurrentEuiTheme,
|
||||
EuiThemeType,
|
||||
} from './use_color_range';
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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 = {
|
|||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
Loading…
Reference in a new issue