Compare commits

...

6 commits

7 changed files with 253 additions and 291 deletions

View file

@ -146,6 +146,7 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
data-test-subj="savedViews-openPopover"
iconType="arrowDown"
iconSide="right"
color="text"
>
{currentView
? currentView.name

View file

@ -56,17 +56,6 @@ export const BottomDrawer: React.FC<{
{isOpen ? hideHistory : showHistory}
</ShowHideButton>
</EuiFlexItem>
<EuiFlexItem
grow={false}
style={{
position: 'relative',
minWidth: 400,
height: '16px',
}}
>
{children}
</EuiFlexItem>
<RightSideSpacer />
</BottomActionTopBar>
<EuiFlexGroup style={{ marginTop: 0 }}>
<Timeline isVisible={isOpen} interval={interval} yAxisFormatter={formatter} />

View file

@ -17,8 +17,13 @@ import { calculateBoundsFromNodes } from '../lib/calculate_bounds_from_nodes';
import { PageContent } from '../../../../components/page';
import { useWaffleTimeContext } from '../hooks/use_waffle_time';
import { useWaffleFiltersContext } from '../hooks/use_waffle_filters';
import { DEFAULT_LEGEND, useWaffleOptionsContext } from '../hooks/use_waffle_options';
import { InfraFormatterType } from '../../../../lib/lib';
import {
DEFAULT_LEGEND,
useWaffleOptionsContext,
WaffleLegendOptions,
} from '../hooks/use_waffle_options';
import { useSourceContext } from '../../../../containers/metrics_source';
import { InfraFormatterType, InfraWaffleMapBounds } from '../../../../lib/lib';
import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common';
import { Toolbar } from './toolbars/toolbar';
import { ViewSwitcher } from './waffle/view_switcher';
@ -27,6 +32,7 @@ import { createLegend } from '../lib/create_legend';
import { useWaffleViewState } from '../hooks/use_waffle_view_state';
import { BottomDrawer } from './bottom_drawer';
import { Legend } from './waffle/legend';
import { LegendControls } from './waffle/legend_controls';
interface Props {
shouldLoadDefault: boolean;
@ -37,6 +43,12 @@ interface Props {
loading: boolean;
}
interface LegendControlOptions {
auto: boolean;
bounds: InfraWaffleMapBounds;
legend: WaffleLegendOptions;
}
export const Layout = React.memo(
({ shouldLoadDefault, currentView, reload, interval, nodes, loading }: Props) => {
const [showLoading, setShowLoading] = useState(true);
@ -50,6 +62,9 @@ export const Layout = React.memo(
autoBounds,
boundsOverride,
legend,
changeBoundsOverride,
changeAutoBounds,
changeLegend,
} = useWaffleOptionsContext();
const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext();
const { applyFilterQuery } = useWaffleFiltersContext();
@ -115,6 +130,15 @@ export const Layout = React.memo(
setShowLoading(!hasNodes);
}, [nodes]);
const handleLegendControlChange = useCallback(
(opts: LegendControlOptions) => {
changeBoundsOverride(opts.bounds);
changeAutoBounds(opts.auto);
changeLegend(opts.legend);
},
[changeBoundsOverride, changeAutoBounds, changeLegend]
);
return (
<>
<PageContent>
@ -134,6 +158,18 @@ export const Layout = React.memo(
gutterSize="m"
>
<Toolbar nodeType={nodeType} currentTime={currentTime} />
{view === 'map' && (
<EuiFlexItem grow={false}>
<LegendControls
options={legend != null ? legend : DEFAULT_LEGEND}
dataBounds={dataBounds}
bounds={bounds}
autoBounds={autoBounds}
boundsOverride={boundsOverride}
onChange={handleLegendControlChange}
/>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<ViewSwitcher view={view} onChange={changeView} />
</EuiFlexItem>
@ -164,14 +200,7 @@ export const Layout = React.memo(
interval={interval}
formatter={formatter}
width={width}
>
<Legend
formatter={formatter}
bounds={bounds}
dataBounds={dataBounds}
legend={options.legend}
/>
</BottomDrawer>
/>
)}
</>
)}
@ -179,6 +208,14 @@ export const Layout = React.memo(
</>
)}
</AutoSizer>
{view === 'map' && (
<Legend
formatter={formatter}
bounds={bounds}
dataBounds={dataBounds}
legend={options.legend}
/>
)}
</MainContainer>
)}
</AutoSizer>

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useCallback } from 'react';
import React from 'react';
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
import {
@ -17,13 +17,7 @@ import {
GradientLegendRT,
} from '../../../../../lib/lib';
import { GradientLegend } from './gradient_legend';
import { LegendControls } from './legend_controls';
import { StepLegend } from './steps_legend';
import {
DEFAULT_LEGEND,
useWaffleOptionsContext,
WaffleLegendOptions,
} from '../../hooks/use_waffle_options';
import { SteppedGradientLegend } from './stepped_gradient_legend';
interface Props {
legend: InfraWaffleMapLegend;
@ -32,39 +26,9 @@ interface Props {
formatter: InfraFormatter;
}
interface LegendControlOptions {
auto: boolean;
bounds: InfraWaffleMapBounds;
legend: WaffleLegendOptions;
}
export const Legend: React.FC<Props> = ({ dataBounds, legend, bounds, formatter }) => {
const {
changeBoundsOverride,
changeAutoBounds,
autoBounds,
legend: legendOptions,
changeLegend,
boundsOverride,
} = useWaffleOptionsContext();
const handleChange = useCallback(
(options: LegendControlOptions) => {
changeBoundsOverride(options.bounds);
changeAutoBounds(options.auto);
changeLegend(options.legend);
},
[changeBoundsOverride, changeAutoBounds, changeLegend]
);
export const Legend: React.FC<Props> = ({ legend, bounds, formatter }) => {
return (
<LegendContainer>
<LegendControls
options={legendOptions != null ? legendOptions : DEFAULT_LEGEND}
dataBounds={dataBounds}
bounds={bounds}
autoBounds={autoBounds}
boundsOverride={boundsOverride}
onChange={handleChange}
/>
{GradientLegendRT.is(legend) && (
<GradientLegend formatter={formatter} legend={legend} bounds={bounds} />
)}
@ -78,7 +42,7 @@ export const Legend: React.FC<Props> = ({ dataBounds, legend, bounds, formatter
const LegendContainer = euiStyled.div`
position: absolute;
bottom: 0px;
left: 10px;
top: 0px;
right: 10px;
bottom: 0px;
`;

View file

@ -26,7 +26,6 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { SyntheticEvent, useState, useCallback, useEffect } from 'react';
import { first, last } from 'lodash';
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
import { InfraWaffleMapBounds, InventoryColorPalette, PALETTES } from '../../../../../lib/lib';
import { WaffleLegendOptions } from '../../hooks/use_waffle_options';
import { getColorPalette } from '../../lib/get_color_palette';
@ -78,8 +77,10 @@ export const LegendControls = ({
const buttonComponent = (
<EuiButtonIcon
iconType="controlsHorizontal"
iconType="color"
color="text"
display="base"
size="s"
aria-label={i18n.translate('xpack.infra.legendControls.buttonLabel', {
defaultMessage: 'configure legend',
})}
@ -179,182 +180,174 @@ export const LegendControls = ({
: [];
return (
<ControlContainer>
<EuiPopover
isOpen={isPopoverOpen}
closePopover={handleCancelClick}
id="legendControls"
button={buttonComponent}
>
<EuiPopoverTitle>Legend Options</EuiPopoverTitle>
<EuiForm style={{ minWidth: 400 }}>
<EuiFormRow
display="columnCompressed"
label={i18n.translate('xpack.infra.legendControls.colorPaletteLabel', {
defaultMessage: 'Color palette',
})}
>
<>
<EuiSelect
options={PALETTE_OPTIONS}
value={draftLegend.palette}
id="palette"
onChange={handlePaletteChange}
compressed
/>
<EuiSpacer size="m" />
<PalettePreview
palette={draftLegend.palette}
steps={draftLegend.steps}
reverse={draftLegend.reverseColors}
/>
</>
</EuiFormRow>
<EuiFormRow
display="columnCompressed"
label={i18n.translate('xpack.infra.legendControls.stepsLabel', {
defaultMessage: 'Number of colors',
})}
>
<EuiRange
id="steps"
min={2}
max={18}
<EuiPopover
isOpen={isPopoverOpen}
closePopover={handleCancelClick}
id="legendControls"
button={buttonComponent}
anchorPosition="leftCenter"
>
<EuiPopoverTitle>Legend Options</EuiPopoverTitle>
<EuiForm style={{ minWidth: 400 }}>
<EuiFormRow
display="columnCompressed"
label={i18n.translate('xpack.infra.legendControls.colorPaletteLabel', {
defaultMessage: 'Color palette',
})}
>
<>
<EuiSelect
options={PALETTE_OPTIONS}
value={draftLegend.palette}
id="palette"
onChange={handlePaletteChange}
compressed
/>
<EuiSpacer size="m" />
<PalettePreview
palette={draftLegend.palette}
steps={draftLegend.steps}
reverse={draftLegend.reverseColors}
/>
</>
</EuiFormRow>
<EuiFormRow
display="columnCompressed"
label={i18n.translate('xpack.infra.legendControls.stepsLabel', {
defaultMessage: 'Number of colors',
})}
>
<EuiRange
id="steps"
min={2}
max={18}
step={1}
value={draftLegend.steps}
onChange={handleStepsChange}
showValue
fullWidth
/>
</EuiFormRow>
<EuiFormRow
fullWidth
display="columnCompressed"
label={i18n.translate('xpack.infra.legendControls.reverseDirectionLabel', {
defaultMessage: 'Reverse direction',
})}
>
<EuiSwitch
showLabel={false}
name="reverseColors"
label="reverseColors"
checked={draftLegend.reverseColors}
onChange={handleReverseColors}
compressed
style={{
position: 'relative',
top: '8px',
}}
/>
</EuiFormRow>
<EuiFormRow
fullWidth
display="columnCompressed"
label={i18n.translate('xpack.infra.legendControls.switchLabel', {
defaultMessage: 'Auto calculate range',
})}
>
<EuiSwitch
showLabel={false}
name="bounds"
label="bounds"
checked={draftAuto}
onChange={handleAutoChange}
compressed
style={{
position: 'relative',
top: '8px',
}}
/>
</EuiFormRow>
<EuiFormRow
fullWidth
label={
<SwatchLabel
color={first(paletteColors)!}
label={i18n.translate('xpack.infra.legendControls.minLabel', {
defaultMessage: 'Minimum',
})}
/>
}
isInvalid={!boundsValidRange}
display="columnCompressed"
error={errors}
>
<div style={{ maxWidth: 150 }}>
<EuiFieldNumber
disabled={draftAuto}
step={1}
value={draftLegend.steps}
onChange={handleStepsChange}
showValue
fullWidth
/>
</EuiFormRow>
<EuiFormRow
fullWidth
display="columnCompressed"
label={i18n.translate('xpack.infra.legendControls.reverseDirectionLabel', {
defaultMessage: 'Reverse direction',
})}
>
<EuiSwitch
showLabel={false}
name="reverseColors"
label="reverseColors"
checked={draftLegend.reverseColors}
onChange={handleReverseColors}
value={isNaN(draftBounds.min) ? '' : draftBounds.min}
isInvalid={!boundsValidRange}
name="legendMin"
onChange={handleMinBounds}
append="%"
compressed
style={{
position: 'relative',
top: '8px',
}}
/>
</EuiFormRow>
<EuiFormRow
fullWidth
display="columnCompressed"
label={i18n.translate('xpack.infra.legendControls.switchLabel', {
defaultMessage: 'Auto calculate range',
})}
>
<EuiSwitch
showLabel={false}
name="bounds"
label="bounds"
checked={draftAuto}
onChange={handleAutoChange}
</div>
</EuiFormRow>
<EuiFormRow
fullWidth
display="columnCompressed"
label={
<SwatchLabel
color={last(paletteColors)!}
label={i18n.translate('xpack.infra.legendControls.maxLabel', {
defaultMessage: 'Maxium',
})}
/>
}
isInvalid={!boundsValidRange}
error={errors}
>
<div style={{ maxWidth: 150 }}>
<EuiFieldNumber
disabled={draftAuto}
step={1}
isInvalid={!boundsValidRange}
value={isNaN(draftBounds.max) ? '' : draftBounds.max}
name="legendMax"
onChange={handleMaxBounds}
append="%"
compressed
style={{
position: 'relative',
top: '8px',
}}
/>
</EuiFormRow>
<EuiFormRow
fullWidth
label={
<SwatchLabel
color={first(paletteColors)!}
label={i18n.translate('xpack.infra.legendControls.minLabel', {
defaultMessage: 'Minimum',
})}
</div>
</EuiFormRow>
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="flexEnd" responsive={false}>
<EuiFlexItem grow={false}>
<EuiButtonEmpty type="submit" size="s" onClick={handleCancelClick}>
<FormattedMessage
id="xpack.infra.legendControls.cancelButton"
defaultMessage="Cancel"
/>
}
isInvalid={!boundsValidRange}
display="columnCompressed"
error={errors}
>
<div style={{ maxWidth: 150 }}>
<EuiFieldNumber
disabled={draftAuto}
step={1}
value={isNaN(draftBounds.min) ? '' : draftBounds.min}
isInvalid={!boundsValidRange}
name="legendMin"
onChange={handleMinBounds}
append="%"
compressed
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
type="submit"
size="s"
fill
disabled={commited || !boundsValidRange}
onClick={handleApplyClick}
>
<FormattedMessage
id="xpack.infra.legendControls.applyButton"
defaultMessage="Apply"
/>
</div>
</EuiFormRow>
<EuiFormRow
fullWidth
display="columnCompressed"
label={
<SwatchLabel
color={last(paletteColors)!}
label={i18n.translate('xpack.infra.legendControls.maxLabel', {
defaultMessage: 'Maxium',
})}
/>
}
isInvalid={!boundsValidRange}
error={errors}
>
<div style={{ maxWidth: 150 }}>
<EuiFieldNumber
disabled={draftAuto}
step={1}
isInvalid={!boundsValidRange}
value={isNaN(draftBounds.max) ? '' : draftBounds.max}
name="legendMax"
onChange={handleMaxBounds}
append="%"
compressed
/>
</div>
</EuiFormRow>
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="flexEnd" responsive={false}>
<EuiFlexItem grow={false}>
<EuiButtonEmpty type="submit" size="s" onClick={handleCancelClick}>
<FormattedMessage
id="xpack.infra.legendControls.cancelButton"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
type="submit"
size="s"
fill
disabled={commited || !boundsValidRange}
onClick={handleApplyClick}
>
<FormattedMessage
id="xpack.infra.legendControls.applyButton"
defaultMessage="Apply"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
</EuiPopover>
</ControlContainer>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
</EuiPopover>
);
};
const ControlContainer = euiStyled.div`
position: absolute;
top: -20px;
right: 6px;
bottom: 0;
`;

View file

@ -6,6 +6,7 @@
*/
import React from 'react';
import { EuiText } from '@elastic/eui';
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
import {
InfraWaffleMapBounds,
@ -22,10 +23,7 @@ type TickValue = 0 | 1;
export const SteppedGradientLegend: React.FC<Props> = ({ legend, bounds, formatter }) => {
return (
<LegendContainer>
<Ticks>
<TickLabel value={0} bounds={bounds} formatter={formatter} />
<TickLabel value={1} bounds={bounds} formatter={formatter} />
</Ticks>
<TickLabel value={0} bounds={bounds} formatter={formatter} />
<GradientContainer>
{legend.rules.map((rule, index) => (
<GradientStep
@ -34,6 +32,7 @@ export const SteppedGradientLegend: React.FC<Props> = ({ legend, bounds, formatt
/>
))}
</GradientContainer>
<TickLabel value={1} bounds={bounds} formatter={formatter} />
</LegendContainer>
);
};
@ -46,62 +45,41 @@ interface TickProps {
const TickLabel = ({ value, bounds, formatter }: TickProps) => {
const normalizedValue = value === 0 ? bounds.min : bounds.max * value;
const style = { left: `${value * 100}%` };
const label = formatter(normalizedValue);
return <Tick style={style}>{label}</Tick>;
return (
<div>
<EuiText size="xs">{label}</EuiText>
</div>
);
};
const GradientStep = euiStyled.div`
height: ${(props) => props.theme.eui.paddingSizes.s};
flex: 1 1 auto;
&:first-child {
border-radius: ${(props) => props.theme.eui.euiBorderRadius} 0 0 ${(props) =>
props.theme.eui.euiBorderRadius};
}
&:last-child {
border-radius: 0 ${(props) => props.theme.eui.euiBorderRadius} ${(props) =>
props.theme.eui.euiBorderRadius} 0;
}
`;
const Ticks = euiStyled.div`
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
top: -18px;
`;
const Tick = euiStyled.div`
position: absolute;
font-size: 11px;
text-align: center;
top: 0;
left: 0;
white-space: nowrap;
transform: translate(-50%, 0);
&:first-child {
padding-left: 5px;
transform: translate(0, 0);
}
&:last-child {
padding-right: 5px;
transform: translate(-100%, 0);
}
`;
const GradientContainer = euiStyled.div`
display: flex;
flex-direction; row;
align-items: stretch;
flex-grow: 1;
`;
const LegendContainer = euiStyled.div`
position: absolute;
height: 10px;
top: 0;
right: -16px;
bottom: 0;
left: 0;
right: 40px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
const GradientContainer = euiStyled.div`
height: 200px;
width: 10px;
display: flex;
flex-direction: column;
align-items: stretch;
`;
const GradientStep = euiStyled.div`
flex: 1 1 auto;
&:first-child {
border-radius: ${(props) => props.theme.eui.euiBorderRadius} ${(props) =>
props.theme.eui.euiBorderRadius} 0 0;
}
&:last-child {
border-radius: 0 0 ${(props) => props.theme.eui.euiBorderRadius} ${(props) =>
props.theme.eui.euiBorderRadius};
}
`;

View file

@ -37,8 +37,8 @@ export const ViewSwitcher = ({ view, onChange }: Props) => {
defaultMessage: 'Switch between table and map view',
})}
options={buttons}
color="primary"
buttonSize="m"
color="text"
buttonSize="s"
idSelected={view}
onChange={onChange}
isIconOnly