mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Lens] Chart switch redesign (#187475)
## Summary 1. The main change is chart switch redesign: <img width="401" alt="Screenshot 2024-08-26 at 15 08 50" src="https://github.com/user-attachments/assets/a2da17d5-b068-4eeb-9723-0b64cc6cbcf0"> 2. Toolbar changes for most of the visualization (refer to design files) <img width="930" alt="Screenshot 2024-08-26 at 15 09 14" src="https://github.com/user-attachments/assets/7f76aed3-99d8-438b-b304-91042c99381d"> <img width="942" alt="Screenshot 2024-08-26 at 15 08 56" src="https://github.com/user-attachments/assets/5bed1916-8723-42b5-8f7d-4b363c5736c9"> #### Subtasks - reorders the elements in the charts switch - redesigns chart switch visually - adds deprecated group (metric) - changes the add subtype layer menu - adds description to chart switch - subselect for bar/gauge visualizations - adds border to workspace panel - changes `boxesHorizontal` icon to `boxesVertical` Resolves https://github.com/elastic/kibana/issues/179260 also fixes https://github.com/elastic/kibana/issues/182677 switching between chart types
This commit is contained in:
parent
ff630fdc37
commit
9e8244f470
133 changed files with 3200 additions and 2664 deletions
|
@ -165,7 +165,7 @@ as your source to see both the raw and rolled up data.
|
|||
[role="screenshot"]
|
||||
image::images/management-create-rollup-bar-chart.png[Create visualization of rolled up data]
|
||||
|
||||
. Select *Bar vertical stacked* in the chart type dropdown.
|
||||
. Select *Bar* in the chart type dropdown.
|
||||
|
||||
. Add the `@timestamp` field to the *Horizontal axis*.
|
||||
|
||||
|
|
|
@ -38,10 +38,18 @@ export {
|
|||
EuiIconLegend,
|
||||
IconRegionMap,
|
||||
IconChartHeatmap,
|
||||
IconChartGauge,
|
||||
IconChartHorizontalBullet,
|
||||
IconDonutHoleLarge,
|
||||
IconDonutHoleMedium,
|
||||
IconDonutHoleSmall,
|
||||
IconChartVerticalBullet,
|
||||
IconChartLinearSimple,
|
||||
IconChartGaugeSemiCircle,
|
||||
IconChartGaugeSemiCircleSimple,
|
||||
IconChartGaugeArc,
|
||||
IconChartGaugeArcSimple,
|
||||
IconChartGaugeCircle,
|
||||
IconChartGaugeCircleSimple,
|
||||
IconChartTagcloud,
|
||||
} from './src/assets';
|
||||
|
|
|
@ -42,8 +42,12 @@ import {
|
|||
EuiIconLegend,
|
||||
IconRegionMap,
|
||||
IconChartHeatmap,
|
||||
IconChartGauge,
|
||||
IconChartHorizontalBullet,
|
||||
IconChartVerticalBullet,
|
||||
IconDonutHoleLarge,
|
||||
IconDonutHoleMedium,
|
||||
IconDonutHoleSmall,
|
||||
} from '../..';
|
||||
|
||||
export default {
|
||||
|
@ -183,10 +187,17 @@ const IconsArray: Array<{
|
|||
title: 'IconChartHorizontalBullet',
|
||||
Component: IconChartHorizontalBullet,
|
||||
},
|
||||
{
|
||||
title: 'IconChartGauge',
|
||||
Component: IconChartGauge,
|
||||
},
|
||||
{
|
||||
title: 'IconChartVerticalBullet',
|
||||
Component: IconChartVerticalBullet,
|
||||
},
|
||||
{ title: 'IconDonutHoleLarge', Component: IconDonutHoleLarge },
|
||||
{ title: 'IconDonutHoleMedium', Component: IconDonutHoleMedium },
|
||||
{ title: 'IconDonutHoleSmall', Component: IconDonutHoleSmall },
|
||||
];
|
||||
|
||||
interface RootComponentProps {
|
||||
|
|
|
@ -8,17 +8,10 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { IconSimpleWrapper } from '../icon_simple_wrapper';
|
||||
|
||||
export const IconCircle = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconCircle = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M7.427.522c-.813.081-1.398.21-2.039.45a7.318 7.318 0 0 0-2.67 1.72A7.045 7.045 0 0 0 1.28 4.68 7.241 7.241 0 0 0 .507 8c0 1.196.243 2.24.773 3.32a7.047 7.047 0 0 0 1.425 1.975A7.047 7.047 0 0 0 4.68 14.72a7.254 7.254 0 0 0 3.32.773 7.254 7.254 0 0 0 3.32-.773 7.047 7.047 0 0 0 1.975-1.425 7.047 7.047 0 0 0 1.425-1.975A7.254 7.254 0 0 0 15.493 8a7.254 7.254 0 0 0-.773-3.32 7.045 7.045 0 0 0-1.438-1.988C12.111 1.524 10.695.818 9.027.571 8.773.533 7.659.499 7.427.522m1.426 1.041a6.519 6.519 0 0 1 3.091 1.271c.329.246.976.893 1.222 1.222.561.751.976 1.634 1.164 2.479a6.766 6.766 0 0 1 0 2.93c-.414 1.861-1.725 3.513-3.463 4.363a6.76 6.76 0 0 1-1.987.616c-.424.065-1.336.065-1.76 0-1.948-.296-3.592-1.359-4.627-2.993a7.502 7.502 0 0 1-.634-1.332A6.158 6.158 0 0 1 1.514 8c0-1.039.201-1.925.646-2.84.34-.698.686-1.18 1.253-1.747A5.956 5.956 0 0 1 5.16 2.16a6.452 6.452 0 0 1 3.693-.597" />
|
||||
</svg>
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
|
|
@ -8,18 +8,10 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { IconSimpleWrapper } from '../icon_simple_wrapper';
|
||||
|
||||
export const IconTriangle = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
fill="none"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path d="M3.373 3.079c-.391.062-.637.158-.88.342-.545.415-.577 1.146-.087 1.979.174.296 3.984 6.347 4.164 6.613.32.474.755.838 1.117.937a1.7 1.7 0 0 0 .596.021c.138-.032.341-.126.49-.226.202-.135.523-.478.713-.762.378-.563 4.084-6.475 4.211-6.716.385-.733.368-1.339-.051-1.757-.168-.168-.437-.307-.767-.395l-.226-.06L8.12 3.05c-3.567-.004-4.579.002-4.747.029m9.267 1.004c.208.058.317.121.335.194.022.086-.029.259-.141.482-.129.258-4.174 6.679-4.327 6.87-.264.328-.441.419-.63.323-.116-.059-.33-.275-.454-.459-.276-.405-4.148-6.585-4.234-6.758a.93.93 0 0 1-.117-.364l-.011-.153.103-.053c.094-.048.288-.095.503-.121.044-.006 2.048-.008 4.453-.006 3.853.003 4.391.009 4.52.045" />
|
||||
</svg>
|
||||
export const IconTriangle = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M3.373 3.079c-.391.062-.637.158-.88.342-.545.415-.577 1.146-.087 1.979.174.296 3.984 6.347 4.164 6.613.32.474.755.838 1.117.937a1.7 1.7 0 0 0 .596.021c.138-.032.341-.126.49-.226.202-.135.523-.478.713-.762.378-.563 4.084-6.475 4.211-6.716.385-.733.368-1.339-.051-1.757-.168-.168-.437-.307-.767-.395l-.226-.06L8.12 3.05c-3.567-.004-4.579.002-4.747.029m9.267 1.004c.208.058.317.121.335.194.022.086-.029.259-.141.482-.129.258-4.174 6.679-4.327 6.87-.264.328-.441.419-.63.323-.116-.059-.33-.275-.454-.459-.276-.405-4.148-6.585-4.234-6.758a.93.93 0 0 1-.117-.364l-.011-.153.103-.053c.094-.048.288-.095.503-.121.044-.006 2.048-.008 4.453-.006 3.853.003 4.391.009 4.52.045" />{' '}
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
|
|
@ -7,26 +7,12 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { IconSimpleWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const EuiIconAxisBottom = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: {
|
||||
title: string;
|
||||
titleId: string;
|
||||
}) => (
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const EuiIconAxisBottom = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M5 1.5a.5.5 0 111 0v7a.5.5 0 01-1 0v-7zM15.39 11.39a1.5 1.5 0 010 2.12l-2.122 2.122a.5.5 0 11-.707-.707l2.121-2.122a.5.5 0 000-.707l-2.121-2.12a.5.5 0 11.707-.708l2.121 2.121zM3.439 9.269a.5.5 0 010 .707l-2.122 2.121a.5.5 0 000 .707l2.122 2.121a.5.5 0 01-.707.708L.61 13.51a1.5 1.5 0 010-2.121l2.122-2.121a.5.5 0 01.707 0zM8 3a.5.5 0 01.5.5v5a.5.5 0 01-1 0v-5A.5.5 0 018 3zM11 5.5a.5.5 0 00-1 0v3a.5.5 0 001 0v-3z" />
|
||||
<path d="M3.5 12a.5.5 0 000 1h9a.5.5 0 000-1h-9z" />
|
||||
</svg>
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
|
|
@ -6,28 +6,14 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { IconSimpleWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const EuiIconAxisLeft = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: {
|
||||
title: string;
|
||||
titleId: string;
|
||||
}) => (
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const EuiIconAxisLeft = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M7.378 3.585a.5.5 0 00.353-.854L5.61.611a1.5 1.5 0 00-2.121 0L1.367 2.73a.5.5 0 00.708.707l2.12-2.12a.5.5 0 01.708 0l2.121 2.12a.5.5 0 00.354.147z" />
|
||||
<path d="M5.046 3.088v4.129l.005.04v5.658a.5.5 0 01-.992.09l-.01-.09V8.786l-.004-.04V3.087a.5.5 0 01.992-.09l.01.09z" />
|
||||
<path d="M4.55 15.829a1.5 1.5 0 001.06-.44l2.122-2.121a.5.5 0 10-.707-.707l-2.121 2.121a.5.5 0 01-.708 0l-2.12-2.121a.5.5 0 00-.708.707l2.121 2.121a1.5 1.5 0 001.061.44zM13.5 4a.5.5 0 01.5.5v7a.5.5 0 11-1 0v-7a.5.5 0 01.5-.5zM10.5 6.5a.5.5 0 011 0v5a.5.5 0 11-1 0v-5zM8.5 8a.5.5 0 00-.5.5v3a.5.5 0 101 0v-3a.5.5 0 00-.5-.5z" />
|
||||
</svg>
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
|
|
@ -6,28 +6,14 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { IconSimpleWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const EuiIconAxisRight = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: {
|
||||
title: string;
|
||||
titleId: string;
|
||||
}) => (
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const EuiIconAxisRight = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M8.622 3.585a.5.5 0 01-.353-.854L10.39.611a1.5 1.5 0 012.121 0l2.122 2.12a.5.5 0 11-.707.707l-2.122-2.12a.5.5 0 00-.707 0l-2.121 2.12a.5.5 0 01-.354.147z" />
|
||||
<path d="M11.95 12.915V8.786l.005-.04V3.087a.5.5 0 00-.992-.09l-.01.09v4.129l-.004.04v5.658a.5.5 0 00.992.09l.01-.09z" />
|
||||
<path d="M11.45 15.829a1.5 1.5 0 01-1.06-.44l-2.122-2.121a.5.5 0 11.707-.707l2.121 2.121a.5.5 0 00.707 0l2.122-2.121a.5.5 0 01.707.707l-2.121 2.121a1.5 1.5 0 01-1.061.44zM2.5 4a.5.5 0 00-.5.5v7a.5.5 0 101 0v-7a.5.5 0 00-.5-.5zM5.5 6.5a.5.5 0 00-1 0v5a.5.5 0 101 0v-5zM7.5 8a.5.5 0 01.5.5v3a.5.5 0 11-1 0v-3a.5.5 0 01.5-.5z" />
|
||||
</svg>
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
|
|
@ -6,31 +6,17 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { IconSimpleWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const EuiIconAxisTop = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: {
|
||||
title: string;
|
||||
titleId: string;
|
||||
}) => (
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const EuiIconAxisTop = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M4.99991 7.49999C4.99991 7.22384 5.22376 6.99999 5.49991 6.99999C5.77605 6.99999 5.99991 7.22384 5.99991 7.49999V14.5C5.99991 14.7761 5.77605 15 5.49991 15C5.22376 15 4.99991 14.7761 4.99991 14.5V7.49999Z" />
|
||||
<path d="M15.3893 2.38929C15.6706 2.67059 15.8287 3.05212 15.8287 3.44995C15.8287 3.84777 15.6706 4.2293 15.3893 4.51061L13.268 6.63193C13.1742 6.7257 13.0471 6.77838 12.9145 6.77838C12.7818 6.77838 12.6547 6.7257 12.5609 6.63193C12.4671 6.53816 12.4145 6.41098 12.4145 6.27838C12.4145 6.14577 12.4671 6.01859 12.5609 5.92482L14.6822 3.8035C14.776 3.70973 14.8287 3.58256 14.8287 3.44995C14.8287 3.31734 14.776 3.19016 14.6822 3.09639L12.5609 0.975075C12.4671 0.881307 12.4145 0.754129 12.4145 0.621522C12.4145 0.488914 12.4671 0.361736 12.5609 0.267968C12.6547 0.1742 12.7819 0.121521 12.9145 0.121521C13.0471 0.121521 13.1742 0.1742 13.268 0.267968L15.3893 2.38929Z" />
|
||||
<path d="M3.43867 0.26864C3.53243 0.362408 3.58511 0.489585 3.58511 0.622193C3.58511 0.754801 3.53243 0.881978 3.43867 0.975746L1.31735 3.09707C1.22358 3.19083 1.1709 3.31801 1.1709 3.45062C1.1709 3.58323 1.22358 3.71041 1.31735 3.80417L3.43867 5.92549C3.53243 6.01926 3.58511 6.14644 3.58511 6.27905C3.58511 6.41166 3.53243 6.53883 3.43867 6.6326C3.3449 6.72637 3.21772 6.77905 3.08511 6.77905C2.9525 6.77905 2.82533 6.72637 2.73156 6.6326L0.610239 4.51128C0.328934 4.22998 0.170898 3.84844 0.170898 3.45062C0.170898 3.0528 0.328934 2.67126 0.610238 2.38996L2.73156 0.26864C2.82533 0.174871 2.9525 0.122192 3.08511 0.122192C3.21772 0.122192 3.3449 0.174871 3.43867 0.26864Z" />
|
||||
<path d="M7.99991 6.99999C8.27605 6.99999 8.49991 7.22384 8.49991 7.49999V12.5C8.49991 12.7761 8.27605 13 7.99991 13C7.72377 13 7.49991 12.7761 7.49991 12.5V7.49999C7.49991 7.22384 7.72377 6.99999 7.99991 6.99999Z" />
|
||||
<path d="M10.9999 7.49999C10.9999 7.22384 10.776 6.99999 10.4999 6.99999C10.2238 6.99999 9.99991 7.22384 9.99991 7.49999V10.5C9.99991 10.7761 10.2238 11 10.4999 11C10.776 11 10.9999 10.7761 10.9999 10.5V7.49999Z" />
|
||||
<path d="M3.50015 2.99999C3.22401 2.99999 3.00015 3.22384 3.00015 3.49999C3.00015 3.77613 3.22401 3.99999 3.50015 3.99999H12.5002C12.7763 3.99999 13.0002 3.77613 13.0002 3.49999C13.0002 3.22384 12.7763 2.99999 12.5002 2.99999H3.50015Z" />
|
||||
</svg>
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartArea = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartArea = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M30 6v15a1 1 0 01-1 1H1a1 1 0 01-1-1v-2c1 0 3.5-4 6-4s5 3 6 3 3.23-6.994 5.865-6.997C20.5 11 23 11 24 11s3-5 6-5z"
|
||||
className={colors.accent}
|
||||
|
@ -29,5 +21,5 @@ export const IconChartArea = ({ title, titleId, ...props }: Omit<EuiIconProps, '
|
|||
d="M6 1c3 0 5 6 6 6s3.5-3 6-3c1.667 0 2.944 2.333 3.833 6.999l.309.001c-1.013 0-2.27 0-3.593.002h-.684C15.231 11.007 13 18 12 18s-3.5-3-6-3-5 4-6 4V7c1-1.5 3-6 6-6z"
|
||||
className={colors.subdued}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,22 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartAreaPercentage = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartAreaPercentage = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M0 13v8a1 1 0 001 1h28a1 1 0 001-1V9.25c-1.251-.929-2.45-1.734-3.493-2.313a11.028 11.028 0 00-1.478-.703C24.592 6.072 24.25 6 24 6c-.262 0-.63.212-1.126.77-.472.53-.952 1.249-1.458 2.007l-.013.02c-.49.736-1.006 1.51-1.53 2.098C19.37 11.462 18.739 12 18 12c-1.062 0-2.112-.263-3.092-.508l-.03-.007C13.869 11.232 12.929 11 12 11c-.337 0-.729.171-1.2.525-.466.35-.94.822-1.446 1.329l-.015.015c-.49.489-1.01 1.01-1.539 1.406-.529.396-1.137.725-1.8.725-.657 0-1.57-.212-2.48-.424l-.058-.014C2.275 14.287 1.032 14 0 14v-1z"
|
||||
className={colors.accent}
|
||||
|
@ -33,5 +21,5 @@ export const IconChartAreaPercentage = ({
|
|||
d="M29 0a1 1 0 011 1v6.012c-1.06-.764-2.085-1.437-3.007-1.95a11.93 11.93 0 00-1.616-.765C24.887 4.115 24.418 4 24 4c-.738 0-1.369.538-1.874 1.105-.523.589-1.039 1.362-1.529 2.098l-.013.02c-.506.758-.985 1.476-1.458 2.007-.495.558-.864.77-1.126.77-.928 0-1.867-.232-2.879-.485l-.029-.007C14.112 9.263 13.062 9 12 9c-.663 0-1.271.328-1.8.725-.528.396-1.05.917-1.538 1.406l-.015.015c-.507.507-.98.98-1.447 1.329-.471.354-.863.525-1.2.525-.528 0-1.328-.183-2.311-.412l-.034-.007C2.507 12.314 1.159 12 .001 12V1a1 1 0 011-1h28z"
|
||||
className={colors.subdued}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartAreaStacked = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={31}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartAreaStacked = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M0 15.213l.484.091.813.146.762.129C3.779 15.859 5.09 16 6 16c.986 0 1.712-.25 3.166-.966l.281-.14C10.802 14.217 11.381 14 12 14c.507 0 .988.146 1.89.571l1.209.592c1.28.617 1.977.837 2.901.837 1.028 0 1.75-.349 3.119-1.344l.89-.659C23.034 13.252 23.535 13 24 13c.581 0 1.232.185 2.598.718l1.1.436.568.217c.72.27 1.256.438 1.736.532L30 21a1 1 0 01-1 1H1a1 1 0 01-1-1v-5.787z"
|
||||
className={colors.accent}
|
||||
|
@ -29,5 +21,5 @@ export const IconChartAreaStacked = ({ title, titleId, ...props }: Omit<EuiIconP
|
|||
d="M24 1c1.334 0 3.334 1 6 3v8.842l-.324-.098c-.346-.11-.759-.262-1.273-.462l-1.101-.436-.568-.217-.536-.193C25.277 11.118 24.676 11 24 11c-1.028 0-1.75.349-3.119 1.344l-.89.659c-1.024.745-1.524.997-1.99.997-.508 0-.989-.146-1.89-.571l-1.21-.592c-1.28-.617-1.977-.837-2.9-.837-.987 0-1.713.25-3.167.966l-.281.14C7.198 13.783 6.619 14 6 14l-.334-.007c-1.182-.045-3.08-.317-5.665-.815V9c2 0 4.666 1 6 1 2 0 4-4 6-4s4 1 6 1 4-6 6-6z"
|
||||
className={colors.subdued}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartBar = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartBar = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M5 7a1 1 0 011 1v13a1 1 0 01-1 1H1a1 1 0 01-1-1V8a1 1 0 011-1h4zm16-7a1 1 0 011 1v20a1 1 0 01-1 1h-4a1 1 0 01-1-1V1a1 1 0 011-1h4z"
|
||||
className={colors.subdued}
|
||||
|
@ -29,5 +21,5 @@ export const IconChartBar = ({ title, titleId, ...props }: Omit<EuiIconProps, 't
|
|||
d="M13 11a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1v-9a1 1 0 011-1h4zm16-7a1 1 0 011 1v16a1 1 0 01-1 1h-4a1 1 0 01-1-1V5a1 1 0 011-1h4z"
|
||||
className={colors.accent}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,22 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartBarAnnotations = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
width="30"
|
||||
height="22"
|
||||
viewBox="0 0 30 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartBarAnnotations = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<g>
|
||||
<path
|
||||
className={colors.subdued}
|
||||
|
@ -35,5 +23,5 @@ export const IconChartBarAnnotations = ({
|
|||
d="M10 1a1 1 0 011-1h3a1 1 0 011 1h3v4h-3a1 1 0 01-1-1h-2v9a1 1 0 11-2 0V1z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,22 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartBarHorizontal = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartBarHorizontal = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M29 16a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1v-4a1 1 0 011-1h28zM22 0a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V1a1 1 0 011-1h21z"
|
||||
className={colors.subdued}
|
||||
|
@ -33,5 +21,5 @@ export const IconChartBarHorizontal = ({
|
|||
d="M0 9a1 1 0 011-1h15a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V9z"
|
||||
className={colors.accent}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,22 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartBarHorizontalPercentage = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartBarHorizontalPercentage = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M20 16v6H1a1 1 0 01-1-1v-4a1 1 0 011-1h19zm-3-8v6H1.222C.547 14 0 13.552 0 13V9c0-.552.547-1 1.222-1H17zm1-8v6H1.042C.466 6 0 5.552 0 5V1c0-.552.466-1 1.042-1H18z"
|
||||
className={colors.subdued}
|
||||
|
@ -33,5 +21,5 @@ export const IconChartBarHorizontalPercentage = ({
|
|||
d="M29 16a1 1 0 011 1v4a1 1 0 01-1 1h-7v-6h7zm-.222-8C29.453 8 30 8.448 30 9v4c0 .552-.547 1-1.222 1H19V8h9.778zm.18-8C29.534 0 30 .448 30 1v4c0 .552-.466 1-1.042 1H20V0h8.958z"
|
||||
className={colors.accent}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,22 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartBarHorizontalStacked = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartBarHorizontalStacked = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M18 16v6H1a1 1 0 01-1-1v-4a1 1 0 011-1h17zm-3-8v6H1.222C.547 14 0 13.552 0 13V9c0-.552.547-1 1.222-1H15zm1-8v6H1.042C.466 6 0 5.552 0 5V1c0-.552.466-1 1.042-1H16z"
|
||||
className={colors.subdued}
|
||||
|
@ -33,5 +21,5 @@ export const IconChartBarHorizontalStacked = ({
|
|||
d="M29 16a1 1 0 011 1v4a1 1 0 01-1 1h-9v-6h9zm-9.222-8C20.453 8 21 8.448 21 9v4c0 .552-.547 1-1.222 1H17V8h2.778zm3.18-8C23.534 0 24 .448 24 1v4c0 .552-.466 1-1.042 1H18V0h4.958z"
|
||||
className={colors.accent}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,22 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartBarPercentage = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartBarPercentage = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M6 13v8a1 1 0 01-1 1H1a1 1 0 01-1-1v-8h6zm8-4v12a1 1 0 01-1 1H9a1 1 0 01-1-1V9h6zm8 4v8a1 1 0 01-1 1h-4a1 1 0 01-1-1v-8h6zm8 1v7a1 1 0 01-1 1h-4a1 1 0 01-1-1v-7h6z"
|
||||
className={colors.subdued}
|
||||
|
@ -33,5 +21,5 @@ export const IconChartBarPercentage = ({
|
|||
d="M29 0a1 1 0 011 1v11h-6V1a1 1 0 011-1h4zM5 0a1 1 0 011 1v10H0V1a1 1 0 011-1h4zm16 0a1 1 0 011 1v10h-6V1a1 1 0 011-1h4zm-8 0a1 1 0 011 1v6H8V1a1 1 0 011-1h4z"
|
||||
className={colors.accent}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartBarStacked = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartBarStacked = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.subdued}
|
||||
d="M6 13v8a1 1 0 01-1 1H1a1 1 0 01-1-1v-8h6zm8-4v12a1 1 0 01-1 1H9a1 1 0 01-1-1V9h6zm8 4v8a1 1 0 01-1 1h-4a1 1 0 01-1-1v-8h6zm8 1v7a1 1 0 01-1 1h-4a1 1 0 01-1-1v-7h6z"
|
||||
|
@ -29,5 +21,5 @@ export const IconChartBarStacked = ({ title, titleId, ...props }: Omit<EuiIconPr
|
|||
d="M29 1a1 1 0 011 1v10h-6V2a1 1 0 011-1h4zM5 7a1 1 0 011 1v3H0V8a1 1 0 011-1h4zm16-4a1 1 0 011 1v7h-6V4a1 1 0 011-1h4zm-8-3a1 1 0 011 1v6H8V1a1 1 0 011-1h4z"
|
||||
className={colors.accent}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartDatatable = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartDatatable = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M11 18a1 1 0 011 1v2a1 1 0 01-1 1H1a1 1 0 01-1-1v-2a1 1 0 011-1h10zm0-6a1 1 0 011 1v2a1 1 0 01-1 1H1a1 1 0 01-1-1v-2a1 1 0 011-1h10zm0-6a1 1 0 011 1v2a1 1 0 01-1 1H1a1 1 0 01-1-1V7a1 1 0 011-1h10zm18-6a1 1 0 011 1v2a1 1 0 01-1 1H1a1 1 0 01-1-1V1a1 1 0 011-1h28z"
|
||||
className={colors.subdued}
|
||||
|
@ -29,5 +21,5 @@ export const IconChartDatatable = ({ title, titleId, ...props }: Omit<EuiIconPro
|
|||
d="M20 18a1 1 0 011 1v2a1 1 0 01-.883.993L20 22h-5a1 1 0 01-1-1v-2a1 1 0 011-1h5zm9 0a1 1 0 011 1v2a1 1 0 01-1 1h-5a1 1 0 01-1-1v-2a1 1 0 011-1h5zm-9-6a1 1 0 011 1v2a1 1 0 01-.883.993L20 16h-5a1 1 0 01-1-1v-2a1 1 0 011-1h5zm9 0a1 1 0 011 1v2a1 1 0 01-1 1h-5a1 1 0 01-1-1v-2a1 1 0 011-1h5zm-9-6a1 1 0 011 1v2a1 1 0 01-.883.993L20 10h-5a1 1 0 01-1-1V7a1 1 0 011-1h5zm9 0a1 1 0 011 1v2a1 1 0 01-1 1h-5a1 1 0 01-1-1V7a1 1 0 011-1h5z"
|
||||
className={colors.accent}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartDonut = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartDonut = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M19.21 21.119a11 11 0 006.595-8.1c.11-.577-.355-1.082-.942-1.082H20.75c-.477 0-.878.342-1.046.788a5.028 5.028 0 11-6.474-6.474c.447-.168.788-.569.788-1.046V1.094c0-.588-.505-1.053-1.082-.943a11 11 0 106.272 20.968h.002z"
|
||||
className={colors.subdued}
|
||||
|
@ -29,5 +21,5 @@ export const IconChartDonut = ({ title, titleId, ...props }: Omit<EuiIconProps,
|
|||
d="M22.778 3.176A11 11 0 0017.084.154C16.507.042 16 .507 16 1.095v4.116c0 .475.34.875.784 1.044l.14.055A5.026 5.026 0 0119.7 9.17c.168.445.568.784 1.044.784h4.115c.588 0 1.053-.506.942-1.084a11 11 0 00-3.023-5.694z"
|
||||
className={colors.accent}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
25
packages/kbn-chart-icons/src/assets/chart_gauge.tsx
Normal file
25
packages/kbn-chart-icons/src/assets/chart_gauge.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 type { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartGauge = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.subdued}
|
||||
d="M1 13a1 1 0 00-1 1v2a1 1 0 102 0v-1h5v1a1 1 0 102 0v-1h5v1a1 1 0 102 0v-1h5v1a1 1 0 102 0v-1h5v1a1 1 0 102 0v-2a1 1 0 00-1-1H1z"
|
||||
/>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M0 6a1 1 0 011-1h24a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V6z"
|
||||
/>
|
||||
</ChartIconWrapper>
|
||||
);
|
|
@ -9,28 +9,18 @@
|
|||
import React from 'react';
|
||||
import type { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartGaugeArc = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => {
|
||||
return (
|
||||
<svg
|
||||
width="30"
|
||||
height="22"
|
||||
viewBox="0 0 30 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M5.9 20.7c-.5 0-1-.2-1.3-.7-1.2-1.9-1.9-4.2-1.9-6.5C2.8 6.8 8.3 1.3 15 1.3c5.3 0 10.2 3.6 11.7 8.7.2.8-.2 1.6-1 1.9-.8.2-1.6-.2-1.9-1C22.7 7 19 4.3 15 4.3c-5.1 0-9.2 4.1-9.2 9.2 0 1.7.5 3.4 1.4 4.9.4.7.2 1.6-.5 2.1-.2.1-.5.2-.8.2z"
|
||||
/>
|
||||
<g className={colors.subdued}>
|
||||
<path d="m10 18.6.8-.6-.6-.8-1.5 1.2v.1c.3.4.9.4 1.3.1zm9.2-.6.8.6c.4.3 1 .3 1.3-.2l-1.5-1.2-.6.8z" />
|
||||
<path d="m21.3 18.5-1.6-1.2c.9-1.1 1.3-2.4 1.3-3.8 0-3.3-2.7-6-6-6s-6 2.7-6 6c0 1.3.4 2.6 1.3 3.7l-1.6 1.2C7.5 17 7 15.3 7 13.5c0-4.4 3.6-8 8-8s8 3.6 8 8c0 1.8-.6 3.6-1.7 5z" />
|
||||
<path d="m10.7 18.1.7-.5c.4-.3.4-.9.1-1.2-.3-.4-.9-.4-1.2-.1l-.7.5M8.7 13l.9.2c.5.1.9-.2 1-.7s-.2-.9-.7-1l-.9-.2m2.5-3 .4.8c.2.4.7.6 1.2.4.4-.2.6-.7.4-1.2l-.4-.8m3.8 0-.4.8c-.2.4 0 1 .4 1.2s1 0 1.2-.4l.4-.8m2.4 3-.9.2c-.5.1-.8.6-.7 1s.6.8 1 .7l.9-.2m-.8 3.7-.7-.5c-.4-.3-.9-.2-1.2.1-.3.4-.2.9.1 1.2l.7.5" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const IconChartGaugeArc = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M5.9 20.7c-.5 0-1-.2-1.3-.7-1.2-1.9-1.9-4.2-1.9-6.5C2.8 6.8 8.3 1.3 15 1.3c5.3 0 10.2 3.6 11.7 8.7.2.8-.2 1.6-1 1.9-.8.2-1.6-.2-1.9-1C22.7 7 19 4.3 15 4.3c-5.1 0-9.2 4.1-9.2 9.2 0 1.7.5 3.4 1.4 4.9.4.7.2 1.6-.5 2.1-.2.1-.5.2-.8.2z"
|
||||
/>
|
||||
<g className={colors.subdued}>
|
||||
<path d="m10 18.6.8-.6-.6-.8-1.5 1.2v.1c.3.4.9.4 1.3.1zm9.2-.6.8.6c.4.3 1 .3 1.3-.2l-1.5-1.2-.6.8z" />
|
||||
<path d="m21.3 18.5-1.6-1.2c.9-1.1 1.3-2.4 1.3-3.8 0-3.3-2.7-6-6-6s-6 2.7-6 6c0 1.3.4 2.6 1.3 3.7l-1.6 1.2C7.5 17 7 15.3 7 13.5c0-4.4 3.6-8 8-8s8 3.6 8 8c0 1.8-.6 3.6-1.7 5z" />
|
||||
<path d="m10.7 18.1.7-.5c.4-.3.4-.9.1-1.2-.3-.4-.9-.4-1.2-.1l-.7.5M8.7 13l.9.2c.5.1.9-.2 1-.7s-.2-.9-.7-1l-.9-.2m2.5-3 .4.8c.2.4.7.6 1.2.4.4-.2.6-.7.4-1.2l-.4-.8m3.8 0-.4.8c-.2.4 0 1 .4 1.2s1 0 1.2-.4l.4-.8m2.4 3-.9.2c-.5.1-.8.6-.7 1s.6.8 1 .7l.9-.2m-.8 3.7-.7-.5c-.4-.3-.9-.2-1.2.1-.3.4-.2.9.1 1.2l.7.5" />
|
||||
</g>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,27 +9,17 @@
|
|||
import React from 'react';
|
||||
import type { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartGaugeCircle = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => {
|
||||
return (
|
||||
<svg
|
||||
width="30"
|
||||
height="22"
|
||||
viewBox="0 0 30 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M5.9 20.3c-.5 0-1-.2-1.3-.7-1.2-1.9-1.9-4.2-1.9-6.5C2.8 6.4 8.3.9 15 .9c5.3 0 10.2 3.6 11.7 8.7.2.8-.2 1.6-1 1.9-.8.2-1.6-.2-1.9-1C22.7 6.6 19 3.9 15 3.9c-5.1 0-9.2 4.1-9.2 9.2 0 1.7.5 3.4 1.4 4.9.4.7.2 1.6-.5 2.1-.2.1-.5.2-.8.2z"
|
||||
/>
|
||||
<g className={colors.subdued}>
|
||||
<path d="M15 21.1c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8zm0-14c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6z" />
|
||||
<path d="M15.9 19.3v-.9c0-.5-.4-.9-.9-.9s-.9.4-.9.9v.9m-3.4-1.7.7-.5c.4-.3.4-.9.1-1.2-.3-.4-.9-.4-1.2-.1l-.7.5m-.9-3.7.9.2c.5.1.9-.2 1-.7s-.2-.9-.7-1l-.9-.2m2.5-3 .4.8c.2.4.7.6 1.2.4.4-.2.6-.7.4-1.2l-.4-.8m3.8 0-.4.8c-.2.4 0 1 .4 1.2s1 0 1.2-.4l.4-.8m2.4 3-.9.1c-.5.1-.8.6-.7 1s.6.8 1 .7l.9-.2m-.8 3.8-.7-.5c-.4-.3-.9-.2-1.2.1-.3.4-.2.9.1 1.2l.7.5" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const IconChartGaugeCircle = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M5.9 20.3c-.5 0-1-.2-1.3-.7-1.2-1.9-1.9-4.2-1.9-6.5C2.8 6.4 8.3.9 15 .9c5.3 0 10.2 3.6 11.7 8.7.2.8-.2 1.6-1 1.9-.8.2-1.6-.2-1.9-1C22.7 6.6 19 3.9 15 3.9c-5.1 0-9.2 4.1-9.2 9.2 0 1.7.5 3.4 1.4 4.9.4.7.2 1.6-.5 2.1-.2.1-.5.2-.8.2z"
|
||||
/>
|
||||
<g className={colors.subdued}>
|
||||
<path d="M15 21.1c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8zm0-14c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6z" />
|
||||
<path d="M15.9 19.3v-.9c0-.5-.4-.9-.9-.9s-.9.4-.9.9v.9m-3.4-1.7.7-.5c.4-.3.4-.9.1-1.2-.3-.4-.9-.4-1.2-.1l-.7.5m-.9-3.7.9.2c.5.1.9-.2 1-.7s-.2-.9-.7-1l-.9-.2m2.5-3 .4.8c.2.4.7.6 1.2.4.4-.2.6-.7.4-1.2l-.4-.8m3.8 0-.4.8c-.2.4 0 1 .4 1.2s1 0 1.2-.4l.4-.8m2.4 3-.9.1c-.5.1-.8.6-.7 1s.6.8 1 .7l.9-.2m-.8 3.8-.7-.5c-.4-.3-.9-.2-1.2.1-.3.4-.2.9.1 1.2l.7.5" />
|
||||
</g>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,32 +9,18 @@
|
|||
import React from 'react';
|
||||
import type { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartGaugeSemiCircle = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => {
|
||||
return (
|
||||
<svg
|
||||
width="30"
|
||||
height="22"
|
||||
viewBox="0 0 30 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M3 17.9c-.8 0-1.5-.7-1.5-1.5 0-3.1 1.1-6.1 3-8.5 2.6-3.2 6.5-5 10.6-5 3.4 0 6.7 1.3 9.2 3.6.6.6.7 1.5.1 2.1-.6.6-1.5.6-2.1.1-2-1.8-4.5-2.8-7.2-2.8-3.2 0-6.2 1.4-8.2 3.9-1.5 1.9-2.3 4.2-2.3 6.6-.1.9-.7 1.5-1.6 1.5z"
|
||||
/>
|
||||
<g className={colors.subdued}>
|
||||
<path d="M24.3 16.7h-2c0-4-3.3-7.3-7.3-7.3-2.2 0-4.3 1-5.7 2.7-1.1 1.3-1.6 2.9-1.6 4.6h-2c0-2.1.7-4.2 2.1-5.8 1.8-2.2 4.4-3.5 7.3-3.5 5-.1 9.2 4.1 9.2 9.3z" />
|
||||
<path d="M7.5 17.6h1c.6 0 1-.4 1-1s-.4-1-1-1h-1m6.7-6.8v1c0 .6.4 1 1 1s1-.4 1-1v-1m-7.3 3.1.7.7c.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4l-.7-.7m9.3 0-.7.7c-.4.4-.4 1 0 1.4.4.4 1 .4 1.4 0l.7-.7m1.55 3.7h-1c-.6 0-1 .4-1 1s.4 1 1 1h1" />
|
||||
<path d="M6.6 17.6h1v-1H5.7c0 .5.4 1 .9 1zm15.8 0h1c.5 0 1-.4 1-1h-1.9v1z" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const IconChartGaugeSemiCircle = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M3 17.9c-.8 0-1.5-.7-1.5-1.5 0-3.1 1.1-6.1 3-8.5 2.6-3.2 6.5-5 10.6-5 3.4 0 6.7 1.3 9.2 3.6.6.6.7 1.5.1 2.1-.6.6-1.5.6-2.1.1-2-1.8-4.5-2.8-7.2-2.8-3.2 0-6.2 1.4-8.2 3.9-1.5 1.9-2.3 4.2-2.3 6.6-.1.9-.7 1.5-1.6 1.5z"
|
||||
/>
|
||||
<g className={colors.subdued}>
|
||||
<path d="M24.3 16.7h-2c0-4-3.3-7.3-7.3-7.3-2.2 0-4.3 1-5.7 2.7-1.1 1.3-1.6 2.9-1.6 4.6h-2c0-2.1.7-4.2 2.1-5.8 1.8-2.2 4.4-3.5 7.3-3.5 5-.1 9.2 4.1 9.2 9.3z" />
|
||||
<path d="M7.5 17.6h1c.6 0 1-.4 1-1s-.4-1-1-1h-1m6.7-6.8v1c0 .6.4 1 1 1s1-.4 1-1v-1m-7.3 3.1.7.7c.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4l-.7-.7m9.3 0-.7.7c-.4.4-.4 1 0 1.4.4.4 1 .4 1.4 0l.7-.7m1.55 3.7h-1c-.6 0-1 .4-1 1s.4 1 1 1h1" />
|
||||
<path d="M6.6 17.6h1v-1H5.7c0 .5.4 1 .9 1zm15.8 0h1c.5 0 1-.4 1-1h-1.9v1z" />
|
||||
</g>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 type { EuiIconProps } from '@elastic/eui';
|
||||
import { IconSimpleWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartLinearSimple = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M0 7.5C0 7.22386 0.223858 7 0.5 7H15.5C15.7761 7 16 7.22386 16 7.5V9.5C16 9.77614 15.7761 10 15.5 10C15.2239 10 15 9.77614 15 9.5V8H1V9.5C1 9.77614 0.776142 10 0.5 10C0.223858 10 0 9.77614 0 9.5V7.5Z" />
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
||||
export const IconChartGaugeSemiCircleSimple = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M1.01894 11H2.5C2.77614 11 3 11.2239 3 11.5C3 11.7761 2.77614 12 2.5 12H0.5C0.223858 12 0 11.7761 0 11.5C0 7.35786 3.35786 4 7.5 4C11.6421 4 15 7.35786 15 11.5C15 11.7761 14.7761 12 14.5 12H12.5C12.2239 12 12 11.7761 12 11.5C12 11.2239 12.2239 11 12.5 11H13.9811C13.7257 7.64378 10.9216 5 7.5 5C4.07839 5 1.27426 7.64378 1.01894 11Z" />
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
||||
export const IconChartGaugeCircleSimple = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M7.5 1C3.91015 1 1 3.91015 1 7.5C1 11.0899 3.91015 14 7.5 14C11.0899 14 14 11.0899 14 7.5C14 3.91015 11.0899 1 7.5 1ZM0 7.5C0 3.35786 3.35786 0 7.5 0C11.6421 0 15 3.35786 15 7.5C15 11.6421 11.6421 15 7.5 15C3.35786 15 0 11.6421 0 7.5Z" />
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
||||
export const IconChartGaugeArcSimple = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M12.0962 3.90381C9.55779 1.3654 5.44221 1.3654 2.90381 3.90381C0.484359 6.32325 0.370973 10.1755 2.56365 12.7292L3.61091 11.682C3.80617 11.4867 4.12276 11.4867 4.31802 11.682C4.51328 11.8772 4.51328 12.1938 4.31802 12.3891L2.90381 13.8033C2.70854 13.9986 2.39196 13.9986 2.1967 13.8033C-0.732233 10.8744 -0.732233 6.12563 2.1967 3.1967C5.12563 0.267767 9.87437 0.267767 12.8033 3.1967C15.7322 6.12563 15.7322 10.8744 12.8033 13.8033C12.608 13.9986 12.2915 13.9986 12.0962 13.8033L10.682 12.3891C10.4867 12.1938 10.4867 11.8772 10.682 11.682C10.8772 11.4867 11.1938 11.4867 11.3891 11.682L12.4364 12.7292C14.629 10.1755 14.5156 6.32325 12.0962 3.90381Z" />
|
||||
</IconSimpleWrapper>
|
||||
);
|
|
@ -9,27 +9,17 @@
|
|||
import { EuiIconProps } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartHeatmap = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => {
|
||||
return (
|
||||
<svg
|
||||
width={30}
|
||||
height={22}
|
||||
viewBox="0 0 30 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
className={colors.subdued}
|
||||
d="M16 1a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V1zM0 17a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1v-4zm17-9a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V9a1 1 0 00-1-1h-4zm-1 9a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4zm9-17a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V1a1 1 0 00-1-1h-4z"
|
||||
/>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M0 1a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V1zm0 8a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V9zm9-9a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V1a1 1 0 00-1-1H9zM8 9a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H9a1 1 0 01-1-1V9zm1 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1v-4a1 1 0 00-1-1H9zm15-7a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V9zm1 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1v-4a1 1 0 00-1-1h-4z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const IconChartHeatmap = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.subdued}
|
||||
d="M16 1a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V1zM0 17a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1v-4zm17-9a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V9a1 1 0 00-1-1h-4zm-1 9a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4zm9-17a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V1a1 1 0 00-1-1h-4z"
|
||||
/>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M0 1a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V1zm0 8a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V9zm9-9a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V1a1 1 0 00-1-1H9zM8 9a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H9a1 1 0 01-1-1V9zm1 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1v-4a1 1 0 00-1-1H9zm15-7a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V9zm1 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1v-4a1 1 0 00-1-1h-4z"
|
||||
/>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,31 +9,17 @@
|
|||
import React from 'react';
|
||||
import type { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartHorizontalBullet = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => {
|
||||
return (
|
||||
<svg
|
||||
width="30"
|
||||
height="22"
|
||||
viewBox="0 0 30 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
className={colors.subdued}
|
||||
d="M1 13a1 1 0 00-1 1v2a1 1 0 102 0v-1h5v1a1 1 0 102 0v-1h5v1a1 1 0 102 0v-1h5v1a1 1 0 102 0v-1h5v1a1 1 0 102 0v-2a1 1 0 00-1-1H1z"
|
||||
/>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M0 6a1 1 0 011-1h24a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V6z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const IconChartHorizontalBullet = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.subdued}
|
||||
d="M1 13a1 1 0 00-1 1v2a1 1 0 102 0v-1h5v1a1 1 0 102 0v-1h5v1a1 1 0 102 0v-1h5v1a1 1 0 102 0v-1h5v1a1 1 0 102 0v-2a1 1 0 00-1-1H1z"
|
||||
/>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M0 6a1 1 0 011-1h24a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V6z"
|
||||
/>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartLine = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartLine = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M23.434 16.456c.211.553.406.982.58 1.277l.073.119c.038.058.072.105.101.141.52-.04 1.07-.248 2.13-.77l.235-.117C28.198 16.283 28.953 16 30 16v2c-.507 0-.988.146-1.89.571l-1.209.592C25.621 19.78 24.924 20 24 20l-.174-.005c-1.251-.076-1.805-1.036-2.994-4.993L22.93 15c.18.563.335 1.012.505 1.455zM6 1c1.269 0 1.966.69 3.492 2.939l.774 1.146.272.387.26.358C11.419 6.658 11.788 7 12 7c.466 0 .967-.252 1.99-.997l.891-.659.458-.325C16.424 4.272 17.093 4 18 4c1.377 0 1.925.863 3.168 4.999L19.07 9a26.766 26.766 0 00-.505-1.457c-.24-.631-.46-1.1-.652-1.395l-.051-.074-.045-.06c-.452.064-.988.375-2.007 1.13l-.691.512-.458.325C13.576 8.728 12.907 9 12 9c-1.269 0-1.966-.69-3.492-2.939l-.774-1.146-.272-.387-.26-.358C6.581 3.342 6.213 3 6 3c-.294 0-.885.651-2.017 2.33l-.491.731-.326.475C1.859 8.409 1.175 9 0 9V7c.294 0 .885-.651 2.017-2.33l.491-.731.326-.475C4.141 1.591 4.825 1 6 1z"
|
||||
className={colors.subdued}
|
||||
|
@ -29,5 +21,5 @@ export const IconChartLine = ({ title, titleId, ...props }: Omit<EuiIconProps, '
|
|||
d="M0 21c1.123 0 1.852-.477 3.295-1.885l.758-.75.345-.33C5.208 17.275 5.648 17 6 17c.466 0 .967.252 1.99.997l.891.659.458.325C10.424 19.728 11.093 20 12 20c1.325 0 1.996-.772 3.546-3.444l.593-1.028.385-.646C17.328 13.562 17.796 13 18 13h6c1.333 0 1.978-.795 3.452-3.676l.692-1.37.358-.69C29.333 5.7 29.831 5 30 5V3c-1.333 0-1.978.795-3.452 3.676l-.692 1.37-.358.69C24.667 10.3 24.169 11 24 11h-6c-1.325 0-1.996.772-3.546 3.444l-.593 1.028-.385.646C12.672 17.438 12.204 18 12 18c-.466 0-.967-.252-1.99-.997l-.891-.659-.458-.325C7.576 15.272 6.907 15 6 15c-1.123 0-1.852.477-3.295 1.885l-.758.75-.345.33C.792 18.725.352 19 0 19v2z"
|
||||
className={colors.accent}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartMetric = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartMetric = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M25 0a1 1 0 011 1v14a1 1 0 01-1 1H5a1 1 0 01-1-1V1a1 1 0 011-1h20zm-9.878 3c-1.887 0-3.265 1.107-3.26 2.61-.005 1.054.67 1.938 1.672 2.157v.067l-.155.03a2.453 2.453 0 00-1.879 2.394C11.495 11.84 12.997 13 15.122 13c2.105 0 3.612-1.16 3.617-2.742-.005-1.217-.903-2.234-2.035-2.424v-.067l.162-.042c.896-.275 1.507-1.12 1.511-2.116C18.373 4.112 16.994 3 15.122 3zm0 5.542c1.036 0 1.796.665 1.806 1.592-.01.898-.718 1.507-1.806 1.507-1.103 0-1.816-.609-1.806-1.507-.01-.932.755-1.592 1.806-1.592zm0-4.164c.908 0 1.53.561 1.54 1.398-.01.85-.651 1.43-1.54 1.43-.903 0-1.55-.584-1.54-1.43-.01-.837.618-1.398 1.54-1.398z"
|
||||
className={colors.accent}
|
||||
|
@ -29,5 +21,5 @@ export const IconChartMetric = ({ title, titleId, ...props }: Omit<EuiIconProps,
|
|||
d="M1 18h28a1 1 0 011 1v2a1 1 0 01-1 1H1a1 1 0 01-1-1v-2a1 1 0 011-1z"
|
||||
className={colors.subdued}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartMixedXy = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartMixedXy = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M24 16l.186-.002.345-.02.266-.03.334-.058.193-.043.25-.067.223-.07.215-.074.209-.079.305-.124c.166-.07.34-.148.524-.234l.285-.135 1.202-.588c.428-.203.728-.326.966-.396.146-.044.27-.067.384-.076L30 14v7a1 1 0 01-1 1H1a1 1 0 01-1-1v-2c1 0 3.5-4 6-4s5 3 6 3 3.23-7.994 5.865-7.997L19.032 10l.541 1.205.272.595.29.612c.517 1.069.955 1.842 1.391 2.391.08.1.16.194.241.28.095.101.189.191.283.272l.143.114c.218.164.446.284.69.368.207.071.426.116.662.14l.14.012.154.008L24 16zm6-12v6l-.186.002-.345.02-.266.03-.331.057-.196.044-.25.067-.304.097-.134.047a9.492 9.492 0 00-.386.15l-.128.053c-.166.07-.34.148-.524.234l-.285.135-1.264.618-.352.159-.187.078-.256.095-.178.054a13.872 13.872 0 01-.38-.687l-.16-.311-.233-.47-.016-.472H24c1 0 3-6 6-6z"
|
||||
className={colors.accent}
|
||||
|
@ -33,5 +25,5 @@ export const IconChartMixedXy = ({ title, titleId, ...props }: Omit<EuiIconProps
|
|||
d="M6 13v7.889C6 21.503 5.552 22 5 22H1c-.552 0-1-.497-1-1.111V13a1 1 0 011-1h4a1 1 0 011 1zm8-1v9a1 1 0 01-1 1H9a1 1 0 01-1-1v-9a1 1 0 011-1h4a1 1 0 011 1zm8 5v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4a1 1 0 011-1h4a1 1 0 011 1zm8 2v2a1 1 0 01-1 1h-4a1 1 0 01-1-1v-2a1 1 0 011-1h4a1 1 0 011 1z"
|
||||
className={colors.subdued}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import type { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartMosaic = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId} /> : null}
|
||||
export const IconChartMosaic = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.subdued}
|
||||
d="M2 0a1 1 0 00-1 1v2a1 1 0 001 1h6a1 1 0 001-1V1a1 1 0 00-1-1H2zM2 14a1 1 0 00-1 1v6a1 1 0 001 1h6a1 1 0 001-1v-6a1 1 0 00-1-1H2zM11 13a1 1 0 011-1h6a1 1 0 011 1v8a1 1 0 01-1 1h-6a1 1 0 01-1-1v-8zM12 0a1 1 0 100 2h6a1 1 0 100-2h-6zM21 15a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1h-6a1 1 0 01-1-1v-6zM22 0a1 1 0 00-1 1v4a1 1 0 001 1h6a1 1 0 001-1V1a1 1 0 00-1-1h-6z"
|
||||
|
@ -29,5 +21,5 @@ export const IconChartMosaic = ({ title, titleId, ...props }: Omit<EuiIconProps,
|
|||
className={colors.accent}
|
||||
d="M11 5a1 1 0 011-1h6a1 1 0 011 1v4a1 1 0 01-1 1h-6a1 1 0 01-1-1V5zM1 7a1 1 0 011-1h6a1 1 0 011 1v4a1 1 0 01-1 1H2a1 1 0 01-1-1V7zM22 8a1 1 0 00-1 1v2a1 1 0 001 1h6a1 1 0 001-1V9a1 1 0 00-1-1h-6z"
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartPie = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartPie = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M17.827 21.189a10.001 10.001 0 005.952-7.148c.124-.578-.343-1.091-.935-1.091H14a1 1 0 01-1-1V3.106c0-.592-.513-1.059-1.092-.935a10 10 0 105.919 19.018z"
|
||||
className={colors.subdued}
|
||||
|
@ -29,5 +21,5 @@ export const IconChartPie = ({ title, titleId, ...props }: Omit<EuiIconProps, 't
|
|||
d="M22.462 3.538A12.29 12.29 0 0016.094.16C15.512.048 15 .514 15 1.106V10a1 1 0 001 1h8.895c.591 0 1.057-.512.945-1.094a12.288 12.288 0 00-3.378-6.368z"
|
||||
className={colors.accent}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -6,17 +6,13 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import React from 'react';
|
||||
import type { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartTagcloud: FunctionComponent = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="22" viewBox="0 0 30 22" {...props}>
|
||||
{title ? <title id={titleId} /> : null}
|
||||
export const IconChartTagcloud = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M19 5a2 2 0 0 1 2-2h4a2 2 0 1 1 0 4h-4a2 2 0 0 1-2-2ZM2 11a2 2 0 0 1 2-2h8a2 2 0 1 1 0 4H4a2 2 0 0 1-2-2Zm15 4a2 2 0 1 0 0 4h6a2 2 0 1 0 0-4h-6Z"
|
||||
className={colors.accent}
|
||||
|
@ -25,5 +21,5 @@ export const IconChartTagcloud: FunctionComponent = ({
|
|||
d="M6 4a1 1 0 0 0 0 2h4a1 1 0 1 0 0-2H6Zm8 0a1 1 0 1 0 0 2h2a1 1 0 1 0 0-2h-2Zm2 7a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H17a1 1 0 0 1-1-1Zm-8 5a1 1 0 1 0 0 2h4a1 1 0 1 0 0-2H8Z"
|
||||
className={colors.subdued}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartTreemap = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const IconChartTreemap = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
d="M0 1a1 1 0 011-1h13a1 1 0 011 1v20a1 1 0 01-1 1H1a1 1 0 01-1-1V1z"
|
||||
className={colors.subdued}
|
||||
|
@ -33,5 +25,5 @@ export const IconChartTreemap = ({ title, titleId, ...props }: Omit<EuiIconProps
|
|||
d="M29 16H18a1 1 0 00-1 1v4a1 1 0 001 1h11a1 1 0 001-1v-4a1 1 0 00-1-1z"
|
||||
className={colors.subdued}
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,31 +9,17 @@
|
|||
import React from 'react';
|
||||
import type { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartVerticalBullet = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => {
|
||||
return (
|
||||
<svg
|
||||
width="30"
|
||||
height="22"
|
||||
viewBox="0 0 30 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M16 22a1 1 0 01-1-1V4a1 1 0 011-1h4a1 1 0 011 1v17a1 1 0 01-1 1h-4z"
|
||||
/>
|
||||
<path
|
||||
className={colors.subdued}
|
||||
d="M10 0h2a1 1 0 011 1v20a1 1 0 01-1 1h-2a1 1 0 110-2h1v-3h-1a1 1 0 110-2h1v-3h-1a1 1 0 110-2h1V7h-1a1 1 0 010-2h1V2h-1a1 1 0 010-2z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const IconChartVerticalBullet = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M16 22a1 1 0 01-1-1V4a1 1 0 011-1h4a1 1 0 011 1v17a1 1 0 01-1 1h-4z"
|
||||
/>
|
||||
<path
|
||||
className={colors.subdued}
|
||||
d="M10 0h2a1 1 0 011 1v20a1 1 0 01-1 1h-2a1 1 0 110-2h1v-3h-1a1 1 0 110-2h1v-3h-1a1 1 0 110-2h1V7h-1a1 1 0 010-2h1V2h-1a1 1 0 010-2z"
|
||||
/>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import React from 'react';
|
||||
import type { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconChartWaffle = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId} /> : null}
|
||||
export const IconChartWaffle = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M16 1a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V1zM4 13a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1v-2zM17 6a1 1 0 00-1 1v2a1 1 0 001 1h2a1 1 0 001-1V7a1 1 0 00-1-1h-2zM23 0a1 1 0 00-1 1v2a1 1 0 001 1h2a1 1 0 001-1V1a1 1 0 00-1-1h-2zM5 0a1 1 0 00-1 1v2a1 1 0 001 1h2a1 1 0 001-1V1a1 1 0 00-1-1H5zM4 7a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V7zM11 0a1 1 0 00-1 1v2a1 1 0 001 1h2a1 1 0 001-1V1a1 1 0 00-1-1h-2zM10 7a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V7zM11 12a1 1 0 00-1 1v2a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 00-1-1h-2zM22 7a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V7z"
|
||||
|
@ -29,5 +21,5 @@ export const IconChartWaffle = ({ title, titleId, ...props }: Omit<EuiIconProps,
|
|||
className={colors.subdued}
|
||||
d="M22 13a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1v-2zM4 19a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1v-2zM16 19a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1v-2zM11 18a1 1 0 00-1 1v2a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 00-1-1h-2zM23 18a1 1 0 00-1 1v2a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 00-1-1h-2zM16 13a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1v-2z"
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
32
packages/kbn-chart-icons/src/assets/donut_hole_icons.tsx
Normal file
32
packages/kbn-chart-icons/src/assets/donut_hole_icons.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 type { EuiIconProps } from '@elastic/eui';
|
||||
import { IconSimpleWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconDonutHoleMedium = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M7.5 4a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7ZM5 7.5a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0Z" />
|
||||
<path d="M7.5 0a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15ZM1 7.5a6.5 6.5 0 1 1 13 0 6.5 6.5 0 0 1-13 0Z" />
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
||||
export const IconDonutHoleLarge = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M7.5 3a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9ZM4 7.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Z" />
|
||||
<path d="M7.5 0a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15ZM1 7.5a6.5 6.5 0 1 1 13 0 6.5 6.5 0 0 1-13 0Z" />
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
||||
export const IconDonutHoleSmall = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path d="M7.5 5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM6 7.5a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z" />
|
||||
<path d="M7.5 0a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15ZM1 7.5a6.5 6.5 0 1 1 13 0 6.5 6.5 0 0 1-13 0Z" />
|
||||
</IconSimpleWrapper>
|
||||
);
|
37
packages/kbn-chart-icons/src/assets/icon_simple_wrapper.tsx
Normal file
37
packages/kbn-chart-icons/src/assets/icon_simple_wrapper.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 type { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const IconSimpleWrapper = ({
|
||||
title,
|
||||
titleId,
|
||||
children,
|
||||
height = '16',
|
||||
width = '16',
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={`0 0 ${width} ${height}`}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChartIconWrapper = (props: Omit<EuiIconProps, 'type'>) =>
|
||||
IconSimpleWrapper({ ...props, width: '30', height: '22' });
|
|
@ -38,9 +38,17 @@ export { GlobeIllustration } from './globe_illustration';
|
|||
export { EuiIconLegend } from './legend';
|
||||
export { IconRegionMap } from './region_map';
|
||||
export { IconChartHeatmap } from './chart_heatmap';
|
||||
export { IconChartGauge } from './chart_gauge';
|
||||
export { IconChartHorizontalBullet } from './chart_horizontal_bullet';
|
||||
export { IconChartVerticalBullet } from './chart_vertical_bullet';
|
||||
export { IconChartGaugeSemiCircle } from './chart_gauge_semi_circle';
|
||||
export { IconChartGaugeArc } from './chart_gauge_arc';
|
||||
export { IconChartGaugeCircle } from './chart_gauge_circle';
|
||||
export { IconDonutHoleSmall, IconDonutHoleMedium, IconDonutHoleLarge } from './donut_hole_icons';
|
||||
export {
|
||||
IconChartLinearSimple,
|
||||
IconChartGaugeSemiCircleSimple,
|
||||
IconChartGaugeArcSimple,
|
||||
IconChartGaugeCircleSimple,
|
||||
} from './chart_gauge_simple_icons';
|
||||
export { IconChartTagcloud } from './chart_tagcloud';
|
||||
|
|
|
@ -7,18 +7,11 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import { IconSimpleWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const EuiIconLegend = ({ title, titleId, ...props }: { title: string; titleId: string }) => (
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
export const EuiIconLegend = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<IconSimpleWrapper {...props}>
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
fillRule="evenodd"
|
||||
|
@ -37,5 +30,5 @@ export const EuiIconLegend = ({ title, titleId, ...props }: { title: string; tit
|
|||
d="M3 16a2.5 2.5 0 100-5 2.5 2.5 0 000 5zm0-1a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"
|
||||
/>
|
||||
<path d="M8.5 13a.5.5 0 000 1h7a.5.5 0 000-1h-7z" />
|
||||
</svg>
|
||||
</IconSimpleWrapper>
|
||||
);
|
||||
|
|
|
@ -6,17 +6,13 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import React from 'react';
|
||||
import type { EuiIconProps } from '@elastic/eui';
|
||||
import { colors } from './common_styles';
|
||||
import { ChartIconWrapper } from './icon_simple_wrapper';
|
||||
|
||||
export const IconRegionMap: FunctionComponent = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="22" viewBox="0 0 30 22" {...props}>
|
||||
{title ? <title id={titleId} /> : null}
|
||||
export const IconRegionMap = (props: Omit<EuiIconProps, 'type'>) => (
|
||||
<ChartIconWrapper {...props}>
|
||||
<path
|
||||
className={colors.accent}
|
||||
d="M.985.001L1.01 0H7c.325 0 .502.078.602.145.105.07.188.17.254.302A1.46 1.46 0 018 1.01v3.982a1.46 1.46 0 01-.144.562.758.758 0 01-.254.302C7.502 5.922 7.325 6 7 6c-.325 0-.502-.078-.602-.145a.758.758 0 01-.254-.302A1.46 1.46 0 016 4.99v-.012a1.984 1.984 0 00-.006-.135 3.457 3.457 0 00-.35-1.29 2.754 2.754 0 00-.933-1.073C4.248 2.172 3.675 2 3 2H1.009a1.46 1.46 0 01-.562-.144.758.758 0 01-.302-.254C.078 1.502 0 1.325 0 1 0 .675.078.498.145.398A.758.758 0 01.447.144 1.46 1.46 0 01.985.001zM0 21v-5.991l.001-.024a1.46 1.46 0 01.143-.538.757.757 0 01.254-.302C.498 14.078.675 14 1 14h8c.675 0 1.248-.172 1.71-.48.458-.305.75-.704.934-1.073A3.453 3.453 0 0012 11.056v-.046l.002-.025a1.464 1.464 0 01.143-.538.758.758 0 01.254-.302c.1-.067.277-.145.602-.145.325 0 .502.078.602.145.105.07.188.17.254.302a1.464 1.464 0 01.143.538l.001.024V21c0 .325-.078.502-.145.602a.758.758 0 01-.302.254 1.464 1.464 0 01-.538.143h-.013L12.99 22H1.009l-.024-.001a1.464 1.464 0 01-.538-.143.758.758 0 01-.302-.254C.078 21.502 0 21.325 0 21zM27.009 14l-.024.001a1.464 1.464 0 00-.538.143.757.757 0 00-.302.255c-.067.099-.145.276-.145.601v1.991l.001.024a1.464 1.464 0 00.143.538.757.757 0 00.254.302c.1.067.277.145.602.145h1.991l.024-.001a1.464 1.464 0 00.538-.143.757.757 0 00.302-.254c.067-.1.145-.277.145-.602v-1.991l-.001-.024a1.464 1.464 0 00-.143-.538.757.757 0 00-.254-.302c-.1-.067-.277-.145-.602-.145h-1.991z"
|
||||
|
@ -29,5 +25,5 @@ export const IconRegionMap: FunctionComponent = ({
|
|||
className={colors.subdued}
|
||||
d="M20 3V1.009l.001-.024a1.464 1.464 0 01.143-.538.758.758 0 01.254-.302C20.498.078 20.675 0 21 0h8c.325 0 .502.078.602.145.105.07.188.17.254.302a1.464 1.464 0 01.143.538L30 1.01V11c0 .325-.078.502-.145.602a.758.758 0 01-.302.254 1.464 1.464 0 01-.538.143H29L28.99 12h-7.982l-.024-.001a1.464 1.464 0 01-.538-.143.758.758 0 01-.302-.254c-.067-.1-.145-.277-.145-.602 0-.325.078-.502.145-.602a.758.758 0 01.302-.254 1.464 1.464 0 01.538-.143L21.01 10h.025l.02-.001c.027 0 .061-.003.102-.005a3.456 3.456 0 001.29-.35 2.755 2.755 0 001.073-.933C23.828 8.248 24 7.675 24 7s-.172-1.248-.48-1.711a2.755 2.755 0 00-1.073-.933A3.456 3.456 0 0021.022 4h-.012a1.46 1.46 0 01-.563-.144.757.757 0 01-.302-.254C20.078 3.502 20 3.325 20 3zM20.998 14l-.013.001a1.464 1.464 0 00-.538.143.757.757 0 00-.302.255c-.067.099-.145.276-.145.601 0 .675-.172 1.248-.48 1.71-.305.458-.704.75-1.073.934a3.453 3.453 0 01-1.425.356h-.012l-.025.001a1.464 1.464 0 00-.538.143.757.757 0 00-.302.254c-.067.1-.145.277-.145.602v1.991l.001.024a1.464 1.464 0 00.143.538.757.757 0 00.254.302c.1.067.277.145.602.145h11.991l.024-.001a1.464 1.464 0 00.538-.143.758.758 0 00.302-.254c.067-.1.145-.277.145-.602 0-.325-.078-.502-.145-.602a.758.758 0 00-.302-.254A1.464 1.464 0 0028.99 20H27c-.675 0-1.248-.172-1.71-.48a2.755 2.755 0 01-.934-1.073A3.453 3.453 0 0124 17.022V15c0-.325-.078-.502-.145-.601a.757.757 0 00-.302-.255A1.464 1.464 0 0022.99 14H20.998z"
|
||||
/>
|
||||
</svg>
|
||||
</ChartIconWrapper>
|
||||
);
|
||||
|
|
|
@ -10,12 +10,14 @@
|
|||
|
||||
import moment from 'moment';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { screen, within, fireEvent } from '@testing-library/react';
|
||||
import { screen, within, fireEvent, Screen } from '@testing-library/react';
|
||||
|
||||
export const getSelectedButtonInGroup = (testId: string) => () => {
|
||||
const buttonGroup = screen.getByTestId(testId);
|
||||
return within(buttonGroup).getByRole('button', { pressed: true });
|
||||
};
|
||||
export const getSelectedButtonInGroup =
|
||||
(testId: string, container: Screen | ReturnType<typeof within> = screen) =>
|
||||
() => {
|
||||
const buttonGroup = container.getByTestId(testId);
|
||||
return within(buttonGroup).getByRole('button', { pressed: true });
|
||||
};
|
||||
|
||||
export class EuiButtonGroupTestHarness {
|
||||
#testId: string;
|
||||
|
|
|
@ -134,7 +134,7 @@ export const currentSuggestionMock = {
|
|||
} as Suggestion;
|
||||
|
||||
export const histogramESQLSuggestionMock = {
|
||||
title: 'Bar vertical stacked',
|
||||
title: 'Bar',
|
||||
score: 0.16666666666666666,
|
||||
hide: false,
|
||||
incomplete: false,
|
||||
|
|
|
@ -275,7 +275,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Bar vertical stacked');
|
||||
expect(await getCurrentVisTitle()).to.be('Bar');
|
||||
|
||||
await checkESQLHistogramVis(defaultTimespanESQL, '100');
|
||||
|
||||
|
@ -305,12 +305,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await checkESQLHistogramVis(defaultTimespanESQL, '5');
|
||||
await PageObjects.discover.chooseLensSuggestion('donut');
|
||||
await PageObjects.discover.chooseLensSuggestion('pie');
|
||||
|
||||
await testSubjects.existOrFail('unsavedChangesBadge');
|
||||
expect(await monacoEditor.getCodeEditorValue()).to.contain('averageA');
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
|
||||
|
@ -354,12 +354,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await checkESQLHistogramVis(defaultTimespanESQL, '5');
|
||||
await PageObjects.discover.chooseLensSuggestion('donut');
|
||||
await PageObjects.discover.chooseLensSuggestion('pie');
|
||||
|
||||
await testSubjects.existOrFail('unsavedChangesBadge');
|
||||
expect(await monacoEditor.getCodeEditorValue()).to.contain('averageA');
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
|
||||
|
@ -369,7 +369,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await testSubjects.missingOrFail('unsavedChangesBadge');
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
|
||||
|
@ -378,7 +378,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await testSubjects.missingOrFail('unsavedChangesBadge');
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
});
|
||||
|
||||
|
@ -407,12 +407,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await checkESQLHistogramVis(defaultTimespanESQL, '5');
|
||||
await PageObjects.discover.chooseLensSuggestion('donut');
|
||||
await PageObjects.discover.chooseLensSuggestion('pie');
|
||||
|
||||
await testSubjects.existOrFail('unsavedChangesBadge');
|
||||
expect(await monacoEditor.getCodeEditorValue()).to.contain('averageA');
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
|
||||
// now we customize the vis again
|
||||
|
@ -454,29 +454,29 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await checkESQLHistogramVis(defaultTimespanESQL, '5');
|
||||
await PageObjects.discover.chooseLensSuggestion('donut');
|
||||
await PageObjects.discover.chooseLensSuggestion('pie');
|
||||
|
||||
await PageObjects.discover.saveSearch('testCustomESQLVis');
|
||||
await PageObjects.discover.saveSearch('testCustomESQLVisDonut', true);
|
||||
await PageObjects.discover.saveSearch('testCustomESQLVisPartition', true);
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
|
||||
await browser.refresh();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
});
|
||||
|
||||
it('should be able to load a saved search with custom vis, edit query and revert changes', async () => {
|
||||
await PageObjects.discover.loadSavedSearch('testCustomESQLVisDonut');
|
||||
await PageObjects.discover.loadSavedSearch('testCustomESQLVisPartition');
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
|
||||
|
@ -489,7 +489,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Bar vertical stacked');
|
||||
expect(await getCurrentVisTitle()).to.be('Bar');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('histogramForESQL');
|
||||
|
||||
await checkESQLHistogramVis(defaultTimespanESQL, '100');
|
||||
|
@ -503,49 +503,49 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await testSubjects.missingOrFail('unsavedChangesBadge');
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
|
||||
expect(await monacoEditor.getCodeEditorValue()).to.contain('averageB');
|
||||
|
||||
// should be still Donut after reverting and saving again
|
||||
await PageObjects.discover.saveSearch('testCustomESQLVisDonut');
|
||||
// should be still Pie after reverting and saving again
|
||||
await PageObjects.discover.saveSearch('testCustomESQLVisPartition');
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await testSubjects.missingOrFail('unsavedChangesBadge');
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
});
|
||||
|
||||
it('should be able to change to an unfamiliar vis type via lens flyout', async () => {
|
||||
await PageObjects.discover.loadSavedSearch('testCustomESQLVisDonut');
|
||||
await PageObjects.discover.loadSavedSearch('testCustomESQLVisPartition');
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
|
||||
await testSubjects.missingOrFail('unsavedChangesBadge');
|
||||
|
||||
await changeVisShape('Pie');
|
||||
await changeVisShape('Treemap');
|
||||
|
||||
await testSubjects.existOrFail('unsavedChangesBadge');
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
expect(await getCurrentVisTitle()).to.be('Treemap');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
|
||||
await PageObjects.discover.saveSearch('testCustomESQLVisPie', true);
|
||||
await PageObjects.discover.saveSearch('testCustomESQLVisTreemap', true);
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
expect(await getCurrentVisTitle()).to.be('Treemap');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
|
||||
await browser.refresh();
|
||||
|
@ -553,7 +553,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
expect(await getCurrentVisTitle()).to.be('Treemap');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
|
||||
|
@ -564,7 +564,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Bar vertical stacked');
|
||||
expect(await getCurrentVisTitle()).to.be('Bar');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
|
||||
await testSubjects.existOrFail('unsavedChangesBadge');
|
||||
|
@ -575,7 +575,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await testSubjects.missingOrFail('unsavedChangesBadge');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
expect(await getCurrentVisTitle()).to.be('Treemap');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
});
|
||||
|
||||
|
@ -585,7 +585,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
|
||||
await testSubjects.missingOrFail('unsavedChangesBadge');
|
||||
|
@ -602,7 +602,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
await testSubjects.missingOrFail('unsavedChangesBadge');
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
|
||||
await PageObjects.discover.chooseLensSuggestion('barVerticalStacked');
|
||||
|
@ -629,7 +629,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
expect(await getCurrentVisTitle()).to.be('Bar vertical stacked');
|
||||
expect(await getCurrentVisTitle()).to.be('Bar');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
|
||||
await testSubjects.missingOrFail('unsavedChangesBadge');
|
||||
|
@ -644,8 +644,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await testSubjects.missingOrFail('unsavedChangesBadge');
|
||||
|
||||
await PageObjects.discover.chooseLensSuggestion('donut');
|
||||
expect(await getCurrentVisTitle()).to.be('Donut');
|
||||
await PageObjects.discover.chooseLensSuggestion('pie');
|
||||
expect(await getCurrentVisTitle()).to.be('Pie');
|
||||
await testSubjects.existOrFail('partitionVisChart');
|
||||
expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('lensSuggestion');
|
||||
|
||||
|
|
|
@ -44,20 +44,19 @@ export const getRotatingNumberVisualization = ({
|
|||
}): Visualization<RotatingNumberState> => ({
|
||||
id: 'rotatingNumber',
|
||||
|
||||
getVisualizationTypeId() {
|
||||
return 'rotatingNumber';
|
||||
},
|
||||
visualizationTypes: [
|
||||
{
|
||||
id: 'rotatingNumber',
|
||||
icon: 'refresh',
|
||||
label: 'Rotating number',
|
||||
groupLabel: 'Goal and single value',
|
||||
description: 'A number that rotates',
|
||||
sortPriority: 3,
|
||||
},
|
||||
],
|
||||
|
||||
getVisualizationTypeId() {
|
||||
return 'rotatingNumber';
|
||||
},
|
||||
|
||||
clearLayer(state) {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import './chart_switch.scss';
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiIcon,
|
||||
EuiText,
|
||||
EuiHighlight,
|
||||
IconType,
|
||||
useEuiTheme,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ChartOption = ({
|
||||
option,
|
||||
searchValue = '',
|
||||
}: {
|
||||
option: { label: string; description?: string; icon?: IconType };
|
||||
searchValue?: string;
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
css={css`
|
||||
text-align: left;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon className="lnsChartSwitch__chartIcon" type={option.icon || 'empty'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s" data-test-subj="lnsChartSwitch-option-label">
|
||||
<EuiHighlight search={searchValue}>{option.label}</EuiHighlight>
|
||||
</EuiText>
|
||||
<EuiText size="xs" color="subdued">
|
||||
{option.description ? (
|
||||
<EuiHighlight search={searchValue}>{option.description}</EuiHighlight>
|
||||
) : null}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const getDataLossWarning = (dataLoss: 'nothing' | 'layers' | 'everything' | 'columns') => {
|
||||
if (dataLoss === 'nothing') {
|
||||
return;
|
||||
}
|
||||
if (dataLoss === 'everything') {
|
||||
return i18n.translate('xpack.lens.chartSwitch.dataLossEverything', {
|
||||
defaultMessage: 'Changing to this visualization clears the current configuration.',
|
||||
});
|
||||
}
|
||||
if (dataLoss === 'layers') {
|
||||
return i18n.translate('xpack.lens.chartSwitch.dataLossLayersDescription', {
|
||||
defaultMessage:
|
||||
'Changing to this visualization modifies currently selected layer`s configuration and removes all other layers.',
|
||||
});
|
||||
} else
|
||||
return i18n.translate('xpack.lens.chartSwitch.dataLossColumns', {
|
||||
defaultMessage: `Changing to this visualization modifies the current configuration.`,
|
||||
});
|
||||
};
|
||||
|
||||
const CheckIcon = () => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
return <EuiIcon type="check" color={euiTheme.colors.darkestShade} />;
|
||||
};
|
||||
|
||||
const DataLossWarning = ({ content, id }: { content?: string; id: string }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
if (!content) return null;
|
||||
return (
|
||||
<EuiIconTip
|
||||
size="m"
|
||||
aria-label={content}
|
||||
type="dot"
|
||||
color={euiTheme.colors.warning}
|
||||
content={content}
|
||||
iconProps={{
|
||||
className: 'lnsChartSwitch__chartIcon',
|
||||
'data-test-subj': `lnsChartSwitchPopoverAlert_${id}`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChartSwitchOptionPrepend = ({
|
||||
isChecked,
|
||||
dataLoss,
|
||||
subtypeId,
|
||||
}: {
|
||||
isChecked: boolean;
|
||||
dataLoss: 'nothing' | 'layers' | 'everything' | 'columns';
|
||||
subtypeId: string;
|
||||
}) => {
|
||||
const dataLossWarning = getDataLossWarning(dataLoss);
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
{isChecked && <CheckIcon />}
|
||||
{dataLossWarning && <DataLossWarning content={dataLossWarning} id={subtypeId} />}
|
||||
{!dataLossWarning && !isChecked && <EuiIcon type="empty" />}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import './chart_switch.scss';
|
||||
import React from 'react';
|
||||
import { EuiFlexItem, EuiIconTip, EuiFlexGroup } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ExperimentalBadge } from '../../../../shared_components';
|
||||
|
||||
export const getDataLossWarning = (dataLoss: 'nothing' | 'layers' | 'everything' | 'columns') => {
|
||||
if (dataLoss === 'nothing') {
|
||||
return;
|
||||
}
|
||||
if (dataLoss === 'everything') {
|
||||
return i18n.translate('xpack.lens.chartSwitch.dataLossEverything', {
|
||||
defaultMessage: 'Changing to this visualization clears the current configuration.',
|
||||
});
|
||||
}
|
||||
if (dataLoss === 'layers') {
|
||||
return i18n.translate('xpack.lens.chartSwitch.dataLossLayersDescription', {
|
||||
defaultMessage:
|
||||
'Changing to this visualization modifies currently selected layer`s configuration and removes all other layers.',
|
||||
});
|
||||
} else
|
||||
return i18n.translate('xpack.lens.chartSwitch.dataLossColumns', {
|
||||
defaultMessage: `Changing to this visualization modifies the current configuration.`,
|
||||
});
|
||||
};
|
||||
|
||||
const DataLossWarning = ({ content, id }: { content?: string; id: string }) => {
|
||||
if (!content) return null;
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
size="s"
|
||||
aria-label={content}
|
||||
type="dot"
|
||||
color="warning"
|
||||
content={content}
|
||||
iconProps={{
|
||||
className: 'lnsChartSwitch__chartIcon',
|
||||
'data-test-subj': `lnsChartSwitchPopoverAlert_${id}`,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChartOptionAppend = ({
|
||||
dataLoss,
|
||||
showExperimentalBadge,
|
||||
id,
|
||||
}: {
|
||||
dataLoss: 'nothing' | 'layers' | 'everything' | 'columns';
|
||||
showExperimentalBadge?: boolean;
|
||||
id: string;
|
||||
}) => (
|
||||
<EuiFlexGroup
|
||||
gutterSize="xs"
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
className="lnsChartSwitch__append"
|
||||
>
|
||||
{showExperimentalBadge ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<ExperimentalBadge />
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<DataLossWarning content={getDataLossWarning(dataLoss)} id={id} />
|
||||
</EuiFlexGroup>
|
||||
);
|
|
@ -5,6 +5,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.lnsChartSwitch__options {
|
||||
width: 384px;
|
||||
}
|
||||
|
||||
.lnsChartSwitch__summaryIcon {
|
||||
transform: translateY(-1px);
|
||||
color: $euiTextSubduedColor;
|
||||
|
|
|
@ -18,8 +18,10 @@ import {
|
|||
} from '../../../../mocks';
|
||||
|
||||
import { DatasourcePublicAPI, SuggestionRequest, DatasourceSuggestion } from '../../../../types';
|
||||
import { ChartSwitch, ChartSwitchProps } from './chart_switch';
|
||||
import { ChartSwitchProps } from './chart_switch';
|
||||
import { ChartSwitchPopover } from './chart_switch_popover';
|
||||
import { LensAppState, applyChanges } from '../../../../state_management';
|
||||
import faker from 'faker';
|
||||
|
||||
const mockFrame = (layers: string[]) => ({
|
||||
...createMockFramePublicAPI(),
|
||||
|
@ -189,7 +191,8 @@ describe('chart_switch', () => {
|
|||
icon: 'empty',
|
||||
id,
|
||||
label: id,
|
||||
groupLabel: `${id}Group`,
|
||||
sortPriority: 1,
|
||||
description: faker.lorem.sentence(),
|
||||
})),
|
||||
getVisualizationTypeId: jest.fn((state) => state.type),
|
||||
getSuggestions: jest.fn((options) => {
|
||||
|
@ -224,7 +227,7 @@ describe('chart_switch', () => {
|
|||
}
|
||||
) => {
|
||||
const { store, ...rtlRender } = renderWithReduxStore(
|
||||
<ChartSwitch
|
||||
<ChartSwitchPopover
|
||||
framePublicAPI={frame}
|
||||
visualizationMap={visualizationMap}
|
||||
datasourceMap={datasourceMap}
|
||||
|
|
|
@ -7,18 +7,8 @@
|
|||
|
||||
import './chart_switch.scss';
|
||||
import React, { useState, useMemo, memo } from 'react';
|
||||
import {
|
||||
EuiIcon,
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSelectable,
|
||||
EuiSelectableOption,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ChartSwitchTrigger } from '@kbn/visualization-ui-components';
|
||||
import { ExperimentalBadge } from '../../../../shared_components';
|
||||
import {
|
||||
Visualization,
|
||||
FramePublicAPI,
|
||||
|
@ -26,6 +16,7 @@ import {
|
|||
VisualizationMap,
|
||||
DatasourceMap,
|
||||
Suggestion,
|
||||
DatasourcePublicAPI,
|
||||
} from '../../../../types';
|
||||
import { getSuggestions, switchToSuggestion } from '../../suggestion_helpers';
|
||||
import { showMemoizedErrorNotification } from '../../../../lens_ui_errors';
|
||||
|
@ -41,7 +32,13 @@ import {
|
|||
selectDatasourceStates,
|
||||
} from '../../../../state_management';
|
||||
import { generateId } from '../../../../id_generator/id_generator';
|
||||
import { ChartOptionAppend } from './chart_option_append';
|
||||
import { ChartSwitchSelectable, SelectableEntry } from './chart_switch_selectable';
|
||||
import { ChartSwitchOptionPrepend } from './chart_option';
|
||||
|
||||
type VisChartSwitchPosition = VisualizationType & {
|
||||
visualizationId: string;
|
||||
selection: VisualizationSelection;
|
||||
};
|
||||
|
||||
interface VisualizationSelection {
|
||||
visualizationId: string;
|
||||
|
@ -59,19 +56,23 @@ export interface ChartSwitchProps {
|
|||
visualizationMap: VisualizationMap;
|
||||
datasourceMap: DatasourceMap;
|
||||
layerId: string;
|
||||
onChartSelect: () => void;
|
||||
}
|
||||
|
||||
type SelectableEntry = EuiSelectableOption<{ value: string }>;
|
||||
const sortByPriority = (a: VisualizationType, b: VisualizationType) => {
|
||||
return a.sortPriority - b.sortPriority;
|
||||
};
|
||||
|
||||
const MAX_LIST_HEIGHT = 380;
|
||||
const ENTRY_HEIGHT = 32;
|
||||
|
||||
function computeListHeight(list: SelectableEntry[], maxHeight: number): number {
|
||||
if (list.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return Math.min(list.length * ENTRY_HEIGHT, maxHeight);
|
||||
}
|
||||
const deprecatedGroupChartSwitchElement = {
|
||||
key: 'deprecated',
|
||||
value: 'deprecated',
|
||||
label: i18n.translate('xpack.lens.configPanel.deprecated', {
|
||||
defaultMessage: 'Deprecated',
|
||||
}),
|
||||
isGroupLabel: true,
|
||||
'aria-label': 'deprecated',
|
||||
'data-test-subj': `lnsChartSwitchPopover_deprecated`,
|
||||
};
|
||||
|
||||
function safeFnCall<TReturn>(action: () => TReturn, defaultReturnValue: TReturn): TReturn {
|
||||
try {
|
||||
|
@ -82,28 +83,20 @@ function safeFnCall<TReturn>(action: () => TReturn, defaultReturnValue: TReturn)
|
|||
}
|
||||
}
|
||||
|
||||
function getCurrentVisualizationId(
|
||||
activeVisualization: Visualization,
|
||||
visualizationState: unknown
|
||||
) {
|
||||
return safeFnCall(
|
||||
() => activeVisualization.getVisualizationTypeId(visualizationState),
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
export const ChartSwitch = memo(function ChartSwitch({
|
||||
framePublicAPI,
|
||||
visualizationMap,
|
||||
datasourceMap,
|
||||
layerId,
|
||||
onChartSelect,
|
||||
}: ChartSwitchProps) {
|
||||
const [flyoutOpen, setFlyoutOpen] = useState<boolean>(false);
|
||||
const dispatchLens = useLensDispatch();
|
||||
const activeDatasourceId = useLensSelector(selectActiveDatasourceId);
|
||||
const visualization = useLensSelector(selectVisualization);
|
||||
const datasourceStates = useLensSelector(selectDatasourceStates);
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
const commitSelection = (selection: VisualizationSelection) => {
|
||||
switchToSuggestion(
|
||||
dispatchLens,
|
||||
|
@ -142,9 +135,6 @@ export const ChartSwitch = memo(function ChartSwitch({
|
|||
return state;
|
||||
};
|
||||
const layers = Object.entries(framePublicAPI.datasourceLayers);
|
||||
const containsData = layers.some(
|
||||
([_layerId, datasource]) => datasource && datasource.getTableSpec().length > 0
|
||||
);
|
||||
|
||||
// Always show the active visualization as a valid selection
|
||||
if (
|
||||
|
@ -183,19 +173,7 @@ export const ChartSwitch = memo(function ChartSwitch({
|
|||
layerId
|
||||
);
|
||||
|
||||
let dataLoss: VisualizationSelection['dataLoss'];
|
||||
|
||||
if (!containsData) {
|
||||
dataLoss = 'nothing';
|
||||
} else if (!topSuggestion) {
|
||||
dataLoss = 'everything';
|
||||
} else if (layers.length > 1 && layers.length > topSuggestion.keptLayerIds.length) {
|
||||
dataLoss = 'layers';
|
||||
} else if (topSuggestion.columns !== layers[0][1]?.getTableSpec().length) {
|
||||
dataLoss = 'columns';
|
||||
} else {
|
||||
dataLoss = 'nothing';
|
||||
}
|
||||
const dataLoss = getDataLoss(layers, topSuggestion);
|
||||
|
||||
function addNewLayer() {
|
||||
const newLayerId = generateId();
|
||||
|
@ -238,31 +216,18 @@ export const ChartSwitch = memo(function ChartSwitch({
|
|||
};
|
||||
}
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
const { visualizationTypes, visualizationsLookup } = useMemo(
|
||||
() => {
|
||||
if (!flyoutOpen) {
|
||||
return { visualizationTypes: [], visualizationsLookup: {} };
|
||||
}
|
||||
const subVisualizationId =
|
||||
visualization.activeId && visualizationMap[visualization.activeId]
|
||||
? getCurrentVisualizationId(visualizationMap[visualization.activeId], visualization.state)
|
||||
? getVisualizationTypeId(
|
||||
visualizationMap[visualization.activeId],
|
||||
visualization.state,
|
||||
layerId
|
||||
)
|
||||
: undefined;
|
||||
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||
// reorganize visualizations in groups
|
||||
const grouped: Record<
|
||||
string,
|
||||
{
|
||||
priority: number;
|
||||
visualizations: Array<
|
||||
VisualizationType & {
|
||||
visualizationId: string;
|
||||
selection: VisualizationSelection;
|
||||
}
|
||||
>;
|
||||
}
|
||||
> = {};
|
||||
|
||||
// Will need it later on to quickly pick up the metadata from it
|
||||
const lookup: Record<
|
||||
string,
|
||||
|
@ -271,176 +236,131 @@ export const ChartSwitch = memo(function ChartSwitch({
|
|||
selection: VisualizationSelection;
|
||||
}
|
||||
> = {};
|
||||
|
||||
const chartSwitchPositions: VisChartSwitchPosition[] = [];
|
||||
const deprecatedChartSwitchPositions: VisChartSwitchPosition[] = [];
|
||||
Object.entries(visualizationMap).forEach(([visualizationId, v]) => {
|
||||
for (const visualizationType of v.visualizationTypes) {
|
||||
const chartSwitchOptions = v.visualizationTypes;
|
||||
for (const visualizationType of chartSwitchOptions) {
|
||||
const isSearchMatch =
|
||||
visualizationType.label.toLowerCase().includes(lowercasedSearchTerm) ||
|
||||
visualizationType.fullLabel?.toLowerCase().includes(lowercasedSearchTerm);
|
||||
visualizationType.description?.toLowerCase().includes(lowercasedSearchTerm);
|
||||
if (isSearchMatch) {
|
||||
grouped[visualizationType.groupLabel] = grouped[visualizationType.groupLabel] || {
|
||||
priority: 0,
|
||||
visualizations: [],
|
||||
};
|
||||
const subtypeId = findSubtypeId(visualizationType, subVisualizationId);
|
||||
|
||||
const visualizationEntry = {
|
||||
...visualizationType,
|
||||
visualizationId,
|
||||
selection: getSelection(visualizationId, visualizationType.id),
|
||||
selection: getSelection(visualizationId, subtypeId),
|
||||
};
|
||||
grouped[visualizationType.groupLabel].priority += visualizationType.sortPriority || 0;
|
||||
grouped[visualizationType.groupLabel].visualizations.push(visualizationEntry);
|
||||
if (visualizationEntry.isDeprecated) {
|
||||
deprecatedChartSwitchPositions.push(visualizationEntry);
|
||||
} else {
|
||||
chartSwitchPositions.push(visualizationEntry);
|
||||
}
|
||||
lookup[`${visualizationId}:${visualizationType.id}`] = visualizationEntry;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const toSelectableEntry = (v: VisChartSwitchPosition): SelectableEntry => {
|
||||
const isChecked = subVisualizationId === v.id;
|
||||
return {
|
||||
'aria-label': v.label,
|
||||
className: 'lnsChartSwitch__option',
|
||||
key: `${v.visualizationId}:${v.id}`, // todo: should we simplify?
|
||||
value: `${v.visualizationId}:${v.id}`,
|
||||
'data-test-subj': `lnsChartSwitchPopover_${v.id}`,
|
||||
label: v.label,
|
||||
prepend: (
|
||||
<ChartSwitchOptionPrepend
|
||||
isChecked={isChecked}
|
||||
dataLoss={v.selection.dataLoss}
|
||||
subtypeId={v.selection.subVisualizationId}
|
||||
/>
|
||||
),
|
||||
data: {
|
||||
description: v.description,
|
||||
icon: v.icon,
|
||||
},
|
||||
append: v.showExperimentalBadge ? <ExperimentalBadge size="m" /> : null,
|
||||
// Apparently checked: null is not valid for TS
|
||||
...(isChecked && { checked: 'on' }),
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
visualizationTypes: Object.keys(grouped)
|
||||
.sort((groupA, groupB) => {
|
||||
return grouped[groupB].priority - grouped[groupA].priority;
|
||||
})
|
||||
.flatMap((group): SelectableEntry[] => {
|
||||
const { visualizations } = grouped[group];
|
||||
if (visualizations.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: group,
|
||||
label: group,
|
||||
isGroupLabel: true,
|
||||
'aria-label': group,
|
||||
'data-test-subj': `lnsChartSwitchPopover_${group}`,
|
||||
} as SelectableEntry,
|
||||
].concat(
|
||||
visualizations
|
||||
// alphabetical order within each group
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
(b.sortOrder ?? 0) - (a.sortOrder ?? 0) ||
|
||||
(a.fullLabel || a.label).localeCompare(b.fullLabel || b.label)
|
||||
);
|
||||
})
|
||||
.map((v): SelectableEntry => {
|
||||
return {
|
||||
'aria-label': v.fullLabel || v.label,
|
||||
className: 'lnsChartSwitch__option',
|
||||
isGroupLabel: false,
|
||||
key: `${v.visualizationId}:${v.id}`,
|
||||
value: `${v.visualizationId}:${v.id}`,
|
||||
'data-test-subj': `lnsChartSwitchPopover_${v.id}`,
|
||||
label: v.fullLabel || v.label,
|
||||
prepend: (
|
||||
<EuiIcon className="lnsChartSwitch__chartIcon" type={v.icon || 'empty'} />
|
||||
),
|
||||
append:
|
||||
v.selection.dataLoss !== 'nothing' || v.showExperimentalBadge ? (
|
||||
<ChartOptionAppend
|
||||
dataLoss={v.selection.dataLoss}
|
||||
showExperimentalBadge={v.showExperimentalBadge}
|
||||
id={v.selection.subVisualizationId}
|
||||
/>
|
||||
) : null,
|
||||
// Apparently checked: null is not valid for TS
|
||||
...(subVisualizationId === v.id && { checked: 'on' }),
|
||||
};
|
||||
})
|
||||
);
|
||||
}),
|
||||
visualizationTypes: chartSwitchPositions
|
||||
.sort(sortByPriority)
|
||||
.map(toSelectableEntry)
|
||||
.concat(
|
||||
deprecatedChartSwitchPositions.length
|
||||
? [
|
||||
deprecatedGroupChartSwitchElement,
|
||||
...deprecatedChartSwitchPositions.map(toSelectableEntry),
|
||||
]
|
||||
: []
|
||||
),
|
||||
visualizationsLookup: lookup,
|
||||
};
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
flyoutOpen,
|
||||
visualizationMap,
|
||||
framePublicAPI,
|
||||
visualization.activeId,
|
||||
visualization.state,
|
||||
searchTerm,
|
||||
]
|
||||
[visualizationMap, framePublicAPI, visualization.activeId, visualization.state, searchTerm]
|
||||
);
|
||||
|
||||
const { icon, label } = (visualization.activeId &&
|
||||
visualizationMap[visualization.activeId]?.getDescription(visualization.state, layerId)) || {
|
||||
label: i18n.translate('xpack.lens.configPanel.selectVisualization', {
|
||||
defaultMessage: 'Select a visualization',
|
||||
}),
|
||||
icon: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="lnsChartSwitch__header">
|
||||
<EuiPopover
|
||||
id="lnsChartSwitchPopover"
|
||||
ownFocus
|
||||
initialFocus=".lnsChartSwitch__popoverPanel"
|
||||
panelClassName="lnsChartSwitch__popoverPanel"
|
||||
panelPaddingSize="s"
|
||||
button={
|
||||
<ChartSwitchTrigger
|
||||
icon={icon}
|
||||
label={label}
|
||||
dataTestSubj="lnsChartSwitchPopover"
|
||||
onClick={() => setFlyoutOpen(!flyoutOpen)}
|
||||
/>
|
||||
<ChartSwitchSelectable
|
||||
searchable
|
||||
options={visualizationTypes}
|
||||
onChange={(newOptions: Array<{ label: string; value?: string; checked?: string }>) => {
|
||||
onChartSelect();
|
||||
const chosenType = newOptions.find(({ checked }) => checked === 'on');
|
||||
if (!chosenType || !chosenType.value) {
|
||||
return;
|
||||
}
|
||||
isOpen={flyoutOpen}
|
||||
closePopover={() => setFlyoutOpen(false)}
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiPopoverTitle>
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
{i18n.translate('xpack.lens.configPanel.visualizationType', {
|
||||
defaultMessage: 'Visualization type',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPopoverTitle>
|
||||
<EuiSelectable
|
||||
className="lnsChartSwitch__options"
|
||||
height={computeListHeight(visualizationTypes, MAX_LIST_HEIGHT)}
|
||||
searchable
|
||||
singleSelection
|
||||
isPreFiltered
|
||||
data-test-subj="lnsChartSwitchList"
|
||||
searchProps={{
|
||||
className: 'lnsChartSwitch__search',
|
||||
'data-test-subj': 'lnsChartSwitchSearch',
|
||||
onChange: (value) => setSearchTerm(value),
|
||||
}}
|
||||
options={visualizationTypes}
|
||||
onChange={(newOptions) => {
|
||||
setFlyoutOpen(false);
|
||||
const chosenType = newOptions.find(({ checked }) => checked === 'on');
|
||||
if (!chosenType) {
|
||||
return;
|
||||
}
|
||||
const id = chosenType.value!;
|
||||
commitSelection(visualizationsLookup[id].selection);
|
||||
}}
|
||||
noMatchesMessage={
|
||||
<FormattedMessage
|
||||
id="xpack.lens.chartSwitch.noResults"
|
||||
defaultMessage="No results found for {term}."
|
||||
values={{
|
||||
term: <strong>{searchTerm}</strong>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{(list, search) => (
|
||||
<>
|
||||
{search}
|
||||
{list}
|
||||
</>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
</EuiPopover>
|
||||
</div>
|
||||
const id = chosenType.value;
|
||||
commitSelection(visualizationsLookup[id].selection);
|
||||
}}
|
||||
searchTerm={searchTerm}
|
||||
setSearchTerm={setSearchTerm}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const getVisualizationSubtypes = (
|
||||
visualization: Visualization,
|
||||
state: unknown,
|
||||
layerId?: string
|
||||
) => {
|
||||
const typeId = safeFnCall(() => visualization.getVisualizationTypeId(state, layerId), undefined);
|
||||
const type = visualization.visualizationTypes.find((t) => t.id === typeId);
|
||||
if (type?.subtypes) {
|
||||
return type.subtypes;
|
||||
}
|
||||
return [typeId];
|
||||
};
|
||||
|
||||
const getVisualizationTypeId = (
|
||||
activeVisualization: Visualization,
|
||||
visualizationState: unknown,
|
||||
layerId?: string
|
||||
) =>
|
||||
safeFnCall(
|
||||
() => activeVisualization.getVisualizationTypeId(visualizationState, layerId),
|
||||
undefined
|
||||
);
|
||||
|
||||
const findSubtypeId = (visType: VisualizationType, subtypeId?: string) => {
|
||||
if (visType.subtypes) {
|
||||
return (
|
||||
visType.subtypes.find((subtype) => subtype === subtypeId) ||
|
||||
visType.getCompatibleSubtype?.(subtypeId) ||
|
||||
visType.subtypes[0]
|
||||
);
|
||||
}
|
||||
return visType.id;
|
||||
};
|
||||
|
||||
function getTopSuggestion(
|
||||
visualizationMap: VisualizationMap,
|
||||
datasourceMap: DatasourceMap,
|
||||
|
@ -475,15 +395,9 @@ function getTopSuggestion(
|
|||
const suggestions = unfilteredSuggestions.filter((suggestion) => {
|
||||
// don't use extended versions of current data table on switching between visualizations
|
||||
// to avoid confusing the user.
|
||||
return (
|
||||
suggestion.changeType !== 'extended' &&
|
||||
safeFnCall(
|
||||
() =>
|
||||
newVisualization.getVisualizationTypeId(suggestion.visualizationState) ===
|
||||
subVisualizationId,
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
const subtypes = getVisualizationSubtypes(newVisualization, suggestion.visualizationState);
|
||||
return suggestion.changeType !== 'extended' && subtypes.includes(subVisualizationId);
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -492,3 +406,23 @@ function getTopSuggestion(
|
|||
suggestions[0]
|
||||
);
|
||||
}
|
||||
|
||||
const getDataLoss = (
|
||||
layers: Array<[string, DatasourcePublicAPI | undefined]>,
|
||||
topSuggestion: Suggestion<unknown, unknown> | undefined
|
||||
) => {
|
||||
const containsData = layers.some(
|
||||
([_layerId, datasource]) => datasource && datasource.getTableSpec().length > 0
|
||||
);
|
||||
if (!containsData) {
|
||||
return 'nothing';
|
||||
} else if (!topSuggestion) {
|
||||
return 'everything';
|
||||
} else if (layers.length > 1 && layers.length > topSuggestion.keptLayerIds.length) {
|
||||
return 'layers';
|
||||
} else if (topSuggestion.columns !== layers[0][1]?.getTableSpec().length) {
|
||||
return 'columns';
|
||||
} else {
|
||||
return 'nothing';
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import './chart_switch.scss';
|
||||
import React, { useState, memo } from 'react';
|
||||
import { EuiPopover } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ChartSwitchTrigger } from '@kbn/visualization-ui-components';
|
||||
import { useLensSelector, selectVisualization } from '../../../../state_management';
|
||||
import { ChartSwitch, ChartSwitchProps } from './chart_switch';
|
||||
|
||||
export const ChartSwitchPopover = memo(function ChartSwitchPopover(
|
||||
props: Omit<ChartSwitchProps, 'onChartSelect'>
|
||||
) {
|
||||
const [flyoutOpen, setFlyoutOpen] = useState<boolean>(false);
|
||||
const visualization = useLensSelector(selectVisualization);
|
||||
|
||||
const { icon, label } = (visualization.activeId &&
|
||||
props.visualizationMap[visualization.activeId]?.getDescription(
|
||||
visualization.state,
|
||||
props.layerId
|
||||
)) || {
|
||||
label: i18n.translate('xpack.lens.configPanel.selectVisualization', {
|
||||
defaultMessage: 'Select a visualization',
|
||||
}),
|
||||
icon: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="lnsChartSwitch__header">
|
||||
<EuiPopover
|
||||
id="lnsChartSwitchPopover"
|
||||
ownFocus
|
||||
initialFocus=".lnsChartSwitch__popoverPanel"
|
||||
panelClassName="lnsChartSwitch__popoverPanel"
|
||||
panelPaddingSize="none"
|
||||
button={
|
||||
<ChartSwitchTrigger
|
||||
icon={icon}
|
||||
label={label}
|
||||
dataTestSubj="lnsChartSwitchPopover"
|
||||
onClick={() => setFlyoutOpen(!flyoutOpen)}
|
||||
/>
|
||||
}
|
||||
isOpen={flyoutOpen}
|
||||
closePopover={() => setFlyoutOpen(false)}
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
{flyoutOpen ? <ChartSwitch {...props} onChartSelect={() => setFlyoutOpen(false)} /> : null}
|
||||
</EuiPopover>
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiSelectable,
|
||||
EuiPopoverTitle,
|
||||
EuiSelectableOption,
|
||||
EuiSelectableProps,
|
||||
IconType,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/react';
|
||||
import { ChartOption } from './chart_option';
|
||||
|
||||
export type SelectableEntry = EuiSelectableOption<{
|
||||
value: string;
|
||||
description?: string;
|
||||
icon?: IconType;
|
||||
}>;
|
||||
|
||||
const ITEM_HEIGHT = 52;
|
||||
const MAX_ITEMS_COUNT = 6;
|
||||
const MAX_LIST_HEIGHT = ITEM_HEIGHT * MAX_ITEMS_COUNT;
|
||||
|
||||
function computeListHeight(list: SelectableEntry[]) {
|
||||
if (list.length > MAX_ITEMS_COUNT) {
|
||||
return MAX_LIST_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
export const ChartSwitchSelectable = ({
|
||||
setSearchTerm,
|
||||
searchTerm,
|
||||
...props
|
||||
}: { setSearchTerm: (val: string) => void; searchTerm: string } & EuiSelectableProps) => {
|
||||
return (
|
||||
<EuiSelectable
|
||||
singleSelection
|
||||
isPreFiltered
|
||||
data-test-subj="lnsChartSwitchList"
|
||||
className="lnsChartSwitch__options"
|
||||
height={computeListHeight(props.options as SelectableEntry[])}
|
||||
searchProps={{
|
||||
compressed: true,
|
||||
autoFocus: true,
|
||||
className: 'lnsChartSwitch__search',
|
||||
'data-test-subj': 'lnsChartSwitchSearch',
|
||||
onChange: setSearchTerm,
|
||||
placeholder: i18n.translate('xpack.lens.chartSwitch.search', {
|
||||
defaultMessage: 'Search visualizations',
|
||||
}),
|
||||
}}
|
||||
listProps={{
|
||||
showIcons: false,
|
||||
onFocusBadge: false,
|
||||
isVirtualized: false,
|
||||
}}
|
||||
renderOption={(option, searchValue) => (
|
||||
<ChartOption option={option} searchValue={searchValue} />
|
||||
)}
|
||||
noMatchesMessage={
|
||||
<div
|
||||
css={css`
|
||||
display: inline;
|
||||
`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.chartSwitch.noResults"
|
||||
defaultMessage="No results found for {term}."
|
||||
values={{
|
||||
term: <strong>{searchTerm}</strong>,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{...props}
|
||||
searchable
|
||||
>
|
||||
{(list, search) => (
|
||||
<>
|
||||
<EuiPopoverTitle paddingSize="s">{search}</EuiPopoverTitle>
|
||||
{list}
|
||||
</>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
);
|
||||
};
|
|
@ -1,12 +1,17 @@
|
|||
.lnsLayerAddButton:hover {
|
||||
text-decoration: none;
|
||||
|
||||
.lnsLayerAddButton__label {
|
||||
text-decoration: underline;
|
||||
.lnsLayerAddButton {
|
||||
&:last-child {
|
||||
align-self: unset;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
||||
.lnsLayerAddButton__techBadge,
|
||||
.lnsLayerAddButton__techBadge * {
|
||||
cursor: pointer;
|
||||
}
|
||||
.lnsLayerAddButton__label {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.lnsLayerAddButton__techBadge,
|
||||
.lnsLayerAddButton__techBadge * {
|
||||
cursor: pointer;
|
||||
}}
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ const InContextMenuActions = (props: LayerActionsProps) => {
|
|||
display="empty"
|
||||
color="text"
|
||||
size="s"
|
||||
iconType="boxesHorizontal"
|
||||
iconType="boxesVertical"
|
||||
aria-label={i18n.translate('xpack.lens.layer.actions.contextMenuAriaLabel', {
|
||||
defaultMessage: `Layer actions`,
|
||||
})}
|
||||
|
|
|
@ -43,7 +43,8 @@ describe('LayerHeader', () => {
|
|||
icon: 'empty',
|
||||
id,
|
||||
label: faker.lorem.word(),
|
||||
groupLabel: `${id}Group`,
|
||||
description: faker.lorem.sentence(),
|
||||
sortPriority: 1,
|
||||
})),
|
||||
},
|
||||
hiddenVis: { ...createMockVisualization('hiddenVis'), hideFromChartSwitch: () => true },
|
||||
|
@ -83,7 +84,9 @@ describe('LayerHeader', () => {
|
|||
return screen.queryByRole('presentation', { name: label });
|
||||
};
|
||||
const getAllChartSwitchOptions = () => {
|
||||
return screen.queryAllByRole('presentation').map((el) => el.textContent);
|
||||
return screen
|
||||
.queryAllByRole('option')
|
||||
.map((el) => (el as HTMLInputElement).getAttribute('value'));
|
||||
};
|
||||
return {
|
||||
...rtlRender,
|
||||
|
@ -105,11 +108,11 @@ describe('LayerHeader', () => {
|
|||
openChartSwitch();
|
||||
expect(queryChartOptionByLabel('hiddenVis')).not.toBeInTheDocument();
|
||||
expect(getAllChartSwitchOptions()).toEqual([
|
||||
'testVisGroup',
|
||||
'testVis2Group',
|
||||
'subvisC1Group',
|
||||
'subvisC2Group',
|
||||
'subvisC3Group',
|
||||
'testVis:testVis',
|
||||
'testVis2:testVis2',
|
||||
'testVis3:subvisC1',
|
||||
'testVis3:subvisC2',
|
||||
'testVis3:subvisC3',
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -138,6 +141,10 @@ describe('LayerHeader', () => {
|
|||
activeVisualizationId: 'testVis3',
|
||||
});
|
||||
openChartSwitch();
|
||||
expect(getAllChartSwitchOptions()).toEqual(['subvisC1Group', 'subvisC2Group', 'subvisC3Group']);
|
||||
expect(getAllChartSwitchOptions()).toEqual([
|
||||
'testVis3:subvisC1',
|
||||
'testVis3:subvisC2',
|
||||
'testVis3:subvisC3',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { StaticHeader } from '../../../shared_components';
|
||||
import {
|
||||
DatasourceMap,
|
||||
|
@ -13,7 +14,7 @@ import {
|
|||
VisualizationLayerWidgetProps,
|
||||
VisualizationMap,
|
||||
} from '../../../types';
|
||||
import { ChartSwitch } from './chart_switch';
|
||||
import { ChartSwitchPopover } from './chart_switch/chart_switch_popover';
|
||||
|
||||
export function LayerHeader({
|
||||
activeVisualizationId,
|
||||
|
@ -53,8 +54,24 @@ export function LayerHeader({
|
|||
return <StaticHeader label={description.label} icon={description.icon} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ChartSwitch
|
||||
const SubtypeSwitch = activeVisualization.getSubtypeSwitch?.(layerConfigProps);
|
||||
|
||||
return SubtypeSwitch ? (
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<ChartSwitchPopover
|
||||
datasourceMap={datasourceMap}
|
||||
visualizationMap={availableVisualizationMap}
|
||||
framePublicAPI={layerConfigProps.frame}
|
||||
layerId={layerConfigProps.layerId}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<SubtypeSwitch />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<ChartSwitchPopover
|
||||
datasourceMap={datasourceMap}
|
||||
visualizationMap={availableVisualizationMap}
|
||||
framePublicAPI={layerConfigProps.frame}
|
||||
|
|
|
@ -71,6 +71,8 @@ a tilemap in an iframe: https://github.com/elastic/kibana/issues/16457 */
|
|||
padding: $euiSize $euiSize 0;
|
||||
position: relative;
|
||||
z-index: $lnsZLevel1;
|
||||
border-left: $euiBorderThin;
|
||||
border-right: $euiBorderThin;
|
||||
|
||||
&:first-child {
|
||||
padding-left: $euiSize;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { createFormulaPublicApi } from '../async_services';
|
||||
import { LensPublicStart } from '..';
|
||||
import { visualizationTypes } from '../visualizations/xy/types';
|
||||
import { visualizationSubtypes } from '../visualizations/xy/types';
|
||||
import { mockAllSuggestions } from './suggestions_mock';
|
||||
|
||||
type Start = jest.Mocked<LensPublicStart>;
|
||||
|
@ -29,7 +29,7 @@ export const lensPluginMock = {
|
|||
navigateToPrefilledEditor: jest.fn(),
|
||||
getXyVisTypes: jest
|
||||
.fn()
|
||||
.mockReturnValue(new Promise((resolve) => resolve(visualizationTypes))),
|
||||
.mockReturnValue(new Promise((resolve) => resolve(visualizationSubtypes))),
|
||||
|
||||
stateHelperApi: jest.fn().mockResolvedValue({
|
||||
formula: createFormulaPublicApi(),
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import React from 'react';
|
||||
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
|
||||
import { toExpression } from '@kbn/interpreter';
|
||||
import faker from 'faker';
|
||||
import { Visualization, VisualizationMap } from '../types';
|
||||
|
||||
export function createMockVisualization(
|
||||
|
@ -25,7 +26,8 @@ export function createMockVisualization(
|
|||
icon: 'empty',
|
||||
id,
|
||||
label: id,
|
||||
groupLabel: `${id}Group`,
|
||||
sortPriority: 1,
|
||||
description: faker.lorem.sentence(),
|
||||
},
|
||||
],
|
||||
appendLayer: jest.fn(),
|
||||
|
|
|
@ -699,8 +699,8 @@ export class LensPlugin {
|
|||
return Boolean(core.application.capabilities.visualize?.show);
|
||||
},
|
||||
getXyVisTypes: async () => {
|
||||
const { visualizationTypes } = await import('./visualizations/xy/types');
|
||||
return visualizationTypes;
|
||||
const { visualizationSubtypes } = await import('./visualizations/xy/types');
|
||||
return visualizationSubtypes;
|
||||
},
|
||||
|
||||
stateHelperApi: async () => {
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
|
||||
|
||||
type BarOrientation = 'vertical' | 'horizontal';
|
||||
|
||||
const getBarOrientationOptions = (
|
||||
isDisabled?: boolean
|
||||
): Array<{
|
||||
id: string;
|
||||
value: BarOrientation;
|
||||
label: string;
|
||||
'data-test-subj': string;
|
||||
toolTipContent?: string;
|
||||
}> => [
|
||||
{
|
||||
id: `bar_orientation_horizontal`,
|
||||
value: 'horizontal',
|
||||
label: i18n.translate('xpack.lens.shared.barOrientation.horizontal', {
|
||||
defaultMessage: 'Horizontal',
|
||||
}),
|
||||
'data-test-subj': 'lns_barOrientation_horizontal',
|
||||
toolTipContent: isDisabled
|
||||
? i18n.translate('xpack.lens.shared.barOrientation.disabled', {
|
||||
defaultMessage:
|
||||
'A horizontal bar orientation cannot be applied when there are one or more area visualization layers.',
|
||||
})
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
id: `bar_orientation_vertical`,
|
||||
value: 'vertical',
|
||||
label: i18n.translate('xpack.lens.shared.barOrientation.vertical', {
|
||||
defaultMessage: 'Vertical',
|
||||
}),
|
||||
'data-test-subj': 'lns_barOrientation_vertical',
|
||||
},
|
||||
];
|
||||
|
||||
export interface BarOrientationProps {
|
||||
barOrientation?: BarOrientation;
|
||||
onBarOrientationChange: (newMode: BarOrientation) => void;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const BarOrientationSettings: FC<BarOrientationProps> = ({
|
||||
barOrientation = 'horizontal',
|
||||
onBarOrientationChange,
|
||||
isDisabled = false,
|
||||
}) => {
|
||||
const barOrientationOptions = getBarOrientationOptions(isDisabled);
|
||||
const label = i18n.translate('xpack.lens.shared.barOrientation', {
|
||||
defaultMessage: 'Bar orientation',
|
||||
});
|
||||
const isSelected =
|
||||
barOrientationOptions.find(({ value }) => value === barOrientation)?.id ||
|
||||
'bar_orientation_horizontal';
|
||||
|
||||
return (
|
||||
<EuiFormRow display="columnCompressed" label={label} fullWidth isDisabled={isDisabled}>
|
||||
<EuiButtonGroup
|
||||
isDisabled={isDisabled}
|
||||
isFullWidth
|
||||
legend={label}
|
||||
data-test-subj="lns_barOrientation"
|
||||
buttonSize="compressed"
|
||||
options={barOrientationOptions}
|
||||
idSelected={isSelected}
|
||||
onChange={(modeId) => {
|
||||
const newMode = barOrientationOptions.find(({ id }) => id === modeId);
|
||||
if (newMode) {
|
||||
onBarOrientationChange(newMode.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
};
|
|
@ -16,9 +16,11 @@ const defaultLabel = i18n.translate('xpack.lens.experimentalLabel', {
|
|||
export const ExperimentalBadge = ({
|
||||
label = defaultLabel,
|
||||
color,
|
||||
size = 's',
|
||||
}: {
|
||||
label?: string;
|
||||
color?: 'subdued' | undefined;
|
||||
size?: 's' | 'm';
|
||||
}) => {
|
||||
return (
|
||||
<EuiToolTip content={label}>
|
||||
|
@ -28,7 +30,7 @@ export const ExperimentalBadge = ({
|
|||
`}
|
||||
iconType="beaker"
|
||||
label={label}
|
||||
size="s"
|
||||
size={size}
|
||||
color={color}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
|
|
|
@ -16,12 +16,12 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiComboBox,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
import { Position, VerticalAlignment, HorizontalAlignment, LegendValue } from '@elastic/charts';
|
||||
import { LegendSize } from '@kbn/visualizations-plugin/public';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { XYLegendValue } from '@kbn/visualizations-plugin/common/constants';
|
||||
import { ToolbarDivider } from '../toolbar_divider';
|
||||
import { ToolbarPopover, type ToolbarPopoverProps } from '../toolbar_popover';
|
||||
import { LegendLocationSettings } from './location/legend_location_settings';
|
||||
import { ColumnsNumberSetting } from './layout/columns_number_setting';
|
||||
|
@ -230,7 +230,7 @@ export function LegendSettingsPopover<LegendStats extends LegendValue = XYLegend
|
|||
onNestedLegendChange = noop,
|
||||
legendStats = [],
|
||||
onLegendStatsChange = noop,
|
||||
groupPosition = 'right',
|
||||
groupPosition = 'none',
|
||||
maxLines,
|
||||
onMaxLinesChange = noop,
|
||||
shouldTruncate,
|
||||
|
@ -315,7 +315,7 @@ export function LegendSettingsPopover<LegendStats extends LegendValue = XYLegend
|
|||
|
||||
{showsStatisticsSetting && (
|
||||
<>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
<ToolbarDivider />
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.shared.legendStatistics', {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiHorizontalRule } from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
export const ToolbarDivider = () => (
|
||||
<EuiHorizontalRule
|
||||
margin="s"
|
||||
className={css`
|
||||
margin-inline: -8px;
|
||||
inline-size: calc(100% + 16px);
|
||||
`}
|
||||
/>
|
||||
);
|
|
@ -13,10 +13,10 @@ import { EuiIconLegend } from '@kbn/chart-icons';
|
|||
|
||||
const typeToIconMap: { [type: string]: string | IconType } = {
|
||||
legend: EuiIconLegend as IconType,
|
||||
labels: 'visText',
|
||||
values: 'number',
|
||||
list: 'list',
|
||||
visualOptions: 'brush',
|
||||
titlesAndText: 'visText',
|
||||
};
|
||||
|
||||
export type ToolbarPopoverProps = Partial<EuiPopoverProps> & {
|
||||
|
@ -82,9 +82,12 @@ export const ToolbarPopover: React.FC<PropsWithChildren<ToolbarPopoverProps>> =
|
|||
handleClose?.();
|
||||
}}
|
||||
anchorPosition="downRight"
|
||||
panelPaddingSize="s"
|
||||
{...euiPopoverProps}
|
||||
>
|
||||
<EuiPopoverTitle>{title}</EuiPopoverTitle>
|
||||
<EuiPopoverTitle data-test-subj={`${euiPopoverProps['data-test-subj']}_title`}>
|
||||
{title}
|
||||
</EuiPopoverTitle>
|
||||
{children}
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { FC } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButtonGroup, EuiFormRow, EuiIconTip } from '@elastic/eui';
|
||||
import { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
|
||||
import { ValueLabelConfig } from '../../common/types';
|
||||
|
||||
const valueLabelsOptions: Array<{
|
||||
|
@ -27,8 +27,8 @@ const valueLabelsOptions: Array<{
|
|||
{
|
||||
id: `value_labels_inside`,
|
||||
value: 'show',
|
||||
label: i18n.translate('xpack.lens.shared.valueLabelsVisibility.inside', {
|
||||
defaultMessage: 'Show',
|
||||
label: i18n.translate('xpack.lens.shared.valueLabelsVisibility.show', {
|
||||
defaultMessage: 'Show, if able',
|
||||
}),
|
||||
'data-test-subj': 'lns_valueLabels_inside',
|
||||
},
|
||||
|
@ -38,41 +38,33 @@ export interface VisualOptionsProps {
|
|||
isVisible?: boolean;
|
||||
valueLabels?: ValueLabelConfig;
|
||||
onValueLabelChange: (newMode: ValueLabelConfig) => void;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const defaultLabel = i18n.translate('xpack.lens.shared.chartValueLabelVisibilityLabel', {
|
||||
defaultMessage: 'Value labels',
|
||||
});
|
||||
|
||||
export const ValueLabelsSettings: FC<VisualOptionsProps> = ({
|
||||
isVisible = true,
|
||||
valueLabels = 'hide',
|
||||
onValueLabelChange,
|
||||
label = defaultLabel,
|
||||
}) => {
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
const label = i18n.translate('xpack.lens.shared.chartValueLabelVisibilityLabel', {
|
||||
defaultMessage: 'Labels',
|
||||
});
|
||||
|
||||
const isSelected =
|
||||
valueLabelsOptions.find(({ value }) => value === valueLabels)?.id || 'value_labels_hide';
|
||||
return (
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
display="columnCompressed"
|
||||
label={
|
||||
<span>
|
||||
{label}{' '}
|
||||
<EuiIconTip
|
||||
color="subdued"
|
||||
content={i18n.translate('xpack.lens.shared.chartValueLabelVisibilityTooltip', {
|
||||
defaultMessage: 'If there is not enough space, value labels might be hidden',
|
||||
})}
|
||||
iconProps={{
|
||||
className: 'eui-alignTop',
|
||||
}}
|
||||
position="top"
|
||||
size="s"
|
||||
type="questionInCircle"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
helpText={i18n.translate('xpack.lens.shared.chartValueLabelVisibilityHelpText', {
|
||||
defaultMessage: 'Values can only be shown if space is available.',
|
||||
})}
|
||||
label={label}
|
||||
>
|
||||
<EuiButtonGroup
|
||||
isFullWidth
|
||||
|
|
|
@ -94,7 +94,8 @@ describe('Initializing the store', () => {
|
|||
icon: 'empty',
|
||||
id: 'testVis',
|
||||
label: faker.lorem.word(),
|
||||
groupLabel: 'testVisGroup',
|
||||
sortPriority: 1,
|
||||
description: faker.lorem.sentence(),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -975,32 +975,27 @@ export interface VisualizationType {
|
|||
* Visible label used in the chart switcher and above the workspace panel in collapsed state
|
||||
*/
|
||||
label: string;
|
||||
description: string;
|
||||
/**
|
||||
* Optional label used in visualization type search if chart switcher is expanded and for tooltips
|
||||
*/
|
||||
fullLabel?: string;
|
||||
/**
|
||||
* The group the visualization belongs to
|
||||
*/
|
||||
groupLabel: string;
|
||||
/**
|
||||
* Adds to the priority of the group, accumulated from all visualizations within the same group
|
||||
* Total priority is used to sort groups. Higher number means higher priority (aka top of list).
|
||||
* Priority of the visualization for sorting in chart switch
|
||||
* Lower number means higher priority (aka top of list).
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
sortPriority?: number;
|
||||
/**
|
||||
* The sort order of the visualization in the grouping
|
||||
* Items arranged from highest on top to lowest on bottom.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
sortOrder?: number;
|
||||
sortPriority: number;
|
||||
/**
|
||||
* Indicates if visualization is in the experimental stage.
|
||||
*/
|
||||
showExperimentalBadge?: boolean;
|
||||
/**
|
||||
* Indicates if visualization is deprecated.
|
||||
*/
|
||||
isDeprecated?: boolean;
|
||||
subtypes?: string[];
|
||||
getCompatibleSubtype?: (seriesType?: string) => string | undefined;
|
||||
}
|
||||
|
||||
export interface VisualizationDisplayOptions {
|
||||
|
@ -1097,7 +1092,7 @@ export interface Visualization<T = unknown, P = T, ExtraAppendLayerArg = unknown
|
|||
* Return the ID of the current visualization. Used to highlight
|
||||
* the active subtype of the visualization.
|
||||
*/
|
||||
getVisualizationTypeId: (state: T) => string;
|
||||
getVisualizationTypeId: (state: T, layerId?: string) => string;
|
||||
|
||||
hideFromChartSwitch?: (frame: FramePublicAPI) => boolean;
|
||||
/**
|
||||
|
@ -1192,6 +1187,8 @@ export interface Visualization<T = unknown, P = T, ExtraAppendLayerArg = unknown
|
|||
props: VisualizationLayerWidgetProps<T>
|
||||
) => undefined | ReactElement<VisualizationLayerWidgetProps<T>>;
|
||||
|
||||
getSubtypeSwitch?: (props: VisualizationLayerWidgetProps<T>) => (() => JSX.Element) | null;
|
||||
|
||||
/**
|
||||
* Layer panel content rendered. This can be used to render a custom content below the title,
|
||||
* like a custom dataview switch
|
||||
|
|
|
@ -72,6 +72,7 @@ export function DataTableToolbar(props: VisualizationToolbarProps<DatatableVisua
|
|||
type="visualOptions"
|
||||
groupPosition="none"
|
||||
buttonDataTestSubj="lnsVisualOptionsButton"
|
||||
data-test-subj="lnsVisualOptionsPopover"
|
||||
>
|
||||
<RowHeightSettings
|
||||
rowHeight={state.headerRowHeight ?? DEFAULT_HEADER_ROW_HEIGHT}
|
||||
|
|
|
@ -81,15 +81,15 @@ export const getDatatableVisualization = ({
|
|||
id: 'lnsDatatable',
|
||||
icon: IconChartDatatable,
|
||||
label: visualizationLabel,
|
||||
groupLabel: i18n.translate('xpack.lens.datatable.groupLabel', {
|
||||
defaultMessage: 'Tabular',
|
||||
}),
|
||||
sortPriority: 5,
|
||||
description: i18n.translate('xpack.lens.datatable.visualizationDescription', {
|
||||
defaultMessage: 'Organize data in structured rows and columns.',
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
||||
getVisualizationTypeId() {
|
||||
return 'lnsDatatable';
|
||||
return this.id;
|
||||
},
|
||||
|
||||
getLayerIds(state) {
|
||||
|
|
|
@ -47,12 +47,12 @@ export const gaugeTitlesByType: Record<GaugeShape, string> = {
|
|||
defaultMessage: 'Vertical Bullet',
|
||||
}),
|
||||
[GaugeShapes.SEMI_CIRCLE]: i18n.translate('xpack.lens.gaugeSemiCircle.gaugeLabel', {
|
||||
defaultMessage: 'Semi-circular Gauge',
|
||||
defaultMessage: 'Minor arc',
|
||||
}),
|
||||
[GaugeShapes.ARC]: i18n.translate('xpack.lens.gaugeArc.gaugeLabel', {
|
||||
defaultMessage: 'Arc Gauge',
|
||||
defaultMessage: 'Major arc',
|
||||
}),
|
||||
[GaugeShapes.CIRCLE]: i18n.translate('xpack.lens.gaugeCircle.gaugeLabel', {
|
||||
defaultMessage: 'Circular Gauge',
|
||||
defaultMessage: 'Circle',
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -232,7 +232,7 @@ describe('shows suggestions', () => {
|
|||
shape: 'semiCircle',
|
||||
ticksPosition: 'auto',
|
||||
},
|
||||
title: 'Semi-circular Gauge',
|
||||
title: 'Minor arc',
|
||||
},
|
||||
{
|
||||
hide: false,
|
||||
|
@ -247,7 +247,7 @@ describe('shows suggestions', () => {
|
|||
shape: 'arc',
|
||||
ticksPosition: 'auto',
|
||||
},
|
||||
title: 'Arc Gauge',
|
||||
title: 'Major arc',
|
||||
},
|
||||
{
|
||||
hide: false,
|
||||
|
@ -262,7 +262,7 @@ describe('shows suggestions', () => {
|
|||
shape: 'circle',
|
||||
ticksPosition: 'auto',
|
||||
},
|
||||
title: 'Circular Gauge',
|
||||
title: 'Circle',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -39,123 +39,188 @@ describe('gauge toolbar', () => {
|
|||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
const renderAxisTicksSettingsAndOpen = (
|
||||
propsOverrides?: Partial<VisualizationToolbarProps<GaugeVisualizationState>>
|
||||
const renderGaugeToolbarAndOpen = (
|
||||
propsOverrides?: Partial<VisualizationToolbarProps<GaugeVisualizationState>>,
|
||||
toolbarName = 'Appearance'
|
||||
) => {
|
||||
const rtlRender = render(<GaugeToolbar {...defaultProps} {...propsOverrides} />);
|
||||
const openPopover = () => userEvent.click(screen.getByRole('button', { name: 'Appearance' }));
|
||||
const openPopover = () => userEvent.click(screen.getByRole('button', { name: toolbarName }));
|
||||
openPopover();
|
||||
return {
|
||||
...rtlRender,
|
||||
};
|
||||
};
|
||||
|
||||
const getTitleLabel = () => screen.getByLabelText('Title');
|
||||
const getSubtitleLabel = () => screen.getByLabelText('Subtitle');
|
||||
const getTitleSelectValue = () => screen.getByTestId('lnsToolbarGaugeLabelMajor-select');
|
||||
const getSubtitleSelectValue = () => screen.getByTestId('lnsToolbarGaugeLabelMinor-select');
|
||||
|
||||
it('should reflect state in the UI for default props', async () => {
|
||||
renderAxisTicksSettingsAndOpen();
|
||||
expect(getTitleLabel()).toHaveValue('');
|
||||
const titleSelect = getTitleSelectValue();
|
||||
expect(titleSelect).toHaveValue('auto');
|
||||
expect(getSubtitleLabel()).toHaveValue('');
|
||||
const subtitleSelect = getSubtitleSelectValue();
|
||||
expect(subtitleSelect).toHaveValue('none');
|
||||
});
|
||||
it('should reflect state in the UI for non-default props', async () => {
|
||||
renderAxisTicksSettingsAndOpen({
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
ticksPosition: 'bands' as const,
|
||||
labelMajorMode: 'custom' as const,
|
||||
labelMajor: 'new labelMajor',
|
||||
labelMinor: 'new labelMinor',
|
||||
},
|
||||
});
|
||||
|
||||
expect(getTitleLabel()).toHaveValue('new labelMajor');
|
||||
const titleSelect = getTitleSelectValue();
|
||||
expect(titleSelect).toHaveValue('custom');
|
||||
expect(getSubtitleLabel()).toHaveValue('new labelMinor');
|
||||
const subtitleSelect = getSubtitleSelectValue();
|
||||
expect(subtitleSelect).toHaveValue('custom');
|
||||
});
|
||||
|
||||
describe('labelMajor', () => {
|
||||
it('labelMajor label is disabled if labelMajor is selected to be none', () => {
|
||||
renderAxisTicksSettingsAndOpen({
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
labelMajorMode: 'none' as const,
|
||||
},
|
||||
});
|
||||
describe('gauge titles and text', () => {
|
||||
const getTitleLabel = () => screen.getByLabelText('Title');
|
||||
const getSubtitleLabel = () => screen.getByLabelText('Subtitle');
|
||||
const getTitleSelectValue = () => screen.getByTestId('lnsToolbarGaugeLabelMajor-select');
|
||||
const getSubtitleSelectValue = () => screen.getByTestId('lnsToolbarGaugeLabelMinor-select');
|
||||
it('should reflect state in the UI for default props', async () => {
|
||||
renderGaugeToolbarAndOpen(undefined, 'Titles and text');
|
||||
expect(getTitleLabel()).toHaveValue('');
|
||||
expect(getTitleLabel()).toBeDisabled();
|
||||
const titleSelect = getTitleSelectValue();
|
||||
expect(titleSelect).toHaveValue('none');
|
||||
});
|
||||
it('labelMajor mode switches to custom when user starts typing', () => {
|
||||
renderAxisTicksSettingsAndOpen({
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
labelMajorMode: 'auto' as const,
|
||||
},
|
||||
});
|
||||
const titleSelect = getTitleSelectValue();
|
||||
expect(titleSelect).toHaveValue('auto');
|
||||
expect(getTitleLabel()).toHaveValue('');
|
||||
expect(getTitleLabel()).not.toBeDisabled();
|
||||
expect(getSubtitleLabel()).toHaveValue('');
|
||||
const subtitleSelect = getSubtitleSelectValue();
|
||||
expect(subtitleSelect).toHaveValue('none');
|
||||
});
|
||||
it('should reflect state in the UI for non-default props', async () => {
|
||||
renderGaugeToolbarAndOpen(
|
||||
{
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
ticksPosition: 'bands' as const,
|
||||
labelMajorMode: 'custom' as const,
|
||||
labelMajor: 'new labelMajor',
|
||||
labelMinor: 'new labelMinor',
|
||||
},
|
||||
},
|
||||
'Titles and text'
|
||||
);
|
||||
|
||||
fireEvent.change(getTitleLabel(), { target: { value: 'labelMajor' } });
|
||||
jest.advanceTimersByTime(256);
|
||||
expect(getTitleLabel()).toHaveValue('labelMajor');
|
||||
const updatedTitleSelect = getTitleSelectValue();
|
||||
expect(updatedTitleSelect).toHaveValue('custom');
|
||||
expect(getTitleLabel()).toHaveValue('new labelMajor');
|
||||
const titleSelect = getTitleSelectValue();
|
||||
expect(titleSelect).toHaveValue('custom');
|
||||
expect(getSubtitleLabel()).toHaveValue('new labelMinor');
|
||||
const subtitleSelect = getSubtitleSelectValue();
|
||||
expect(subtitleSelect).toHaveValue('custom');
|
||||
});
|
||||
|
||||
describe('labelMajor', () => {
|
||||
it('labelMajor label is disabled if labelMajor is selected to be none', () => {
|
||||
renderGaugeToolbarAndOpen(
|
||||
{
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
labelMajorMode: 'none' as const,
|
||||
},
|
||||
},
|
||||
'Titles and text'
|
||||
);
|
||||
expect(getTitleLabel()).toHaveValue('');
|
||||
expect(getTitleLabel()).toBeDisabled();
|
||||
const titleSelect = getTitleSelectValue();
|
||||
expect(titleSelect).toHaveValue('none');
|
||||
});
|
||||
it('labelMajor mode switches to custom when user starts typing', () => {
|
||||
renderGaugeToolbarAndOpen(
|
||||
{
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
labelMajorMode: 'auto' as const,
|
||||
},
|
||||
},
|
||||
'Titles and text'
|
||||
);
|
||||
const titleSelect = getTitleSelectValue();
|
||||
expect(titleSelect).toHaveValue('auto');
|
||||
expect(getTitleLabel()).toHaveValue('');
|
||||
expect(getTitleLabel()).not.toBeDisabled();
|
||||
|
||||
fireEvent.change(getTitleLabel(), { target: { value: 'labelMajor' } });
|
||||
jest.advanceTimersByTime(256);
|
||||
expect(getTitleLabel()).toHaveValue('labelMajor');
|
||||
const updatedTitleSelect = getTitleSelectValue();
|
||||
expect(updatedTitleSelect).toHaveValue('custom');
|
||||
expect(defaultProps.setState).toHaveBeenCalledTimes(1);
|
||||
expect(defaultProps.setState).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
labelMajorMode: 'custom',
|
||||
labelMajor: 'labelMajor',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('labelMinor', () => {
|
||||
it('labelMinor label is enabled if labelMinor is string', () => {
|
||||
renderGaugeToolbarAndOpen(
|
||||
{
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
labelMinor: 'labelMinor label',
|
||||
},
|
||||
},
|
||||
'Titles and text'
|
||||
);
|
||||
expect(getSubtitleLabel()).toHaveValue('labelMinor label');
|
||||
const subtitleSelect = getSubtitleSelectValue();
|
||||
expect(subtitleSelect).toHaveValue('custom');
|
||||
expect(getSubtitleLabel()).not.toBeDisabled();
|
||||
});
|
||||
it('labelMajor mode can switch to custom', () => {
|
||||
renderGaugeToolbarAndOpen(
|
||||
{
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
labelMinor: '',
|
||||
},
|
||||
},
|
||||
'Titles and text'
|
||||
);
|
||||
const subtitleSelect = getSubtitleSelectValue();
|
||||
expect(subtitleSelect).toHaveValue('none');
|
||||
expect(getSubtitleLabel()).toHaveValue('');
|
||||
expect(getSubtitleLabel()).toBeDisabled();
|
||||
|
||||
fireEvent.change(getSubtitleLabel(), { target: { value: 'labelMinor label' } });
|
||||
jest.advanceTimersByTime(256);
|
||||
|
||||
expect(defaultProps.setState).toHaveBeenCalledTimes(1);
|
||||
expect(defaultProps.setState).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
labelMinor: 'labelMinor label',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('gauge shape', () => {
|
||||
it('should reflect state in the UI for default props', async () => {
|
||||
renderGaugeToolbarAndOpen();
|
||||
|
||||
const shapeSelect = screen.getByRole('combobox', { name: /gauge shape/i });
|
||||
expect(shapeSelect).toHaveValue('Linear');
|
||||
const verticalBulletOption = screen.getByRole('button', { name: /vertical/i });
|
||||
expect(verticalBulletOption).toHaveAttribute('aria-pressed', 'true');
|
||||
});
|
||||
it('should reflect state in the UI for non-default props', async () => {
|
||||
renderGaugeToolbarAndOpen({
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
shape: 'horizontalBullet',
|
||||
},
|
||||
});
|
||||
const shapeSelect = screen.getByRole('combobox', { name: /gauge shape/i });
|
||||
expect(shapeSelect).toHaveValue('Linear');
|
||||
const horizontalBulletOption = screen.getByRole('button', { name: /horizontal/i });
|
||||
expect(horizontalBulletOption).toHaveAttribute('aria-pressed', 'true');
|
||||
});
|
||||
it('should call setState when changing shape type', async () => {
|
||||
renderGaugeToolbarAndOpen();
|
||||
const shapeSelect = screen.getByRole('combobox', { name: /gauge shape/i });
|
||||
fireEvent.click(shapeSelect);
|
||||
fireEvent.click(screen.getByRole('option', { name: /minor arc/i }));
|
||||
expect(defaultProps.setState).toHaveBeenCalledTimes(1);
|
||||
expect(defaultProps.setState).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
labelMajorMode: 'custom',
|
||||
labelMajor: 'labelMajor',
|
||||
shape: 'semiCircle',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('labelMinor', () => {
|
||||
it('labelMinor label is enabled if labelMinor is string', () => {
|
||||
renderAxisTicksSettingsAndOpen({
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
labelMinor: 'labelMinor label',
|
||||
},
|
||||
});
|
||||
expect(getSubtitleLabel()).toHaveValue('labelMinor label');
|
||||
const subtitleSelect = getSubtitleSelectValue();
|
||||
expect(subtitleSelect).toHaveValue('custom');
|
||||
expect(getSubtitleLabel()).not.toBeDisabled();
|
||||
});
|
||||
it('labelMajor mode can switch to custom', () => {
|
||||
renderAxisTicksSettingsAndOpen({
|
||||
state: {
|
||||
...defaultProps.state,
|
||||
labelMinor: '',
|
||||
},
|
||||
});
|
||||
const subtitleSelect = getSubtitleSelectValue();
|
||||
expect(subtitleSelect).toHaveValue('none');
|
||||
expect(getSubtitleLabel()).toHaveValue('');
|
||||
expect(getSubtitleLabel()).toBeDisabled();
|
||||
|
||||
fireEvent.change(getSubtitleLabel(), { target: { value: 'labelMinor label' } });
|
||||
jest.advanceTimersByTime(256);
|
||||
|
||||
it('should call setState when changing subshape type', async () => {
|
||||
renderGaugeToolbarAndOpen();
|
||||
const horizontalBulletOption = screen.getByRole('button', { name: /horizontal/i });
|
||||
fireEvent.click(horizontalBulletOption);
|
||||
expect(defaultProps.setState).toHaveBeenCalledTimes(1);
|
||||
expect(defaultProps.setState).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
labelMinor: 'labelMinor label',
|
||||
shape: 'horizontalBullet',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
@ -6,16 +6,166 @@
|
|||
*/
|
||||
|
||||
import React, { memo, useState } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui';
|
||||
import {
|
||||
EuiButtonGroup,
|
||||
EuiComboBox,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiIcon,
|
||||
IconType,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { GaugeLabelMajorMode } from '@kbn/expression-gauge-plugin/common';
|
||||
import { GaugeLabelMajorMode, GaugeShape, GaugeShapes } from '@kbn/expression-gauge-plugin/common';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import {
|
||||
IconChartGaugeArcSimple,
|
||||
IconChartGaugeCircleSimple,
|
||||
IconChartGaugeSemiCircleSimple,
|
||||
IconChartLinearSimple,
|
||||
} from '@kbn/chart-icons';
|
||||
import type { VisualizationToolbarProps } from '../../../types';
|
||||
import { ToolbarPopover, VisLabel } from '../../../shared_components';
|
||||
import './gauge_config_panel.scss';
|
||||
import type { GaugeVisualizationState } from '../constants';
|
||||
import { gaugeTitlesByType, type GaugeVisualizationState } from '../constants';
|
||||
|
||||
const PREFIX = `lns_gaugeOrientation_`;
|
||||
export const bulletTypes = [
|
||||
{
|
||||
id: `${PREFIX}horizontalBullet`,
|
||||
label: i18n.translate('xpack.lens.gauge.bullet.orientantionHorizontal', {
|
||||
defaultMessage: 'Horizontal',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: `${PREFIX}verticalBullet`,
|
||||
label: i18n.translate('xpack.lens.gauge.bullet.orientantionVertical', {
|
||||
defaultMessage: 'Vertical',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const CHART_NAMES: Record<GaugeShape, { id: string; icon: IconType; label: string }> = {
|
||||
horizontalBullet: {
|
||||
id: GaugeShapes.HORIZONTAL_BULLET,
|
||||
icon: IconChartLinearSimple,
|
||||
label: i18n.translate('xpack.lens.gaugeLinear.gaugeLabel', {
|
||||
defaultMessage: 'Linear',
|
||||
}),
|
||||
},
|
||||
verticalBullet: {
|
||||
id: GaugeShapes.VERTICAL_BULLET,
|
||||
icon: IconChartLinearSimple,
|
||||
label: i18n.translate('xpack.lens.gaugeLinear.gaugeLabel', {
|
||||
defaultMessage: 'Linear',
|
||||
}),
|
||||
},
|
||||
semiCircle: {
|
||||
id: GaugeShapes.SEMI_CIRCLE,
|
||||
icon: IconChartGaugeSemiCircleSimple,
|
||||
label: gaugeTitlesByType.semiCircle,
|
||||
},
|
||||
arc: {
|
||||
id: GaugeShapes.ARC,
|
||||
icon: IconChartGaugeArcSimple,
|
||||
label: gaugeTitlesByType.arc,
|
||||
},
|
||||
circle: {
|
||||
id: GaugeShapes.CIRCLE,
|
||||
icon: IconChartGaugeCircleSimple,
|
||||
label: gaugeTitlesByType.circle,
|
||||
},
|
||||
};
|
||||
|
||||
const gaugeShapes = [
|
||||
CHART_NAMES.horizontalBullet,
|
||||
CHART_NAMES.semiCircle,
|
||||
CHART_NAMES.arc,
|
||||
CHART_NAMES.circle,
|
||||
];
|
||||
|
||||
export const GaugeToolbar = memo((props: VisualizationToolbarProps<GaugeVisualizationState>) => {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<AppearancePopover {...props} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TitlesAndTextPopover {...props} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
|
||||
const AppearancePopover = (props: VisualizationToolbarProps<GaugeVisualizationState>) => {
|
||||
const { state, setState } = props;
|
||||
|
||||
const selectedOption = CHART_NAMES[state.shape];
|
||||
const selectedBulletType = bulletTypes.find(({ id }) => id === `${PREFIX}${state.shape}`);
|
||||
return (
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.gauge.appearanceLabel', {
|
||||
defaultMessage: 'Appearance',
|
||||
})}
|
||||
type="visualOptions"
|
||||
buttonDataTestSubj="lnsVisualOptionsButton"
|
||||
panelClassName="lnsGaugeToolbar__popover"
|
||||
data-test-subj="lnsVisualOptionsPopover"
|
||||
>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.label.gauge.angleType', {
|
||||
defaultMessage: 'Gauge shape',
|
||||
})}
|
||||
>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
compressed
|
||||
data-test-subj="lnsToolbarGaugeAngleType"
|
||||
aria-label={i18n.translate('xpack.lens.label.gauge.angleType', {
|
||||
defaultMessage: 'Gauge shape',
|
||||
})}
|
||||
onChange={([option]) => {
|
||||
setState({ ...state, shape: option.value as GaugeShape });
|
||||
}}
|
||||
isClearable={false}
|
||||
options={gaugeShapes.map(({ id, label, icon }) => ({
|
||||
value: id,
|
||||
label,
|
||||
prepend: <EuiIcon type={icon} />,
|
||||
}))}
|
||||
selectedOptions={[selectedOption]}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
prepend={<EuiIcon type={selectedOption.icon} />}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{(state.shape === GaugeShapes.HORIZONTAL_BULLET ||
|
||||
state.shape === GaugeShapes.VERTICAL_BULLET) &&
|
||||
selectedBulletType && (
|
||||
<EuiFormRow fullWidth display="columnCompressed" label=" ">
|
||||
<EuiButtonGroup
|
||||
isFullWidth
|
||||
legend={i18n.translate('xpack.lens.gauge.bulletType', {
|
||||
defaultMessage: 'Bullet type',
|
||||
})}
|
||||
data-test-subj="lens-gauge-bullet-type"
|
||||
buttonSize="compressed"
|
||||
options={bulletTypes}
|
||||
idSelected={selectedBulletType.id}
|
||||
onChange={(optionId) => {
|
||||
const newBulletTypeWithPrefix = bulletTypes.find(({ id }) => id === optionId)!.id;
|
||||
const newBulletType = newBulletTypeWithPrefix.replace(PREFIX, '');
|
||||
setState({ ...state, shape: newBulletType as GaugeShape });
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</ToolbarPopover>
|
||||
);
|
||||
};
|
||||
|
||||
const TitlesAndTextPopover = (props: VisualizationToolbarProps<GaugeVisualizationState>) => {
|
||||
const { state, setState, frame } = props;
|
||||
const metricDimensionTitle =
|
||||
state.layerId &&
|
||||
|
@ -31,71 +181,66 @@ export const GaugeToolbar = memo((props: VisualizationToolbarProps<GaugeVisualiz
|
|||
});
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}>
|
||||
<ToolbarPopover
|
||||
handleClose={() => {
|
||||
setSubtitleMode(inputValue.labelMinor ? 'custom' : 'none');
|
||||
}}
|
||||
title={i18n.translate('xpack.lens.gauge.appearanceLabel', {
|
||||
defaultMessage: 'Appearance',
|
||||
})}
|
||||
type="visualOptions"
|
||||
buttonDataTestSubj="lnsVisualOptionsButton"
|
||||
panelClassName="lnsGaugeToolbar__popover"
|
||||
>
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.label.gauge.labelMajor.header', {
|
||||
defaultMessage: 'Title',
|
||||
})}
|
||||
fullWidth
|
||||
>
|
||||
<VisLabel
|
||||
header={i18n.translate('xpack.lens.label.gauge.labelMajor.header', {
|
||||
defaultMessage: 'Title',
|
||||
})}
|
||||
dataTestSubj="lnsToolbarGaugeLabelMajor"
|
||||
label={inputValue.labelMajor || ''}
|
||||
mode={inputValue.labelMajorMode}
|
||||
placeholder={metricDimensionTitle || ''}
|
||||
hasAutoOption={true}
|
||||
handleChange={(value) => {
|
||||
handleInputChange({
|
||||
...inputValue,
|
||||
labelMajor: value.label,
|
||||
labelMajorMode: value.mode,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.label.gauge.labelMinor.header', {
|
||||
defaultMessage: 'Subtitle',
|
||||
})}
|
||||
>
|
||||
<VisLabel
|
||||
header={i18n.translate('xpack.lens.label.gauge.labelMinor.header', {
|
||||
defaultMessage: 'Subtitle',
|
||||
})}
|
||||
dataTestSubj="lnsToolbarGaugeLabelMinor"
|
||||
label={inputValue.labelMinor || ''}
|
||||
mode={subtitleMode}
|
||||
handleChange={(value) => {
|
||||
handleInputChange({
|
||||
...inputValue,
|
||||
labelMinor: value.mode === 'none' ? '' : value.label,
|
||||
});
|
||||
setSubtitleMode(value.mode);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</ToolbarPopover>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<ToolbarPopover
|
||||
handleClose={() => {
|
||||
setSubtitleMode(inputValue.labelMinor ? 'custom' : 'none');
|
||||
}}
|
||||
title={i18n.translate('xpack.lens.gauge.appearanceLabel', {
|
||||
defaultMessage: 'Titles and text',
|
||||
})}
|
||||
type="titlesAndText"
|
||||
buttonDataTestSubj="lnsTextOptionsButton"
|
||||
panelClassName="lnsGaugeToolbar__popover"
|
||||
data-test-subj="lnsTextOptionsPopover"
|
||||
>
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.label.gauge.labelMajor.header', {
|
||||
defaultMessage: 'Title',
|
||||
})}
|
||||
fullWidth
|
||||
>
|
||||
<VisLabel
|
||||
header={i18n.translate('xpack.lens.label.gauge.labelMajor.header', {
|
||||
defaultMessage: 'Title',
|
||||
})}
|
||||
dataTestSubj="lnsToolbarGaugeLabelMajor"
|
||||
label={inputValue.labelMajor || ''}
|
||||
mode={inputValue.labelMajorMode}
|
||||
placeholder={metricDimensionTitle || ''}
|
||||
hasAutoOption={true}
|
||||
handleChange={(value) => {
|
||||
handleInputChange({
|
||||
...inputValue,
|
||||
labelMajor: value.label,
|
||||
labelMajorMode: value.mode,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.label.gauge.labelMinor.header', {
|
||||
defaultMessage: 'Subtitle',
|
||||
})}
|
||||
>
|
||||
<VisLabel
|
||||
header={i18n.translate('xpack.lens.label.gauge.labelMinor.header', {
|
||||
defaultMessage: 'Subtitle',
|
||||
})}
|
||||
dataTestSubj="lnsToolbarGaugeLabelMinor"
|
||||
label={inputValue.labelMinor || ''}
|
||||
mode={subtitleMode}
|
||||
handleChange={(value) => {
|
||||
handleInputChange({
|
||||
...inputValue,
|
||||
labelMinor: value.mode === 'none' ? '' : value.label,
|
||||
});
|
||||
setSubtitleMode(value.mode);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</ToolbarPopover>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -23,13 +23,7 @@ import {
|
|||
getMinValue,
|
||||
getValueFromAccessor,
|
||||
} from '@kbn/expression-gauge-plugin/public';
|
||||
import {
|
||||
IconChartGaugeSemiCircle,
|
||||
IconChartGaugeCircle,
|
||||
IconChartGaugeArc,
|
||||
IconChartHorizontalBullet,
|
||||
IconChartVerticalBullet,
|
||||
} from '@kbn/chart-icons';
|
||||
import { IconChartGauge } from '@kbn/chart-icons';
|
||||
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
|
||||
import type { FormBasedPersistedState } from '../../datasources/form_based/types';
|
||||
import type {
|
||||
|
@ -39,10 +33,9 @@ import type {
|
|||
Suggestion,
|
||||
UserMessage,
|
||||
Visualization,
|
||||
VisualizationType,
|
||||
} from '../../types';
|
||||
import { getSuggestions } from './suggestions';
|
||||
import { GROUP_ID, LENS_GAUGE_ID, GaugeVisualizationState, gaugeTitlesByType } from './constants';
|
||||
import { GROUP_ID, LENS_GAUGE_ID, GaugeVisualizationState } from './constants';
|
||||
import { GaugeToolbar } from './toolbar_component';
|
||||
import { applyPaletteParams } from '../../shared_components';
|
||||
import { GaugeDimensionEditor } from './dimension_editor';
|
||||
|
@ -57,10 +50,6 @@ import {
|
|||
GAUGE_MIN_NE_MAX,
|
||||
} from '../../user_messages_ids';
|
||||
|
||||
const groupLabelForGauge = i18n.translate('xpack.lens.metric.groupLabel', {
|
||||
defaultMessage: 'Goal and single value',
|
||||
});
|
||||
|
||||
interface GaugeVisualizationDeps {
|
||||
paletteService: PaletteRegistry;
|
||||
theme: ThemeServiceStart;
|
||||
|
@ -72,49 +61,6 @@ export const isNumericMetric = (op: OperationMetadata) =>
|
|||
export const isNumericDynamicMetric = (op: OperationMetadata) =>
|
||||
isNumericMetric(op) && !op.isStaticValue;
|
||||
|
||||
export const CHART_NAMES: Record<GaugeShape, VisualizationType> = {
|
||||
[GaugeShapes.HORIZONTAL_BULLET]: {
|
||||
id: GaugeShapes.HORIZONTAL_BULLET,
|
||||
icon: IconChartHorizontalBullet,
|
||||
label: gaugeTitlesByType.horizontalBullet,
|
||||
groupLabel: groupLabelForGauge,
|
||||
showExperimentalBadge: true,
|
||||
sortOrder: 10,
|
||||
},
|
||||
[GaugeShapes.VERTICAL_BULLET]: {
|
||||
id: GaugeShapes.VERTICAL_BULLET,
|
||||
icon: IconChartVerticalBullet,
|
||||
label: gaugeTitlesByType.verticalBullet,
|
||||
groupLabel: groupLabelForGauge,
|
||||
showExperimentalBadge: true,
|
||||
sortOrder: 10,
|
||||
},
|
||||
[GaugeShapes.SEMI_CIRCLE]: {
|
||||
id: GaugeShapes.SEMI_CIRCLE,
|
||||
icon: IconChartGaugeSemiCircle,
|
||||
label: gaugeTitlesByType.semiCircle,
|
||||
groupLabel: groupLabelForGauge,
|
||||
showExperimentalBadge: true,
|
||||
sortOrder: 9,
|
||||
},
|
||||
[GaugeShapes.ARC]: {
|
||||
id: GaugeShapes.ARC,
|
||||
icon: IconChartGaugeArc,
|
||||
label: gaugeTitlesByType.arc,
|
||||
groupLabel: groupLabelForGauge,
|
||||
showExperimentalBadge: true,
|
||||
sortOrder: 8,
|
||||
},
|
||||
[GaugeShapes.CIRCLE]: {
|
||||
id: GaugeShapes.CIRCLE,
|
||||
icon: IconChartGaugeCircle,
|
||||
label: gaugeTitlesByType.circle,
|
||||
groupLabel: groupLabelForGauge,
|
||||
showExperimentalBadge: true,
|
||||
sortOrder: 7,
|
||||
},
|
||||
};
|
||||
|
||||
function computePaletteParams(params: CustomPaletteParams) {
|
||||
return {
|
||||
...params,
|
||||
|
@ -216,17 +162,30 @@ export const getGaugeVisualization = ({
|
|||
paletteService,
|
||||
}: GaugeVisualizationDeps): Visualization<GaugeVisualizationState> => ({
|
||||
id: LENS_GAUGE_ID,
|
||||
|
||||
visualizationTypes: [
|
||||
CHART_NAMES[GaugeShapes.HORIZONTAL_BULLET],
|
||||
CHART_NAMES[GaugeShapes.VERTICAL_BULLET],
|
||||
CHART_NAMES[GaugeShapes.SEMI_CIRCLE],
|
||||
CHART_NAMES[GaugeShapes.ARC],
|
||||
CHART_NAMES[GaugeShapes.CIRCLE],
|
||||
],
|
||||
getVisualizationTypeId(state) {
|
||||
return state.shape;
|
||||
getVisualizationTypeId() {
|
||||
return this.id;
|
||||
},
|
||||
visualizationTypes: [
|
||||
{
|
||||
id: LENS_GAUGE_ID,
|
||||
icon: IconChartGauge,
|
||||
label: i18n.translate('xpack.lens.gauge.label', {
|
||||
defaultMessage: 'Gauge',
|
||||
}),
|
||||
showExperimentalBadge: true,
|
||||
sortPriority: 7,
|
||||
description: i18n.translate('xpack.lens.gauge.visualizationDescription', {
|
||||
defaultMessage: 'Show progress to a goal in linear or arced style.',
|
||||
}),
|
||||
subtypes: [
|
||||
GaugeShapes.HORIZONTAL_BULLET,
|
||||
GaugeShapes.VERTICAL_BULLET,
|
||||
GaugeShapes.SEMI_CIRCLE,
|
||||
GaugeShapes.ARC,
|
||||
GaugeShapes.CIRCLE,
|
||||
],
|
||||
},
|
||||
],
|
||||
getLayerIds(state) {
|
||||
return [state.layerId];
|
||||
},
|
||||
|
@ -241,8 +200,13 @@ export const getGaugeVisualization = ({
|
|||
return newState;
|
||||
},
|
||||
|
||||
getDescription(state) {
|
||||
return CHART_NAMES[state.shape];
|
||||
getDescription() {
|
||||
return {
|
||||
icon: IconChartGauge,
|
||||
label: i18n.translate('xpack.lens.gauge.label', {
|
||||
defaultMessage: 'Gauge',
|
||||
}),
|
||||
};
|
||||
},
|
||||
|
||||
switchVisualizationType: (visualizationTypeId, state) => {
|
||||
|
|
|
@ -13,10 +13,6 @@ export const LENS_HEATMAP_RENDERER = 'lens_heatmap_renderer';
|
|||
export const LENS_HEATMAP_ID = 'lnsHeatmap';
|
||||
export const DEFAULT_PALETTE_NAME = 'temperature';
|
||||
|
||||
const groupLabel = i18n.translate('xpack.lens.heatmap.groupLabel', {
|
||||
defaultMessage: 'Heatmap',
|
||||
});
|
||||
|
||||
export const CHART_SHAPES = {
|
||||
HEATMAP: 'heatmap',
|
||||
} as const;
|
||||
|
@ -28,7 +24,6 @@ export const CHART_NAMES = {
|
|||
label: i18n.translate('xpack.lens.heatmap.heatmapLabel', {
|
||||
defaultMessage: 'Heat map',
|
||||
}),
|
||||
groupLabel,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -24,6 +24,10 @@ import type { HeatmapVisualizationState } from './types';
|
|||
import { getDefaultVisualValuesForLayer } from '../../shared_components/datasource_default_values';
|
||||
import './toolbar_component.scss';
|
||||
|
||||
const PANEL_STYLE = {
|
||||
width: '460px',
|
||||
};
|
||||
|
||||
const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: string }> = [
|
||||
{
|
||||
id: `heatmap_legend_show`,
|
||||
|
@ -58,79 +62,25 @@ export const HeatmapToolbar = memo(
|
|||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}>
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.shared.visualOptionsLabel', {
|
||||
defaultMessage: 'Visual options',
|
||||
})}
|
||||
type="visualOptions"
|
||||
groupPosition="left"
|
||||
buttonDataTestSubj="lnsVisualOptionsButton"
|
||||
>
|
||||
<ValueLabelsSettings
|
||||
valueLabels={state?.gridConfig.isCellLabelVisible ? 'show' : 'hide'}
|
||||
onValueLabelChange={(newMode) => {
|
||||
setState({
|
||||
...state,
|
||||
gridConfig: { ...state.gridConfig, isCellLabelVisible: newMode === 'show' },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ToolbarPopover>
|
||||
|
||||
<LegendSettingsPopover
|
||||
groupPosition={'right'}
|
||||
legendOptions={legendOptions}
|
||||
mode={legendMode}
|
||||
onDisplayChange={(optionId) => {
|
||||
const newMode = legendOptions.find(({ id }) => id === optionId)!.value;
|
||||
if (newMode === 'show') {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, isVisible: true },
|
||||
});
|
||||
} else if (newMode === 'hide') {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, isVisible: false },
|
||||
});
|
||||
}
|
||||
}}
|
||||
position={state?.legend.position}
|
||||
onPositionChange={(id) => {
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.shared.titlesAndTextLabel', {
|
||||
defaultMessage: 'Titles and text',
|
||||
})}
|
||||
type="titlesAndText"
|
||||
buttonDataTestSubj="lnsTextOptionsButton"
|
||||
data-test-subj="lnsTextOptionsPopover"
|
||||
panelStyle={PANEL_STYLE}
|
||||
>
|
||||
<ValueLabelsSettings
|
||||
valueLabels={state?.gridConfig.isCellLabelVisible ? 'show' : 'hide'}
|
||||
onValueLabelChange={(newMode) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, position: id as Position },
|
||||
gridConfig: { ...state.gridConfig, isCellLabelVisible: newMode === 'show' },
|
||||
});
|
||||
}}
|
||||
maxLines={state?.legend.maxLines}
|
||||
onMaxLinesChange={(val) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, maxLines: val },
|
||||
});
|
||||
}}
|
||||
shouldTruncate={state?.legend.shouldTruncate ?? defaultTruncationValue}
|
||||
onTruncateLegendChange={() => {
|
||||
const current = state.legend.shouldTruncate ?? defaultTruncationValue;
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, shouldTruncate: !current },
|
||||
});
|
||||
}}
|
||||
legendSize={legendSize}
|
||||
onLegendSizeChange={(newLegendSize) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
legendSize: newLegendSize,
|
||||
},
|
||||
});
|
||||
}}
|
||||
showAutoLegendSizeOption={hadAutoLegendSize}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</ToolbarPopover>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -229,6 +179,59 @@ export const HeatmapToolbar = memo(
|
|||
</TooltipWrapper>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LegendSettingsPopover
|
||||
legendOptions={legendOptions}
|
||||
mode={legendMode}
|
||||
onDisplayChange={(optionId) => {
|
||||
const newMode = legendOptions.find(({ id }) => id === optionId)!.value;
|
||||
if (newMode === 'show') {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, isVisible: true },
|
||||
});
|
||||
} else if (newMode === 'hide') {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, isVisible: false },
|
||||
});
|
||||
}
|
||||
}}
|
||||
position={state?.legend.position}
|
||||
onPositionChange={(id) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, position: id as Position },
|
||||
});
|
||||
}}
|
||||
maxLines={state?.legend.maxLines}
|
||||
onMaxLinesChange={(val) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, maxLines: val },
|
||||
});
|
||||
}}
|
||||
shouldTruncate={state?.legend.shouldTruncate ?? defaultTruncationValue}
|
||||
onTruncateLegendChange={() => {
|
||||
const current = state.legend.shouldTruncate ?? defaultTruncationValue;
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, shouldTruncate: !current },
|
||||
});
|
||||
}}
|
||||
legendSize={legendSize}
|
||||
onLegendSizeChange={(newLegendSize) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
legendSize: newLegendSize,
|
||||
},
|
||||
});
|
||||
}}
|
||||
showAutoLegendSizeOption={hadAutoLegendSize}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -40,10 +40,6 @@ import { getSafePaletteParams } from './utils';
|
|||
import { FormBasedPersistedState } from '../..';
|
||||
import { HEATMAP_RENDER_ARRAY_VALUES, HEATMAP_X_MISSING_AXIS } from '../../user_messages_ids';
|
||||
|
||||
const groupLabelForHeatmap = i18n.translate('xpack.lens.heatmapVisualization.heatmapGroupLabel', {
|
||||
defaultMessage: 'Magnitude',
|
||||
});
|
||||
|
||||
interface HeatmapVisualizationDeps {
|
||||
paletteService: PaletteRegistry;
|
||||
theme: ThemeServiceStart;
|
||||
|
@ -108,6 +104,9 @@ export const getHeatmapVisualization = ({
|
|||
}: HeatmapVisualizationDeps): Visualization<HeatmapVisualizationState> => ({
|
||||
id: LENS_HEATMAP_ID,
|
||||
|
||||
getVisualizationTypeId(state) {
|
||||
return state.shape;
|
||||
},
|
||||
visualizationTypes: [
|
||||
{
|
||||
id: 'heatmap',
|
||||
|
@ -115,16 +114,13 @@ export const getHeatmapVisualization = ({
|
|||
label: i18n.translate('xpack.lens.heatmapVisualization.heatmapLabel', {
|
||||
defaultMessage: 'Heat map',
|
||||
}),
|
||||
groupLabel: groupLabelForHeatmap,
|
||||
showExperimentalBadge: false,
|
||||
sortPriority: 1,
|
||||
sortPriority: 8,
|
||||
description: i18n.translate('xpack.lens.heatmap.visualizationDescription', {
|
||||
defaultMessage: 'Show density or distribution across two dimensions.',
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
||||
getVisualizationTypeId(state) {
|
||||
return state.shape;
|
||||
},
|
||||
|
||||
getLayerIds(state) {
|
||||
return [state.layerId];
|
||||
},
|
||||
|
|
|
@ -6,22 +6,21 @@
|
|||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, htmlIdGenerator } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import type { VisualizationToolbarProps } from '../../../types';
|
||||
import type { LegacyMetricState } from '../../../../common/types';
|
||||
import { TitlesAndTextOptionsPopover } from './titles_and_text_options_popover';
|
||||
|
||||
import { AppearanceOptionsPopover } from './appearance_options_popover';
|
||||
|
||||
export const MetricToolbar = memo(function MetricToolbar(
|
||||
props: VisualizationToolbarProps<LegacyMetricState>
|
||||
) {
|
||||
const { state, setState, frame } = props;
|
||||
|
||||
export const MetricToolbar = memo(function MetricToolbar({
|
||||
state,
|
||||
setState,
|
||||
frame,
|
||||
}: VisualizationToolbarProps<LegacyMetricState>) {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="m" justifyContent="spaceBetween" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="none" responsive={false}>
|
||||
<AppearanceOptionsPopover
|
||||
<TitlesAndTextOptionsPopover
|
||||
state={state}
|
||||
setState={setState}
|
||||
datasourceLayers={frame.datasourceLayers}
|
||||
|
@ -31,5 +30,3 @@ export const MetricToolbar = memo(function MetricToolbar(
|
|||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
|
||||
export const idPrefix = htmlIdGenerator()();
|
||||
|
|
|
@ -14,30 +14,30 @@ import { FramePublicAPI } from '../../../types';
|
|||
import type { LegacyMetricState } from '../../../../common/types';
|
||||
import { TextFormattingOptions } from './text_formatting_options';
|
||||
|
||||
export interface VisualOptionsPopoverProps {
|
||||
export interface TitlesAndTextPopoverProps {
|
||||
state: LegacyMetricState;
|
||||
setState: (newState: LegacyMetricState) => void;
|
||||
datasourceLayers: FramePublicAPI['datasourceLayers'];
|
||||
}
|
||||
|
||||
export const AppearanceOptionsPopover: React.FC<VisualOptionsPopoverProps> = ({
|
||||
export const TitlesAndTextOptionsPopover: React.FC<TitlesAndTextPopoverProps> = ({
|
||||
state,
|
||||
setState,
|
||||
}) => {
|
||||
return (
|
||||
<TooltipWrapper
|
||||
tooltipContent={i18n.translate('xpack.lens.shared.AppearanceLabel', {
|
||||
defaultMessage: 'Appearance',
|
||||
tooltipContent={i18n.translate('xpack.lens.shared.titlesAndTextLabel', {
|
||||
defaultMessage: 'Titles and text',
|
||||
})}
|
||||
condition={true}
|
||||
>
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.shared.metric.appearanceLabel', {
|
||||
defaultMessage: 'Appearance',
|
||||
title={i18n.translate('xpack.lens.shared.metric.titlesAndTextLabel', {
|
||||
defaultMessage: 'Titles and text',
|
||||
})}
|
||||
type="visualOptions"
|
||||
type="titlesAndText"
|
||||
groupPosition="none"
|
||||
buttonDataTestSubj="lnsLegacyMetricAppearanceButton"
|
||||
buttonDataTestSubj="lnsTextOptionsButton"
|
||||
>
|
||||
<TextFormattingOptions state={state} setState={setState} />
|
||||
<TitlePositionOptions state={state} setState={setState} />
|
|
@ -157,6 +157,9 @@ export const getLegacyMetricVisualization = ({
|
|||
}): Visualization<LegacyMetricState> => ({
|
||||
id: 'lnsLegacyMetric',
|
||||
|
||||
getVisualizationTypeId() {
|
||||
return this.id;
|
||||
},
|
||||
visualizationTypes: [
|
||||
{
|
||||
id: 'lnsLegacyMetric',
|
||||
|
@ -164,8 +167,10 @@ export const getLegacyMetricVisualization = ({
|
|||
label: i18n.translate('xpack.lens.legacyMetric.label', {
|
||||
defaultMessage: 'Legacy Metric',
|
||||
}),
|
||||
groupLabel: i18n.translate('xpack.lens.legacyMetric.groupLabel', {
|
||||
defaultMessage: 'Goal and single value',
|
||||
isDeprecated: true,
|
||||
sortPriority: 100,
|
||||
description: i18n.translate('xpack.lens.legacyMetric.visualizationDescription', {
|
||||
defaultMessage: 'Present individual key metrics or KPIs.',
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
@ -175,10 +180,6 @@ export const getLegacyMetricVisualization = ({
|
|||
);
|
||||
},
|
||||
|
||||
getVisualizationTypeId() {
|
||||
return 'lnsLegacyMetric';
|
||||
},
|
||||
|
||||
clearLayer(state) {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -74,7 +74,7 @@ describe('TitlesAndTextPopover', () => {
|
|||
...fullState,
|
||||
breakdownByAccessor: undefined,
|
||||
});
|
||||
const labelOptionsButton = screen.getByTestId('lnsTitlesTextButton');
|
||||
const labelOptionsButton = screen.getByTestId('lnsTextOptionsButton');
|
||||
labelOptionsButton.click();
|
||||
|
||||
const newSubtitle = 'new subtitle hey';
|
||||
|
@ -108,7 +108,7 @@ describe('TitlesAndTextPopover', () => {
|
|||
|
||||
it('should set titlesTextAlign', async () => {
|
||||
renderToolbarOptions({ ...fullState });
|
||||
const textOptionsButton = screen.getByTestId('lnsTitlesTextButton');
|
||||
const textOptionsButton = screen.getByTestId('lnsTextOptionsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
const titlesAlignBtnGroup = new EuiButtonGroupTestHarness('lens-titles-alignment-btn');
|
||||
|
@ -126,7 +126,7 @@ describe('TitlesAndTextPopover', () => {
|
|||
|
||||
it('should set valuesTextAlign', async () => {
|
||||
renderToolbarOptions({ ...fullState });
|
||||
const textOptionsButton = screen.getByTestId('lnsTitlesTextButton');
|
||||
const textOptionsButton = screen.getByTestId('lnsTextOptionsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
const valueAlignBtnGroup = new EuiButtonGroupTestHarness('lens-values-alignment-btn');
|
||||
|
@ -144,7 +144,7 @@ describe('TitlesAndTextPopover', () => {
|
|||
|
||||
it('should set valueFontMode', async () => {
|
||||
renderToolbarOptions({ ...fullState });
|
||||
const textOptionsButton = screen.getByTestId('lnsTitlesTextButton');
|
||||
const textOptionsButton = screen.getByTestId('lnsTextOptionsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
const modeBtnGroup = new EuiButtonGroupTestHarness('lens-value-font-mode-btn');
|
||||
|
@ -159,7 +159,7 @@ describe('TitlesAndTextPopover', () => {
|
|||
|
||||
it('should set iconAlign', async () => {
|
||||
renderToolbarOptions({ ...fullState, icon: 'sortUp' });
|
||||
const textOptionsButton = screen.getByTestId('lnsTitlesTextButton');
|
||||
const textOptionsButton = screen.getByTestId('lnsTextOptionsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
const iconAlignBtnGroup = new EuiButtonGroupTestHarness('lens-icon-alignment-btn');
|
||||
|
@ -174,7 +174,7 @@ describe('TitlesAndTextPopover', () => {
|
|||
|
||||
it.each([undefined, 'empty'])('should hide iconAlign option when icon is %j', async (icon) => {
|
||||
renderToolbarOptions({ ...fullState, icon });
|
||||
const textOptionsButton = screen.getByTestId('lnsTitlesTextButton');
|
||||
const textOptionsButton = screen.getByTestId('lnsTextOptionsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
expect(screen.queryByTestId('lens-icon-alignment-btn')).not.toBeInTheDocument();
|
||||
|
|
|
@ -32,9 +32,9 @@ export const TitlesAndTextPopover: FC<TitlesAndTextPopoverProps> = ({
|
|||
title={i18n.translate('xpack.lens.metric.toolbarTitlesText.label', {
|
||||
defaultMessage: 'Titles and text',
|
||||
})}
|
||||
type="labels"
|
||||
type="titlesAndText"
|
||||
groupPosition={groupPosition}
|
||||
buttonDataTestSubj="lnsTitlesTextButton"
|
||||
buttonDataTestSubj="lnsTextOptionsButton"
|
||||
>
|
||||
{!state.breakdownByAccessor && (
|
||||
<SubtitleOption
|
||||
|
|
|
@ -58,9 +58,6 @@ const isSupportedDynamicMetric = (op: OperationMetadata) =>
|
|||
export const metricLabel = i18n.translate('xpack.lens.metric.label', {
|
||||
defaultMessage: 'Metric',
|
||||
});
|
||||
const metricGroupLabel = i18n.translate('xpack.lens.metric.groupLabel', {
|
||||
defaultMessage: 'Goal and single value',
|
||||
});
|
||||
|
||||
const getMetricLayerConfiguration = (
|
||||
props: VisualizationConfigProps<MetricVisualizationState>
|
||||
|
@ -304,21 +301,22 @@ export const getMetricVisualization = ({
|
|||
}): Visualization<MetricVisualizationState> => ({
|
||||
id: LENS_METRIC_ID,
|
||||
|
||||
getVisualizationTypeId() {
|
||||
return this.id;
|
||||
},
|
||||
visualizationTypes: [
|
||||
{
|
||||
id: LENS_METRIC_ID,
|
||||
icon: IconChartMetric,
|
||||
label: metricLabel,
|
||||
groupLabel: metricGroupLabel,
|
||||
showExperimentalBadge: true,
|
||||
sortPriority: 3,
|
||||
sortPriority: 4,
|
||||
description: i18n.translate('xpack.lens.metric.visualizationDescription', {
|
||||
defaultMessage: 'Present individual key metrics or KPIs.',
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
||||
getVisualizationTypeId() {
|
||||
return LENS_METRIC_ID;
|
||||
},
|
||||
|
||||
clearLayer(state) {
|
||||
const newState = { ...state };
|
||||
delete newState.subtitle;
|
||||
|
|
|
@ -6,15 +6,17 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { EuiIconProps } from '@elastic/eui';
|
||||
import type { EuiIconProps, IconType } from '@elastic/eui';
|
||||
|
||||
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||
import {
|
||||
IconChartDonut,
|
||||
IconChartPie,
|
||||
IconChartTreemap,
|
||||
IconChartMosaic,
|
||||
IconChartWaffle,
|
||||
IconDonutHoleSmall,
|
||||
IconDonutHoleMedium,
|
||||
IconDonutHoleLarge,
|
||||
} from '@kbn/chart-icons';
|
||||
import type { PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants';
|
||||
import { LegendValue } from '@elastic/charts';
|
||||
|
@ -23,25 +25,28 @@ import { CategoryDisplay, NumberDisplay } from '../../../common/constants';
|
|||
import type { PieChartType } from '../../../common/types';
|
||||
|
||||
interface PartitionChartMeta {
|
||||
id: string;
|
||||
icon: ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => JSX.Element;
|
||||
label: string;
|
||||
groupLabel: string;
|
||||
maxBuckets: number;
|
||||
isExperimental?: boolean;
|
||||
showExperimentalBadge?: boolean;
|
||||
sortPriority: number;
|
||||
description: string;
|
||||
toolbarPopover: {
|
||||
isDisabled?: boolean;
|
||||
categoryOptions: Array<{
|
||||
value: SharedPieLayerState['categoryDisplay'];
|
||||
inputDisplay: string;
|
||||
id: SharedPieLayerState['categoryDisplay'];
|
||||
label: string;
|
||||
}>;
|
||||
numberOptions: Array<{
|
||||
value: SharedPieLayerState['numberDisplay'];
|
||||
inputDisplay: string;
|
||||
id: SharedPieLayerState['numberDisplay'];
|
||||
label: string;
|
||||
}>;
|
||||
emptySizeRatioOptions?: Array<{
|
||||
id: string;
|
||||
value: EmptySizeRatios;
|
||||
value: EmptySizeRatios | 0;
|
||||
label: string;
|
||||
icon?: IconType;
|
||||
}>;
|
||||
};
|
||||
legend: {
|
||||
|
@ -52,70 +57,74 @@ interface PartitionChartMeta {
|
|||
};
|
||||
}
|
||||
|
||||
const groupLabel = i18n.translate('xpack.lens.pie.groupLabel', {
|
||||
defaultMessage: 'Proportion',
|
||||
});
|
||||
|
||||
const categoryOptions: PartitionChartMeta['toolbarPopover']['categoryOptions'] = [
|
||||
{
|
||||
value: CategoryDisplay.DEFAULT,
|
||||
inputDisplay: i18n.translate('xpack.lens.pieChart.showCategoriesLabel', {
|
||||
defaultMessage: 'Inside or outside',
|
||||
id: CategoryDisplay.HIDE,
|
||||
label: i18n.translate('xpack.lens.pieChart.categoriesHideLabel', {
|
||||
defaultMessage: 'Hide',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: CategoryDisplay.INSIDE,
|
||||
inputDisplay: i18n.translate('xpack.lens.pieChart.fitInsideOnlyLabel', {
|
||||
defaultMessage: 'Inside only',
|
||||
id: CategoryDisplay.INSIDE,
|
||||
label: i18n.translate('xpack.lens.pieChart.categoriesInsideOnlyLabel', {
|
||||
defaultMessage: 'Inside',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: CategoryDisplay.HIDE,
|
||||
inputDisplay: i18n.translate('xpack.lens.pieChart.categoriesInLegendLabel', {
|
||||
defaultMessage: 'Hide labels',
|
||||
id: CategoryDisplay.DEFAULT,
|
||||
label: i18n.translate('xpack.lens.pieChart.autoCategoriesLabel', {
|
||||
defaultMessage: 'Auto',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const categoryOptionsTreemap: PartitionChartMeta['toolbarPopover']['categoryOptions'] = [
|
||||
{
|
||||
value: CategoryDisplay.DEFAULT,
|
||||
inputDisplay: i18n.translate('xpack.lens.pieChart.showTreemapCategoriesLabel', {
|
||||
defaultMessage: 'Show labels',
|
||||
id: CategoryDisplay.HIDE,
|
||||
label: i18n.translate('xpack.lens.pieChart.hideTreemapCategoriesLabel', {
|
||||
defaultMessage: 'Hide',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: CategoryDisplay.HIDE,
|
||||
inputDisplay: i18n.translate('xpack.lens.pieChart.categoriesInLegendLabel', {
|
||||
defaultMessage: 'Hide labels',
|
||||
id: CategoryDisplay.DEFAULT,
|
||||
label: i18n.translate('xpack.lens.pieChart.showTreemapCategoriesLabel', {
|
||||
defaultMessage: 'Show',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const numberOptions: PartitionChartMeta['toolbarPopover']['numberOptions'] = [
|
||||
{
|
||||
value: NumberDisplay.HIDDEN,
|
||||
inputDisplay: i18n.translate('xpack.lens.pieChart.hiddenNumbersLabel', {
|
||||
defaultMessage: 'Hide from chart',
|
||||
id: NumberDisplay.HIDDEN,
|
||||
label: i18n.translate('xpack.lens.pieChart.hiddenNumbersLabel', {
|
||||
defaultMessage: 'Hide',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: NumberDisplay.PERCENT,
|
||||
inputDisplay: i18n.translate('xpack.lens.pieChart.showPercentValuesLabel', {
|
||||
defaultMessage: 'Show percent',
|
||||
id: NumberDisplay.VALUE,
|
||||
label: i18n.translate('xpack.lens.pieChart.integerLabel', {
|
||||
defaultMessage: 'Integer',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: NumberDisplay.VALUE,
|
||||
inputDisplay: i18n.translate('xpack.lens.pieChart.showFormatterValuesLabel', {
|
||||
defaultMessage: 'Show value',
|
||||
id: NumberDisplay.PERCENT,
|
||||
label: i18n.translate('xpack.lens.pieChart.percentageLabel', {
|
||||
defaultMessage: 'Percentage',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const emptySizeRatioOptions: PartitionChartMeta['toolbarPopover']['emptySizeRatioOptions'] = [
|
||||
{
|
||||
id: 'emptySizeRatioOption-none',
|
||||
value: 0,
|
||||
label: i18n.translate('xpack.lens.pieChart.emptySizeRatioOptions.none', {
|
||||
defaultMessage: 'None',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'emptySizeRatioOption-small',
|
||||
icon: IconDonutHoleSmall,
|
||||
value: EmptySizeRatios.SMALL,
|
||||
label: i18n.translate('xpack.lens.pieChart.emptySizeRatioOptions.small', {
|
||||
defaultMessage: 'Small',
|
||||
|
@ -124,6 +133,7 @@ const emptySizeRatioOptions: PartitionChartMeta['toolbarPopover']['emptySizeRati
|
|||
{
|
||||
id: 'emptySizeRatioOption-medium',
|
||||
value: EmptySizeRatios.MEDIUM,
|
||||
icon: IconDonutHoleMedium,
|
||||
label: i18n.translate('xpack.lens.pieChart.emptySizeRatioOptions.medium', {
|
||||
defaultMessage: 'Medium',
|
||||
}),
|
||||
|
@ -131,80 +141,48 @@ const emptySizeRatioOptions: PartitionChartMeta['toolbarPopover']['emptySizeRati
|
|||
{
|
||||
id: 'emptySizeRatioOption-large',
|
||||
value: EmptySizeRatios.LARGE,
|
||||
icon: IconDonutHoleLarge,
|
||||
label: i18n.translate('xpack.lens.pieChart.emptySizeRatioOptions.large', {
|
||||
defaultMessage: 'Large',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const sharedPieDonutOptions: Omit<PartitionChartMeta, 'id'> = {
|
||||
icon: IconChartPie,
|
||||
label: i18n.translate('xpack.lens.pie.pielabel', {
|
||||
defaultMessage: 'Pie',
|
||||
}),
|
||||
maxBuckets: 3,
|
||||
toolbarPopover: {
|
||||
categoryOptions,
|
||||
numberOptions,
|
||||
emptySizeRatioOptions,
|
||||
},
|
||||
legend: {
|
||||
getShowLegendDefault: (bucketColumns) => bucketColumns.length > 1,
|
||||
},
|
||||
sortPriority: 6,
|
||||
description: i18n.translate('xpack.lens.pie.visualizationDescription', {
|
||||
defaultMessage: 'Display parts of a whole in a circular format.',
|
||||
}),
|
||||
};
|
||||
|
||||
export const PartitionChartsMeta: Record<PieChartType, PartitionChartMeta> = {
|
||||
donut: {
|
||||
icon: IconChartDonut,
|
||||
label: i18n.translate('xpack.lens.pie.donutLabel', {
|
||||
defaultMessage: 'Donut',
|
||||
}),
|
||||
groupLabel,
|
||||
maxBuckets: 3,
|
||||
toolbarPopover: {
|
||||
categoryOptions,
|
||||
numberOptions,
|
||||
emptySizeRatioOptions,
|
||||
},
|
||||
legend: {
|
||||
getShowLegendDefault: (bucketColumns) => bucketColumns.length > 1,
|
||||
},
|
||||
},
|
||||
pie: {
|
||||
icon: IconChartPie,
|
||||
label: i18n.translate('xpack.lens.pie.pielabel', {
|
||||
defaultMessage: 'Pie',
|
||||
}),
|
||||
groupLabel,
|
||||
maxBuckets: 3,
|
||||
toolbarPopover: {
|
||||
categoryOptions,
|
||||
numberOptions,
|
||||
},
|
||||
legend: {
|
||||
getShowLegendDefault: (bucketColumns) => bucketColumns.length > 1,
|
||||
},
|
||||
id: 'pie',
|
||||
...sharedPieDonutOptions,
|
||||
},
|
||||
treemap: {
|
||||
icon: IconChartTreemap,
|
||||
label: i18n.translate('xpack.lens.pie.treemaplabel', {
|
||||
defaultMessage: 'Treemap',
|
||||
}),
|
||||
groupLabel,
|
||||
maxBuckets: 2,
|
||||
toolbarPopover: {
|
||||
categoryOptions: categoryOptionsTreemap,
|
||||
numberOptions,
|
||||
},
|
||||
legend: {
|
||||
getShowLegendDefault: () => false,
|
||||
},
|
||||
},
|
||||
mosaic: {
|
||||
icon: IconChartMosaic,
|
||||
label: i18n.translate('xpack.lens.pie.mosaiclabel', {
|
||||
defaultMessage: 'Mosaic',
|
||||
}),
|
||||
groupLabel,
|
||||
maxBuckets: 2,
|
||||
toolbarPopover: {
|
||||
categoryOptions: [],
|
||||
numberOptions,
|
||||
},
|
||||
legend: {
|
||||
getShowLegendDefault: () => false,
|
||||
},
|
||||
donut: {
|
||||
id: 'donut',
|
||||
...sharedPieDonutOptions,
|
||||
},
|
||||
waffle: {
|
||||
id: 'waffle',
|
||||
icon: IconChartWaffle,
|
||||
label: i18n.translate('xpack.lens.pie.wafflelabel', {
|
||||
defaultMessage: 'Waffle',
|
||||
}),
|
||||
groupLabel,
|
||||
maxBuckets: 1,
|
||||
toolbarPopover: {
|
||||
isDisabled: true,
|
||||
|
@ -217,5 +195,57 @@ export const PartitionChartsMeta: Record<PieChartType, PartitionChartMeta> = {
|
|||
hideNestedLegendSwitch: true,
|
||||
getShowLegendDefault: () => true,
|
||||
},
|
||||
sortPriority: 9,
|
||||
description: i18n.translate('xpack.lens.waffle.visualizationDescription', {
|
||||
defaultMessage: 'Represent data proportions via a grid of colored cells.',
|
||||
}),
|
||||
},
|
||||
treemap: {
|
||||
id: 'treemap',
|
||||
icon: IconChartTreemap,
|
||||
label: i18n.translate('xpack.lens.pie.treemaplabel', {
|
||||
defaultMessage: 'Treemap',
|
||||
}),
|
||||
maxBuckets: 2,
|
||||
toolbarPopover: {
|
||||
categoryOptions: categoryOptionsTreemap,
|
||||
numberOptions,
|
||||
},
|
||||
legend: {
|
||||
getShowLegendDefault: () => false,
|
||||
},
|
||||
sortPriority: 11,
|
||||
description: i18n.translate('xpack.lens.treemap.visualizationDescription', {
|
||||
defaultMessage: 'Use nested rectangles to show proportions and hierarchy.',
|
||||
}),
|
||||
},
|
||||
mosaic: {
|
||||
id: 'mosaic',
|
||||
icon: IconChartMosaic,
|
||||
label: i18n.translate('xpack.lens.pie.mosaiclabel', {
|
||||
defaultMessage: 'Mosaic',
|
||||
}),
|
||||
maxBuckets: 2,
|
||||
toolbarPopover: {
|
||||
categoryOptions: [],
|
||||
numberOptions,
|
||||
},
|
||||
legend: {
|
||||
getShowLegendDefault: () => false,
|
||||
},
|
||||
sortPriority: 13,
|
||||
description: i18n.translate('xpack.lens.mosaic.visualizationDescription', {
|
||||
defaultMessage: 'Show proportions of categorical data with rectangles.',
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
export const visualizationTypes = [
|
||||
{
|
||||
...PartitionChartsMeta.pie,
|
||||
subtypes: ['pie', 'donut'],
|
||||
},
|
||||
PartitionChartsMeta.waffle,
|
||||
PartitionChartsMeta.treemap,
|
||||
PartitionChartsMeta.mosaic,
|
||||
];
|
||||
|
|
|
@ -11,10 +11,12 @@ import { i18n } from '@kbn/i18n';
|
|||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFormRow,
|
||||
EuiSuperSelect,
|
||||
EuiRange,
|
||||
EuiHorizontalRule,
|
||||
EuiComboBox,
|
||||
EuiIcon,
|
||||
EuiFieldNumber,
|
||||
EuiButtonGroup,
|
||||
EuiFlexItem,
|
||||
EuiComboBoxOptionOption,
|
||||
} from '@elastic/eui';
|
||||
import { LegendValue, Position } from '@elastic/charts';
|
||||
import { LegendSize } from '@kbn/visualizations-plugin/public';
|
||||
|
@ -22,8 +24,8 @@ import { useDebouncedValue } from '@kbn/visualization-utils';
|
|||
import { type PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants';
|
||||
import { DEFAULT_PERCENT_DECIMALS } from './constants';
|
||||
import { PartitionChartsMeta } from './partition_charts_meta';
|
||||
import { PieVisualizationState, SharedPieLayerState } from '../../../common/types';
|
||||
import { LegendDisplay } from '../../../common/constants';
|
||||
import { EmptySizeRatios, PieVisualizationState, SharedPieLayerState } from '../../../common/types';
|
||||
import { LegendDisplay, NumberDisplay } from '../../../common/constants';
|
||||
import { VisualizationToolbarProps } from '../../types';
|
||||
import { ToolbarPopover, LegendSettingsPopover } from '../../shared_components';
|
||||
import { getDefaultVisualValuesForLayer } from '../../shared_components/datasource_default_values';
|
||||
|
@ -66,8 +68,12 @@ const legendOptions: Array<{
|
|||
},
|
||||
];
|
||||
|
||||
const emptySizeRatioLabel = i18n.translate('xpack.lens.pieChart.emptySizeRatioLabel', {
|
||||
defaultMessage: 'Inner area size',
|
||||
const PANEL_STYLE = {
|
||||
width: '500px',
|
||||
};
|
||||
|
||||
const emptySizeRatioLabel = i18n.translate('xpack.lens.pieChart.donutHole', {
|
||||
defaultMessage: 'Donut hole',
|
||||
});
|
||||
|
||||
export function PieToolbar(props: VisualizationToolbarProps<PieVisualizationState>) {
|
||||
|
@ -151,12 +157,26 @@ export function PieToolbar(props: VisualizationToolbarProps<PieVisualizationStat
|
|||
);
|
||||
|
||||
const onEmptySizeRatioChange = useCallback(
|
||||
(sizeId: unknown) => {
|
||||
const emptySizeRatio = emptySizeRatioOptions?.find(({ id }) => id === sizeId)?.value;
|
||||
onStateChange({ emptySizeRatio });
|
||||
([option]: Array<EuiComboBoxOptionOption<string>>) => {
|
||||
if (option.value === 'none') {
|
||||
setState({ ...state, shape: 'pie', layers: [{ ...layer, emptySizeRatio: undefined }] });
|
||||
} else {
|
||||
const emptySizeRatio = emptySizeRatioOptions?.find(({ id }) => id === option.value)?.value;
|
||||
setState({
|
||||
...state,
|
||||
shape: 'donut',
|
||||
layers: [{ ...layer, emptySizeRatio }],
|
||||
});
|
||||
}
|
||||
},
|
||||
[emptySizeRatioOptions, onStateChange]
|
||||
[emptySizeRatioOptions, layer, setState, state]
|
||||
);
|
||||
const selectedOption = emptySizeRatioOptions
|
||||
? emptySizeRatioOptions.find(
|
||||
({ value }) =>
|
||||
value === (state.shape === 'pie' ? 0 : layer.emptySizeRatio ?? EmptySizeRatios.SMALL)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (!layer) {
|
||||
return null;
|
||||
|
@ -168,122 +188,137 @@ export function PieToolbar(props: VisualizationToolbarProps<PieVisualizationStat
|
|||
).truncateText;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}>
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.pieChart.valuesLabel', {
|
||||
defaultMessage: 'Labels',
|
||||
})}
|
||||
isDisabled={Boolean(isToolbarPopoverDisabled)}
|
||||
type="labels"
|
||||
groupPosition="left"
|
||||
buttonDataTestSubj="lnsLabelsButton"
|
||||
>
|
||||
{categoryOptions.length ? (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.lens.pieChart.labelPositionLabel', {
|
||||
defaultMessage: 'Position',
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
{emptySizeRatioOptions?.length && selectedOption ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.pieChart.appearanceLabel', {
|
||||
defaultMessage: 'Appearance',
|
||||
})}
|
||||
fullWidth
|
||||
display="columnCompressed"
|
||||
type="visualOptions"
|
||||
buttonDataTestSubj="lnsVisualOptionsButton"
|
||||
data-test-subj="lnsVisualOptionsPopover"
|
||||
>
|
||||
<EuiSuperSelect
|
||||
compressed
|
||||
valueOfSelected={layer.categoryDisplay}
|
||||
options={categoryOptions}
|
||||
onChange={onCategoryDisplayChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
|
||||
{numberOptions.length && layer.categoryDisplay !== 'hide' ? (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.lens.pieChart.numberLabels', {
|
||||
defaultMessage: 'Values',
|
||||
})}
|
||||
fullWidth
|
||||
display="columnCompressed"
|
||||
>
|
||||
<EuiSuperSelect
|
||||
compressed
|
||||
valueOfSelected={layer.numberDisplay}
|
||||
options={numberOptions}
|
||||
onChange={onNumberDisplayChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
|
||||
{numberOptions.length + categoryOptions.length ? <EuiHorizontalRule margin="s" /> : null}
|
||||
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.lens.pieChart.percentDecimalsLabel', {
|
||||
defaultMessage: 'Maximum decimal places for percent',
|
||||
})}
|
||||
fullWidth
|
||||
display="rowCompressed"
|
||||
>
|
||||
<DecimalPlaceSlider
|
||||
value={layer.percentDecimals ?? DEFAULT_PERCENT_DECIMALS}
|
||||
setValue={onPercentDecimalsChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</ToolbarPopover>
|
||||
{emptySizeRatioOptions?.length ? (
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.pieChart.visualOptionsLabel', {
|
||||
defaultMessage: 'Visual options',
|
||||
})}
|
||||
type="visualOptions"
|
||||
groupPosition="center"
|
||||
buttonDataTestSubj="lnsVisualOptionsButton"
|
||||
>
|
||||
<EuiFormRow label={emptySizeRatioLabel} display="columnCompressed" fullWidth>
|
||||
<EuiButtonGroup
|
||||
isFullWidth
|
||||
buttonSize="compressed"
|
||||
legend={emptySizeRatioLabel}
|
||||
options={emptySizeRatioOptions}
|
||||
idSelected={
|
||||
emptySizeRatioOptions.find(({ value }) => value === layer.emptySizeRatio)?.id ??
|
||||
'emptySizeRatioOption-small'
|
||||
}
|
||||
onChange={onEmptySizeRatioChange}
|
||||
data-test-subj="lnsEmptySizeRatioButtonGroup"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</ToolbarPopover>
|
||||
<EuiFormRow label={emptySizeRatioLabel} display="columnCompressed" fullWidth>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
compressed
|
||||
data-test-subj="lnsEmptySizeRatioOption"
|
||||
aria-label={i18n.translate('xpack.lens.pieChart.donutHole', {
|
||||
defaultMessage: 'Donut hole',
|
||||
})}
|
||||
onChange={onEmptySizeRatioChange}
|
||||
isClearable={false}
|
||||
options={emptySizeRatioOptions.map(({ id, label, icon }) => ({
|
||||
value: id,
|
||||
label,
|
||||
prepend: icon ? <EuiIcon type={icon} /> : undefined,
|
||||
}))}
|
||||
selectedOptions={[{ value: selectedOption.id, label: selectedOption.label }]}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
prepend={selectedOption?.icon ? <EuiIcon type={selectedOption.icon} /> : undefined}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</ToolbarPopover>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<LegendSettingsPopover<PartitionLegendValue>
|
||||
legendOptions={legendOptions}
|
||||
mode={layer.legendDisplay}
|
||||
onDisplayChange={onLegendDisplayChange}
|
||||
legendStats={getLegendStats(layer, state.shape)}
|
||||
allowedLegendStats={
|
||||
PartitionChartsMeta[state.shape]?.legend.defaultLegendStats
|
||||
? partitionLegendValues
|
||||
: undefined
|
||||
}
|
||||
onLegendStatsChange={onLegendStatsChange}
|
||||
position={layer.legendPosition}
|
||||
onPositionChange={onLegendPositionChange}
|
||||
renderNestedLegendSwitch={
|
||||
!PartitionChartsMeta[state.shape]?.legend.hideNestedLegendSwitch &&
|
||||
layer.primaryGroups.length + (layer.secondaryGroups?.length ?? 0) > 1
|
||||
}
|
||||
nestedLegend={Boolean(layer.nestedLegend)}
|
||||
onNestedLegendChange={onNestedLegendChange}
|
||||
shouldTruncate={layer.truncateLegend ?? defaultTruncationValue}
|
||||
onTruncateLegendChange={onTruncateLegendChange}
|
||||
maxLines={layer?.legendMaxLines}
|
||||
onMaxLinesChange={onLegendMaxLinesChange}
|
||||
legendSize={legendSize}
|
||||
onLegendSizeChange={onLegendSizeChange}
|
||||
showAutoLegendSizeOption={hadAutoLegendSize}
|
||||
/>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.pieChart.titlesAndTextLabel', {
|
||||
defaultMessage: 'Titles and text',
|
||||
})}
|
||||
isDisabled={Boolean(isToolbarPopoverDisabled)}
|
||||
type="titlesAndText"
|
||||
panelStyle={PANEL_STYLE}
|
||||
>
|
||||
{categoryOptions.length ? (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.lens.pieChart.labelSliceLabels', {
|
||||
defaultMessage: 'Slice labels',
|
||||
})}
|
||||
fullWidth
|
||||
display="columnCompressed"
|
||||
>
|
||||
<EuiButtonGroup
|
||||
legend={i18n.translate('xpack.lens.pieChart.labelSliceLabels', {
|
||||
defaultMessage: 'Slice labels',
|
||||
})}
|
||||
options={categoryOptions}
|
||||
idSelected={layer.categoryDisplay}
|
||||
onChange={onCategoryDisplayChange}
|
||||
buttonSize="compressed"
|
||||
isFullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
|
||||
{numberOptions.length && layer.categoryDisplay !== 'hide' ? (
|
||||
<>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.lens.pieChart.sliceValues', {
|
||||
defaultMessage: 'Slice values',
|
||||
})}
|
||||
fullWidth
|
||||
display="columnCompressed"
|
||||
>
|
||||
<EuiButtonGroup
|
||||
legend={i18n.translate('xpack.lens.pieChart.sliceValues', {
|
||||
defaultMessage: 'Slice values',
|
||||
})}
|
||||
options={numberOptions}
|
||||
idSelected={layer.numberDisplay}
|
||||
onChange={onNumberDisplayChange}
|
||||
buttonSize="compressed"
|
||||
isFullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{layer.numberDisplay === NumberDisplay.PERCENT && (
|
||||
<EuiFormRow label=" " fullWidth display="columnCompressed">
|
||||
<DecimalPlaceInput
|
||||
value={layer.percentDecimals ?? DEFAULT_PERCENT_DECIMALS}
|
||||
setValue={onPercentDecimalsChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</ToolbarPopover>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LegendSettingsPopover<PartitionLegendValue>
|
||||
groupPosition={'none'}
|
||||
legendOptions={legendOptions}
|
||||
mode={layer.legendDisplay}
|
||||
onDisplayChange={onLegendDisplayChange}
|
||||
legendStats={getLegendStats(layer, state.shape)}
|
||||
allowedLegendStats={
|
||||
PartitionChartsMeta[state.shape]?.legend.defaultLegendStats
|
||||
? partitionLegendValues
|
||||
: undefined
|
||||
}
|
||||
onLegendStatsChange={onLegendStatsChange}
|
||||
position={layer.legendPosition}
|
||||
onPositionChange={onLegendPositionChange}
|
||||
renderNestedLegendSwitch={
|
||||
!PartitionChartsMeta[state.shape]?.legend.hideNestedLegendSwitch &&
|
||||
layer.primaryGroups.length + (layer.secondaryGroups?.length ?? 0) > 1
|
||||
}
|
||||
nestedLegend={Boolean(layer.nestedLegend)}
|
||||
onNestedLegendChange={onNestedLegendChange}
|
||||
shouldTruncate={layer.truncateLegend ?? defaultTruncationValue}
|
||||
onTruncateLegendChange={onTruncateLegendChange}
|
||||
maxLines={layer?.legendMaxLines}
|
||||
onMaxLinesChange={onLegendMaxLinesChange}
|
||||
legendSize={legendSize}
|
||||
onLegendSizeChange={onLegendSizeChange}
|
||||
showAutoLegendSizeOption={hadAutoLegendSize}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const DecimalPlaceSlider = ({
|
||||
const DecimalPlaceInput = ({
|
||||
value,
|
||||
setValue,
|
||||
}: {
|
||||
|
@ -298,12 +333,14 @@ const DecimalPlaceSlider = ({
|
|||
{ allowFalsyValue: true }
|
||||
);
|
||||
return (
|
||||
<EuiRange
|
||||
<EuiFieldNumber
|
||||
data-test-subj="indexPattern-dimension-formatDecimals"
|
||||
value={inputValue}
|
||||
min={0}
|
||||
max={10}
|
||||
showInput
|
||||
prepend={i18n.translate('xpack.lens.pieChart.decimalPlaces', {
|
||||
defaultMessage: 'Decimal places',
|
||||
})}
|
||||
compressed
|
||||
onChange={(e) => {
|
||||
handleInputChange(Number(e.currentTarget.value));
|
||||
|
|
|
@ -45,7 +45,7 @@ import {
|
|||
PieChartTypes,
|
||||
} from '../../../common/constants';
|
||||
import { suggestions } from './suggestions';
|
||||
import { PartitionChartsMeta } from './partition_charts_meta';
|
||||
import { PartitionChartsMeta, visualizationTypes } from './partition_charts_meta';
|
||||
import { PieToolbar } from './toolbar';
|
||||
import { DimensionDataExtraEditor, DimensionEditor } from './dimension_editor';
|
||||
import { LayerSettings } from './layer_settings';
|
||||
|
@ -131,17 +131,9 @@ export const getPieVisualization = ({
|
|||
kibanaTheme: ThemeServiceStart;
|
||||
}): Visualization<PieVisualizationState, PersistedPieVisualizationState> => ({
|
||||
id: 'lnsPie',
|
||||
|
||||
visualizationTypes: Object.entries(PartitionChartsMeta).map(([key, meta]) => ({
|
||||
id: key,
|
||||
icon: meta.icon,
|
||||
label: meta.label,
|
||||
groupLabel: meta.groupLabel,
|
||||
showExperimentalBadge: meta.isExperimental,
|
||||
})),
|
||||
|
||||
visualizationTypes,
|
||||
getVisualizationTypeId(state) {
|
||||
return state.shape;
|
||||
return state.shape === 'donut' ? 'pie' : state.shape;
|
||||
},
|
||||
|
||||
getLayerIds(state) {
|
||||
|
|
|
@ -61,6 +61,7 @@ export function TagcloudToolbar(props: VisualizationToolbarProps<TagcloudState>)
|
|||
})}
|
||||
type="visualOptions"
|
||||
buttonDataTestSubj="lnsVisualOptionsButton"
|
||||
data-test-subj="lnsVisualOptionsPopover"
|
||||
>
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
|
@ -99,7 +100,7 @@ export function TagcloudToolbar(props: VisualizationToolbarProps<TagcloudState>)
|
|||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
display="columnCompressedSwitch"
|
||||
label={i18n.translate('xpack.lens.label.tagcloud.showLabel', {
|
||||
defaultMessage: 'Show label',
|
||||
})}
|
||||
|
|
|
@ -41,21 +41,21 @@ export const getTagcloudVisualization = ({
|
|||
}): Visualization<TagcloudState> => ({
|
||||
id: 'lnsTagcloud',
|
||||
|
||||
getVisualizationTypeId() {
|
||||
return this.id;
|
||||
},
|
||||
visualizationTypes: [
|
||||
{
|
||||
id: 'lnsTagcloud',
|
||||
icon: IconChartTagcloud,
|
||||
label: TAGCLOUD_LABEL,
|
||||
groupLabel: i18n.translate('xpack.lens.pie.groupLabel', {
|
||||
defaultMessage: 'Proportion',
|
||||
sortPriority: 12,
|
||||
description: i18n.translate('xpack.lens.tagcloud.visualizationDescription', {
|
||||
defaultMessage: 'Visualize text data frequency or importance.',
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
||||
getVisualizationTypeId() {
|
||||
return 'lnsTagcloud';
|
||||
},
|
||||
|
||||
clearLayer(state) {
|
||||
const newState = {
|
||||
...state,
|
||||
|
|
|
@ -79,7 +79,7 @@ describe('AddLayerButton', () => {
|
|||
return within(
|
||||
screen.getByTestId('contextMenuPanelTitleButton').parentElement as HTMLElement
|
||||
)
|
||||
.getAllByRole('button')
|
||||
.getAllByTestId('lnsChartSwitch-option-label')
|
||||
.map((el) => el.textContent);
|
||||
},
|
||||
};
|
||||
|
@ -95,17 +95,7 @@ describe('AddLayerButton', () => {
|
|||
clickAddLayer();
|
||||
clickVisualizationButton();
|
||||
await waitForSeriesOptions();
|
||||
|
||||
expect(getSeriesTypeOptions()).toEqual([
|
||||
'Select visualization type',
|
||||
'Bar vertical',
|
||||
'Bar vertical stacked',
|
||||
'Bar vertical percentage',
|
||||
'Area',
|
||||
'Area stacked',
|
||||
'Area percentage',
|
||||
'Line',
|
||||
]);
|
||||
expect(getSeriesTypeOptions()).toEqual(['Bar', 'Area', 'Line']);
|
||||
});
|
||||
it('calls addLayer with a proper series type when button is clicked', async () => {
|
||||
const {
|
||||
|
|
|
@ -13,17 +13,22 @@ import {
|
|||
EuiContextMenu,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
IconType,
|
||||
transparentize,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
|
||||
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { AddLayerFunction, VisualizationLayerDescription } from '../../types';
|
||||
import { LoadAnnotationLibraryFlyout } from './load_annotation_library_flyout';
|
||||
import type { ExtraAppendLayerArg } from './visualization';
|
||||
import { SeriesType, XYState, visualizationTypes } from './types';
|
||||
import { isHorizontalChart, isHorizontalSeries } from './state_helpers';
|
||||
import { isHorizontalChart, isHorizontalSeries, isPercentageSeries } from './state_helpers';
|
||||
import { getDataLayers } from './visualization_helpers';
|
||||
import { ExperimentalBadge } from '../../shared_components';
|
||||
import { ChartOption } from '../../editor_frame_service/editor_frame/config_panel/chart_switch/chart_option';
|
||||
|
||||
interface AddLayerButtonProps {
|
||||
state: XYState;
|
||||
|
@ -36,7 +41,7 @@ interface AddLayerButtonProps {
|
|||
export enum AddLayerPanelType {
|
||||
main = 'main',
|
||||
selectAnnotationMethod = 'selectAnnotationMethod',
|
||||
selectVisualizationType = 'selectVisualizationType',
|
||||
compatibleVisualizationTypes = 'compatibleVisualizationTypes',
|
||||
}
|
||||
|
||||
export function AddLayerButton({
|
||||
|
@ -63,11 +68,11 @@ export function AddLayerButton({
|
|||
disabled,
|
||||
name: (
|
||||
<EuiFlexGroup gutterSize="s" responsive={false} alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={true}>
|
||||
<span className="lnsLayerAddButton__label">{label}</span>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ExperimentalBadge color={disabled ? 'subdued' : undefined} />
|
||||
<ExperimentalBadge color={disabled ? 'subdued' : undefined} size="m" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
|
@ -85,7 +90,7 @@ export function AddLayerButton({
|
|||
toolTipContent,
|
||||
}: (typeof supportedLayers)[0]) => {
|
||||
return {
|
||||
panel: AddLayerPanelType.selectVisualizationType,
|
||||
panel: AddLayerPanelType.compatibleVisualizationTypes,
|
||||
toolTipContent,
|
||||
disabled,
|
||||
name: <span className="lnsLayerAddButtonLabel">{label}</span>,
|
||||
|
@ -101,9 +106,11 @@ export function AddLayerButton({
|
|||
(t) => isHorizontalSeries(t.id as SeriesType) === horizontalOnly
|
||||
);
|
||||
|
||||
const currentLayerVisType =
|
||||
const currentLayerTypeIndex =
|
||||
availableVisTypes.findIndex((t) => t.id === getDataLayers(state.layers)?.[0]?.seriesType) || 0;
|
||||
|
||||
const firstLayerSubtype = getDataLayers(state.layers)?.[0]?.seriesType;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPopover
|
||||
|
@ -116,8 +123,9 @@ export function AddLayerButton({
|
|||
aria-label={i18n.translate('xpack.lens.configPanel.addLayerButton', {
|
||||
defaultMessage: 'Add layer',
|
||||
})}
|
||||
fill
|
||||
color="text"
|
||||
fill={false}
|
||||
color="primary"
|
||||
size="s"
|
||||
onClick={() => toggleLayersChoice(!showLayersChoice)}
|
||||
iconType="layers"
|
||||
>
|
||||
|
@ -145,6 +153,20 @@ export function AddLayerButton({
|
|||
if (type === LayerTypes.ANNOTATIONS) {
|
||||
return annotationPanel(props);
|
||||
} else if (type === LayerTypes.DATA) {
|
||||
if (horizontalOnly) {
|
||||
return {
|
||||
toolTipContent,
|
||||
disabled,
|
||||
name: <span className="lnsLayerAddButtonLabel">{label}</span>,
|
||||
className: 'lnsLayerAddButton',
|
||||
icon: icon && <EuiIcon size="m" type={icon} />,
|
||||
['data-test-subj']: `lnsLayerAddButton-${type}`,
|
||||
onClick: () => {
|
||||
addLayer(type);
|
||||
toggleLayersChoice(false);
|
||||
},
|
||||
};
|
||||
}
|
||||
return dataPanel(props);
|
||||
}
|
||||
return {
|
||||
|
@ -193,20 +215,40 @@ export function AddLayerButton({
|
|||
],
|
||||
},
|
||||
{
|
||||
id: AddLayerPanelType.selectVisualizationType,
|
||||
initialFocusedItemIndex: currentLayerVisType,
|
||||
title: i18n.translate('xpack.lens.layerPanel.selectVisualizationType', {
|
||||
defaultMessage: 'Select visualization type',
|
||||
id: AddLayerPanelType.compatibleVisualizationTypes,
|
||||
initialFocusedItemIndex: currentLayerTypeIndex,
|
||||
title: i18n.translate('xpack.lens.layerPanel.compatibleVisualizationTypes', {
|
||||
defaultMessage: 'Compatible visualization types',
|
||||
}),
|
||||
width: 340,
|
||||
items: availableVisTypes.map((t) => {
|
||||
const canInitializeWithSubtype =
|
||||
t.subtypes?.includes(firstLayerSubtype) && !isPercentageSeries(firstLayerSubtype);
|
||||
|
||||
return {
|
||||
renderItem: () => {
|
||||
return (
|
||||
<ChartOptionWrapper
|
||||
type={t.id}
|
||||
label={t.label}
|
||||
description={t.description}
|
||||
icon={t.icon}
|
||||
onClick={() => {
|
||||
addLayer(
|
||||
LayerTypes.DATA,
|
||||
undefined,
|
||||
undefined,
|
||||
canInitializeWithSubtype
|
||||
? firstLayerSubtype
|
||||
: (t.subtypes?.[0] as SeriesType)
|
||||
);
|
||||
toggleLayersChoice(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
}),
|
||||
items: availableVisTypes.map((t) => ({
|
||||
name: t.fullLabel || t.label,
|
||||
icon: t.icon && <EuiIcon size="m" type={t.icon} />,
|
||||
onClick: () => {
|
||||
addLayer(LayerTypes.DATA, undefined, undefined, t.id as SeriesType);
|
||||
toggleLayersChoice(false);
|
||||
},
|
||||
'data-test-subj': `lnsXY_seriesType-${t.id}`,
|
||||
})),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
@ -225,3 +267,41 @@ export function AddLayerButton({
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const ChartOptionWrapper = ({
|
||||
label,
|
||||
description,
|
||||
icon,
|
||||
onClick,
|
||||
type,
|
||||
}: {
|
||||
label: string;
|
||||
description: string;
|
||||
icon: IconType;
|
||||
onClick: () => void;
|
||||
type: string;
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
data-test-subj={`lnsXY_seriesType-${type}`}
|
||||
onClick={onClick}
|
||||
className="euiContextMenuItem lnsLayerAddButton"
|
||||
css={css`
|
||||
padding: ${euiThemeVars.euiSizeS};
|
||||
border-bottom: ${euiThemeVars.euiBorderThin};
|
||||
border-bottom-color: ${euiThemeVars.euiColorLightestShade};
|
||||
width: 100%;
|
||||
&: hover, &: focus {
|
||||
color: ${euiThemeVars.euiColorPrimary};
|
||||
background-color: ${transparentize(euiThemeVars.euiColorPrimary, 0.1)};
|
||||
span, .euiText {
|
||||
text-decoration: underline;
|
||||
color: ${euiThemeVars.euiColorPrimary}};
|
||||
}
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ChartOption option={{ icon, label, description }} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@ import { validateQuery } from '@kbn/visualization-ui-components';
|
|||
import { DataViewsState } from '../../state_management';
|
||||
import { FramePublicAPI, DatasourcePublicAPI, UserMessage } from '../../types';
|
||||
import {
|
||||
visualizationTypes,
|
||||
visualizationSubtypes,
|
||||
XYLayerConfig,
|
||||
XYDataLayerConfig,
|
||||
XYReferenceLineLayerConfig,
|
||||
|
@ -48,6 +48,26 @@ export function isHorizontalSeries(seriesType: SeriesType) {
|
|||
);
|
||||
}
|
||||
|
||||
export function flipSeriesType(seriesType: SeriesType) {
|
||||
switch (seriesType) {
|
||||
case 'bar':
|
||||
return 'bar_horizontal';
|
||||
case 'bar_stacked':
|
||||
return 'bar_horizontal_stacked';
|
||||
case 'bar_percentage_stacked':
|
||||
return 'bar_horizontal_percentage_stacked';
|
||||
case 'bar_horizontal':
|
||||
return 'bar';
|
||||
case 'bar_horizontal_stacked':
|
||||
return 'bar_stacked';
|
||||
case 'bar_horizontal_percentage_stacked':
|
||||
return 'bar_percentage_stacked';
|
||||
|
||||
default:
|
||||
return 'bar_horizontal';
|
||||
}
|
||||
}
|
||||
|
||||
export function isPercentageSeries(seriesType: SeriesType) {
|
||||
return (
|
||||
seriesType === 'bar_percentage_stacked' ||
|
||||
|
@ -56,16 +76,52 @@ export function isPercentageSeries(seriesType: SeriesType) {
|
|||
);
|
||||
}
|
||||
|
||||
export const AREA_SERIES = ['area_stacked', 'area', 'area_percentage_stacked'];
|
||||
export const NON_BAR_SERIES = [...AREA_SERIES, 'line'];
|
||||
export const BAR_SERIES = [
|
||||
'bar',
|
||||
'bar_stacked',
|
||||
'bar_percentage_stacked',
|
||||
'bar_horizontal',
|
||||
'bar_horizontal_stacked',
|
||||
'bar_horizontal_percentage_stacked',
|
||||
];
|
||||
|
||||
export const hasNonBarSeries = (layers: XYLayerConfig[]) =>
|
||||
layers.some((layer) => isDataLayer(layer) && NON_BAR_SERIES.includes(layer.seriesType));
|
||||
|
||||
export const hasBarSeries = (layers: XYLayerConfig[]) => {
|
||||
return layers.some((layer) => isDataLayer(layer) && BAR_SERIES.includes(layer.seriesType));
|
||||
};
|
||||
|
||||
export const hasAreaSeries = (layers: XYLayerConfig[]) =>
|
||||
layers.some((layer) => isDataLayer(layer) && AREA_SERIES.includes(layer.seriesType));
|
||||
|
||||
export const getBarSeriesLayers = (layers: XYLayerConfig[]): XYDataLayerConfig[] =>
|
||||
getDataLayers(layers).filter((layer) => BAR_SERIES.includes(layer.seriesType));
|
||||
|
||||
export function isStackedChart(seriesType: SeriesType) {
|
||||
return seriesType.includes('stacked');
|
||||
}
|
||||
|
||||
export const isAreaLayer = (layer: XYLayerConfig) => {
|
||||
return isDataLayer(layer) && AREA_SERIES.includes(layer.seriesType);
|
||||
};
|
||||
|
||||
export function isBarLayer(layer: XYLayerConfig) {
|
||||
return isDataLayer(layer) && BAR_SERIES.includes(layer.seriesType);
|
||||
}
|
||||
|
||||
export const getUniqueSeriesTypes = (layers: XYLayerConfig[]) => {
|
||||
return [...new Set(getDataLayers(layers).map(({ seriesType }) => seriesType))];
|
||||
};
|
||||
|
||||
export function isHorizontalChart(layers: XYLayerConfig[]) {
|
||||
return getDataLayers(layers).every((l) => isHorizontalSeries(l.seriesType));
|
||||
}
|
||||
|
||||
export function getIconForSeries(type: SeriesType): EuiIconType {
|
||||
const definition = visualizationTypes.find((t) => t.id === type);
|
||||
const definition = visualizationSubtypes.find((t) => t.id === type);
|
||||
|
||||
if (!definition) {
|
||||
throw new Error(`Unknown series type ${type}`);
|
||||
|
|
|
@ -179,23 +179,28 @@ export interface XYState {
|
|||
|
||||
export type State = XYState;
|
||||
|
||||
const groupLabelForBar = i18n.translate('xpack.lens.xyVisualization.barGroupLabel', {
|
||||
defaultMessage: 'Bar',
|
||||
});
|
||||
const barShared = {
|
||||
sortPriority: 1,
|
||||
description: i18n.translate('xpack.lens.bar.visualizationDescription', {
|
||||
defaultMessage: 'Compare categories or groups of data with bars.',
|
||||
}),
|
||||
};
|
||||
|
||||
const groupLabelForLineAndArea = i18n.translate('xpack.lens.xyVisualization.lineGroupLabel', {
|
||||
defaultMessage: 'Line and area',
|
||||
});
|
||||
const areaShared = {
|
||||
sortPriority: 3,
|
||||
description: i18n.translate('xpack.lens.area.visualizationDescription', {
|
||||
defaultMessage: 'Compare distributions of cumulative data trends.',
|
||||
}),
|
||||
};
|
||||
|
||||
export const visualizationTypes: VisualizationType[] = [
|
||||
export const visualizationSubtypes: VisualizationType[] = [
|
||||
{
|
||||
id: 'bar',
|
||||
icon: IconChartBar,
|
||||
label: i18n.translate('xpack.lens.xyVisualization.barLabel', {
|
||||
defaultMessage: 'Bar vertical',
|
||||
}),
|
||||
groupLabel: groupLabelForBar,
|
||||
sortPriority: 4,
|
||||
...barShared,
|
||||
},
|
||||
{
|
||||
id: 'bar_horizontal',
|
||||
|
@ -206,7 +211,7 @@ export const visualizationTypes: VisualizationType[] = [
|
|||
fullLabel: i18n.translate('xpack.lens.xyVisualization.barHorizontalFullLabel', {
|
||||
defaultMessage: 'Bar horizontal',
|
||||
}),
|
||||
groupLabel: groupLabelForBar,
|
||||
...barShared,
|
||||
},
|
||||
{
|
||||
id: 'bar_stacked',
|
||||
|
@ -214,7 +219,7 @@ export const visualizationTypes: VisualizationType[] = [
|
|||
label: i18n.translate('xpack.lens.xyVisualization.stackedBarLabel', {
|
||||
defaultMessage: 'Bar vertical stacked',
|
||||
}),
|
||||
groupLabel: groupLabelForBar,
|
||||
...barShared,
|
||||
},
|
||||
{
|
||||
id: 'bar_percentage_stacked',
|
||||
|
@ -222,7 +227,7 @@ export const visualizationTypes: VisualizationType[] = [
|
|||
label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarLabel', {
|
||||
defaultMessage: 'Bar vertical percentage',
|
||||
}),
|
||||
groupLabel: groupLabelForBar,
|
||||
...barShared,
|
||||
},
|
||||
{
|
||||
id: 'bar_horizontal_stacked',
|
||||
|
@ -233,7 +238,7 @@ export const visualizationTypes: VisualizationType[] = [
|
|||
fullLabel: i18n.translate('xpack.lens.xyVisualization.stackedBarHorizontalFullLabel', {
|
||||
defaultMessage: 'Bar horizontal stacked',
|
||||
}),
|
||||
groupLabel: groupLabelForBar,
|
||||
...barShared,
|
||||
},
|
||||
{
|
||||
id: 'bar_horizontal_percentage_stacked',
|
||||
|
@ -247,7 +252,7 @@ export const visualizationTypes: VisualizationType[] = [
|
|||
defaultMessage: 'Bar horizontal percentage',
|
||||
}
|
||||
),
|
||||
groupLabel: groupLabelForBar,
|
||||
...barShared,
|
||||
},
|
||||
{
|
||||
id: 'area',
|
||||
|
@ -255,7 +260,7 @@ export const visualizationTypes: VisualizationType[] = [
|
|||
label: i18n.translate('xpack.lens.xyVisualization.areaLabel', {
|
||||
defaultMessage: 'Area',
|
||||
}),
|
||||
groupLabel: groupLabelForLineAndArea,
|
||||
...areaShared,
|
||||
},
|
||||
{
|
||||
id: 'area_stacked',
|
||||
|
@ -263,7 +268,7 @@ export const visualizationTypes: VisualizationType[] = [
|
|||
label: i18n.translate('xpack.lens.xyVisualization.stackedAreaLabel', {
|
||||
defaultMessage: 'Area stacked',
|
||||
}),
|
||||
groupLabel: groupLabelForLineAndArea,
|
||||
...areaShared,
|
||||
},
|
||||
{
|
||||
id: 'area_percentage_stacked',
|
||||
|
@ -271,7 +276,7 @@ export const visualizationTypes: VisualizationType[] = [
|
|||
label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageAreaLabel', {
|
||||
defaultMessage: 'Area percentage',
|
||||
}),
|
||||
groupLabel: groupLabelForLineAndArea,
|
||||
...areaShared,
|
||||
},
|
||||
{
|
||||
id: 'line',
|
||||
|
@ -279,7 +284,73 @@ export const visualizationTypes: VisualizationType[] = [
|
|||
label: i18n.translate('xpack.lens.xyVisualization.lineLabel', {
|
||||
defaultMessage: 'Line',
|
||||
}),
|
||||
groupLabel: groupLabelForLineAndArea,
|
||||
sortPriority: 2,
|
||||
description: i18n.translate('xpack.lens.line.visualizationDescription', {
|
||||
defaultMessage: 'Reveal variations in data over time.',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export const visualizationTypes: VisualizationType[] = [
|
||||
{
|
||||
id: 'bar',
|
||||
subtypes: [
|
||||
'bar',
|
||||
'bar_stacked',
|
||||
'bar_percentage_stacked',
|
||||
'bar_horizontal',
|
||||
'bar_horizontal_stacked',
|
||||
'bar_horizontal_percentage_stacked',
|
||||
],
|
||||
icon: IconChartBar,
|
||||
label: i18n.translate('xpack.lens.xyVisualization.barLabel', {
|
||||
defaultMessage: 'Bar',
|
||||
}),
|
||||
sortPriority: 1,
|
||||
description: i18n.translate('xpack.lens.bar.visualizationDescription', {
|
||||
defaultMessage: 'Compare categories or groups of data via bars.',
|
||||
}),
|
||||
getCompatibleSubtype: (seriesType?: string) => {
|
||||
if (seriesType === 'area') {
|
||||
return 'bar';
|
||||
} else if (seriesType === 'area_stacked') {
|
||||
return 'bar_stacked';
|
||||
} else if (seriesType === 'area_percentage_stacked') {
|
||||
return 'bar_percentage_stacked';
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'area',
|
||||
icon: IconChartArea,
|
||||
label: i18n.translate('xpack.lens.xyVisualization.areaLabel', {
|
||||
defaultMessage: 'Area',
|
||||
}),
|
||||
sortPriority: 3,
|
||||
description: i18n.translate('xpack.lens.area.visualizationDescription', {
|
||||
defaultMessage: 'Compare distributions of cumulative data trends.',
|
||||
}),
|
||||
subtypes: ['area', 'area_stacked', 'area_percentage_stacked'],
|
||||
getCompatibleSubtype: (seriesType?: string) => {
|
||||
if (seriesType === 'bar') {
|
||||
return 'area';
|
||||
} else if (seriesType === 'bar_stacked') {
|
||||
return 'area_stacked';
|
||||
} else if (seriesType === 'bar_percentage_stacked') {
|
||||
return 'area_percentage_stacked';
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'line',
|
||||
icon: IconChartLine,
|
||||
label: i18n.translate('xpack.lens.xyVisualization.lineLabel', {
|
||||
defaultMessage: 'Line',
|
||||
}),
|
||||
sortPriority: 2,
|
||||
description: i18n.translate('xpack.lens.line.visualizationDescription', {
|
||||
defaultMessage: 'Reveal variations in data over time or categorically.',
|
||||
}),
|
||||
subtypes: ['line'],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -143,7 +143,7 @@ describe('xy_visualization', () => {
|
|||
const desc = xyVisualization.getDescription(mixedState());
|
||||
|
||||
expect(desc.icon).toEqual(IconChartBar);
|
||||
expect(desc.label).toEqual('Bar vertical');
|
||||
expect(desc.label).toEqual('Bar');
|
||||
});
|
||||
|
||||
it('should show mixed horizontal bar chart when multiple horizontal bar types', () => {
|
||||
|
@ -156,18 +156,15 @@ describe('xy_visualization', () => {
|
|||
|
||||
it('should show bar chart when bar only', () => {
|
||||
const desc = xyVisualization.getDescription(mixedState('bar_horizontal', 'bar_horizontal'));
|
||||
|
||||
expect(desc.label).toEqual('Bar horizontal');
|
||||
expect(desc.label).toEqual('Bar');
|
||||
});
|
||||
|
||||
it('should show the chart description if not mixed', () => {
|
||||
expect(xyVisualization.getDescription(mixedState('area')).label).toEqual('Area');
|
||||
expect(xyVisualization.getDescription(mixedState('line')).label).toEqual('Line');
|
||||
expect(xyVisualization.getDescription(mixedState('area_stacked')).label).toEqual(
|
||||
'Area stacked'
|
||||
);
|
||||
expect(xyVisualization.getDescription(mixedState('area_stacked')).label).toEqual('Area');
|
||||
expect(xyVisualization.getDescription(mixedState('bar_horizontal_stacked')).label).toEqual(
|
||||
'Bar horizontal stacked'
|
||||
'Bar'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -196,17 +193,15 @@ describe('xy_visualization', () => {
|
|||
it('should combine multiple layers into one type', () => {
|
||||
expect(
|
||||
xyVisualization.getVisualizationTypeId(mixedState('bar_horizontal', 'bar_horizontal'))
|
||||
).toEqual('bar_horizontal');
|
||||
).toEqual('bar');
|
||||
});
|
||||
|
||||
it('should return the subtype for single layers', () => {
|
||||
expect(xyVisualization.getVisualizationTypeId(mixedState('area'))).toEqual('area');
|
||||
expect(xyVisualization.getVisualizationTypeId(mixedState('line'))).toEqual('line');
|
||||
expect(xyVisualization.getVisualizationTypeId(mixedState('area_stacked'))).toEqual(
|
||||
'area_stacked'
|
||||
);
|
||||
expect(xyVisualization.getVisualizationTypeId(mixedState('area_stacked'))).toEqual('area');
|
||||
expect(xyVisualization.getVisualizationTypeId(mixedState('bar_horizontal_stacked'))).toEqual(
|
||||
'bar_horizontal_stacked'
|
||||
'bar'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Position } from '@elastic/charts';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -27,6 +27,8 @@ import { type AccessorConfig, DimensionTrigger } from '@kbn/visualization-ui-com
|
|||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { getColorsFromMapping } from '@kbn/coloring';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { EuiPopover, EuiSelectable } from '@elastic/eui';
|
||||
import { ToolbarButton } from '@kbn/shared-ux-button-toolbar';
|
||||
import { generateId } from '../../id_generator';
|
||||
import {
|
||||
isDraggedDataViewField,
|
||||
|
@ -37,7 +39,7 @@ import {
|
|||
getColorMappingDefaults,
|
||||
} from '../../utils';
|
||||
import { getSuggestions } from './xy_suggestions';
|
||||
import { XyToolbar } from './xy_config_panel';
|
||||
import { XyToolbar, updateLayer } from './xy_config_panel';
|
||||
import {
|
||||
DataDimensionEditor,
|
||||
DataDimensionEditorDataSectionExtra,
|
||||
|
@ -60,6 +62,7 @@ import {
|
|||
type XYLayerConfig,
|
||||
type XYDataLayerConfig,
|
||||
type SeriesType,
|
||||
visualizationSubtypes,
|
||||
visualizationTypes,
|
||||
} from './types';
|
||||
import {
|
||||
|
@ -161,12 +164,13 @@ export const getXyVisualization = ({
|
|||
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
||||
}): Visualization<State, XYPersistedState, ExtraAppendLayerArg> => ({
|
||||
id: XY_ID,
|
||||
visualizationTypes,
|
||||
getVisualizationTypeId(state) {
|
||||
const type = getVisualizationType(state);
|
||||
getVisualizationTypeId(state, layerId) {
|
||||
const type = getVisualizationType(state, layerId);
|
||||
return type === 'mixed' ? type : type.id;
|
||||
},
|
||||
|
||||
visualizationTypes,
|
||||
|
||||
getLayerIds(state) {
|
||||
return getLayersByType(state).map((l) => l.layerId);
|
||||
},
|
||||
|
@ -259,14 +263,30 @@ export const getXyVisualization = ({
|
|||
getDescription,
|
||||
|
||||
switchVisualizationType(seriesType: string, state: State, layerId?: string) {
|
||||
const dataLayer = state.layers.find((l) => l.layerId === layerId);
|
||||
if (dataLayer && !isDataLayer(dataLayer)) {
|
||||
throw new Error('Cannot switch series type for non-data layer');
|
||||
}
|
||||
if (!dataLayer) {
|
||||
return state;
|
||||
}
|
||||
// todo: test how they switch between percentage etc
|
||||
const currentStackingType = stackingTypes.find(({ subtypes }) =>
|
||||
subtypes.includes(dataLayer.seriesType)
|
||||
);
|
||||
const chosenTypeIndex = defaultSeriesTypesByIndex.indexOf(seriesType);
|
||||
|
||||
const compatibleSeriesType: SeriesType = (currentStackingType?.subtypes[chosenTypeIndex] ||
|
||||
seriesType) as SeriesType;
|
||||
|
||||
return {
|
||||
...state,
|
||||
preferredSeriesType: seriesType as SeriesType,
|
||||
preferredSeriesType: compatibleSeriesType,
|
||||
layers: layerId
|
||||
? state.layers.map((layer) =>
|
||||
layer.layerId === layerId ? { ...layer, seriesType: seriesType as SeriesType } : layer
|
||||
layer.layerId === layerId ? { ...layer, seriesType: compatibleSeriesType } : layer
|
||||
)
|
||||
: state.layers.map((layer) => ({ ...layer, seriesType: seriesType as SeriesType })),
|
||||
: state.layers.map((layer) => ({ ...layer, seriesType: compatibleSeriesType })),
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -672,6 +692,23 @@ export const getXyVisualization = ({
|
|||
(!isHorizontalSeries(subtype1 as SeriesType) && !isHorizontalSeries(subtype2 as SeriesType))
|
||||
);
|
||||
},
|
||||
getSubtypeSwitch({ state, setState, layerId }) {
|
||||
const index = state.layers.findIndex((l) => l.layerId === layerId);
|
||||
const layer = state.layers[index];
|
||||
|
||||
if (!layer || !isDataLayer(layer) || layer.seriesType === 'line') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return () => (
|
||||
<SubtypeSwitch
|
||||
layer={layer}
|
||||
setLayerState={(newLayer: XYDataLayerConfig) =>
|
||||
setState(updateLayer(state, newLayer, index))
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
getCustomLayerHeader(props) {
|
||||
const layer = props.state.layers.find((l) => l.layerId === props.layerId);
|
||||
|
@ -1137,9 +1174,9 @@ function getVisualizationInfo(
|
|||
|
||||
if (isDataLayer(layer)) {
|
||||
chartType = layer.seriesType;
|
||||
const layerVisType = visualizationTypes.find((visType) => visType.id === chartType);
|
||||
const layerVisType = visualizationSubtypes.find((visType) => visType.id === chartType);
|
||||
icon = layerVisType?.icon;
|
||||
label = layerVisType?.fullLabel || layerVisType?.label;
|
||||
label = layerVisType?.label;
|
||||
|
||||
if (layer.xAccessor) {
|
||||
dimensions.push({
|
||||
|
@ -1295,3 +1332,97 @@ function getNotifiableFeatures(
|
|||
},
|
||||
];
|
||||
}
|
||||
|
||||
const defaultSeriesTypesByIndex = ['bar', 'area', 'bar_horizontal'];
|
||||
|
||||
export const stackingTypes = [
|
||||
{
|
||||
type: 'stacked',
|
||||
label: i18n.translate('xpack.lens.shared.barLayerStacking.stacked', {
|
||||
defaultMessage: 'Stacked',
|
||||
}),
|
||||
subtypes: ['bar_stacked', 'area_stacked', 'bar_horizontal_stacked'],
|
||||
},
|
||||
{
|
||||
type: 'unstacked',
|
||||
label: i18n.translate('xpack.lens.shared.barLayerStacking.unstacked', {
|
||||
defaultMessage: 'Unstacked',
|
||||
}),
|
||||
subtypes: ['bar', 'area', 'bar_horizontal'],
|
||||
},
|
||||
{
|
||||
type: 'percentage',
|
||||
label: i18n.translate('xpack.lens.shared.barLayerStacking.percentage', {
|
||||
defaultMessage: 'Percentage',
|
||||
}),
|
||||
subtypes: [
|
||||
'bar_percentage_stacked',
|
||||
'area_percentage_stacked',
|
||||
'bar_horizontal_percentage_stacked',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const SubtypeSwitch = ({
|
||||
layer,
|
||||
setLayerState,
|
||||
}: {
|
||||
layer: XYDataLayerConfig;
|
||||
setLayerState: (l: XYDataLayerConfig) => void;
|
||||
}): JSX.Element | null => {
|
||||
const [flyoutOpen, setFlyoutOpen] = useState(false);
|
||||
|
||||
const stackingType = stackingTypes.find(({ subtypes }) => subtypes.includes(layer.seriesType));
|
||||
if (!stackingType) {
|
||||
return null;
|
||||
}
|
||||
const subTypeIndex = stackingType.subtypes.indexOf(layer.seriesType);
|
||||
const options = stackingTypes.map(({ label, subtypes }) => ({
|
||||
label,
|
||||
value: subtypes[subTypeIndex],
|
||||
checked: subtypes[subTypeIndex] === layer.seriesType ? ('on' as const) : undefined,
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPopover
|
||||
ownFocus
|
||||
panelPaddingSize="none"
|
||||
button={
|
||||
<ToolbarButton
|
||||
aria-label={i18n.translate('xpack.lens.xyChart.stackingOptions', {
|
||||
defaultMessage: 'Stacking',
|
||||
})}
|
||||
onClick={() => setFlyoutOpen(true)}
|
||||
fullWidth
|
||||
size="s"
|
||||
label={stackingType.label}
|
||||
/>
|
||||
}
|
||||
isOpen={flyoutOpen}
|
||||
closePopover={() => setFlyoutOpen(false)}
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiSelectable
|
||||
css={{ width: 200 }}
|
||||
singleSelection
|
||||
data-test-subj="lnsChartSwitchList"
|
||||
options={options}
|
||||
onChange={(newOptions) => {
|
||||
setFlyoutOpen(false);
|
||||
const chosenType = newOptions.find(({ checked }) => checked === 'on');
|
||||
if (!chosenType) {
|
||||
return;
|
||||
}
|
||||
setLayerState({
|
||||
...layer,
|
||||
seriesType: chosenType.value as SeriesType,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{(list) => list}
|
||||
</EuiSelectable>
|
||||
</EuiPopover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
} from '../../types';
|
||||
import {
|
||||
State,
|
||||
visualizationTypes,
|
||||
XYState,
|
||||
XYAnnotationLayerConfig,
|
||||
XYLayerConfig,
|
||||
|
@ -27,6 +26,8 @@ import {
|
|||
SeriesType,
|
||||
XYByReferenceAnnotationLayerConfig,
|
||||
XYByValueAnnotationLayerConfig,
|
||||
visualizationTypes,
|
||||
visualizationSubtypes,
|
||||
} from './types';
|
||||
import { isHorizontalChart } from './state_helpers';
|
||||
import { layerTypes } from '../..';
|
||||
|
@ -191,10 +192,27 @@ export const getLayerTypeOptions = (layer: XYLayerConfig, options: LayerTypeToLa
|
|||
return options[layerTypes.ANNOTATIONS](layer);
|
||||
};
|
||||
|
||||
export function getVisualizationSubtypeId(state: State) {
|
||||
if (!state.layers.length) {
|
||||
return (
|
||||
visualizationSubtypes.find((t) => t.id === state.preferredSeriesType) ??
|
||||
visualizationSubtypes[0]
|
||||
).id;
|
||||
}
|
||||
const dataLayers = getDataLayers(state?.layers);
|
||||
const subtype = (
|
||||
visualizationSubtypes.find((t) => t.id === dataLayers[0].seriesType) ?? visualizationSubtypes[0]
|
||||
).id;
|
||||
const seriesTypes = uniq(dataLayers.map((l) => l.seriesType));
|
||||
|
||||
return subtype && seriesTypes.length === 1 ? subtype : 'mixed';
|
||||
}
|
||||
|
||||
export function getVisualizationType(state: State, layerId?: string): VisualizationType | 'mixed' {
|
||||
if (!state.layers.length) {
|
||||
return (
|
||||
visualizationTypes.find((t) => t.id === state.preferredSeriesType) ?? visualizationTypes[0]
|
||||
visualizationTypes.find((t) => t.subtypes?.includes(state.preferredSeriesType)) ??
|
||||
visualizationTypes[0]
|
||||
);
|
||||
}
|
||||
const dataLayers = getDataLayers(state?.layers);
|
||||
|
@ -202,9 +220,14 @@ export function getVisualizationType(state: State, layerId?: string): Visualizat
|
|||
const dataLayerSeries = layerId
|
||||
? dataLayers.find((d) => d.layerId === layerId)?.seriesType
|
||||
: dataLayers[0].seriesType;
|
||||
return visualizationTypes.find((t) => t.id === dataLayerSeries) || 'mixed';
|
||||
return (
|
||||
visualizationTypes.find((t) => dataLayerSeries && t.subtypes?.includes(dataLayerSeries)) ||
|
||||
visualizationTypes[0]
|
||||
);
|
||||
}
|
||||
const visualizationType = visualizationTypes.find((t) => t.id === dataLayers[0].seriesType);
|
||||
const visualizationType =
|
||||
visualizationTypes.find((t) => t.subtypes?.includes(dataLayers[0].seriesType)) ??
|
||||
visualizationTypes[0];
|
||||
const seriesTypes = uniq(dataLayers.map((l) => l.seriesType));
|
||||
|
||||
return visualizationType && seriesTypes.length === 1 ? visualizationType : 'mixed';
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mountWithIntl as mount } from '@kbn/test-jest-helpers';
|
||||
import { EuiButtonGroupProps, EuiButtonGroup } from '@elastic/eui';
|
||||
import { DataDimensionEditor } from './dimension_editor';
|
||||
import { FramePublicAPI, DatasourcePublicAPI } from '../../../types';
|
||||
import { State, XYState, XYDataLayerConfig } from '../types';
|
||||
import { Position } from '@elastic/charts';
|
||||
import { createMockFramePublicAPI, createMockDatasource } from '../../../mocks';
|
||||
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
||||
import { EuiColorPicker } from '@elastic/eui';
|
||||
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
describe('XY Config panels', () => {
|
||||
let frame: FramePublicAPI;
|
||||
|
||||
function testState(): State {
|
||||
return {
|
||||
legend: { isVisible: true, position: Position.Right },
|
||||
valueLabels: 'hide',
|
||||
preferredSeriesType: 'bar',
|
||||
layers: [
|
||||
{
|
||||
seriesType: 'bar',
|
||||
layerType: LayerTypes.DATA,
|
||||
layerId: 'first',
|
||||
splitAccessor: 'baz',
|
||||
xAccessor: 'foo',
|
||||
accessors: ['bar'],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
frame = createMockFramePublicAPI();
|
||||
frame.datasourceLayers = {
|
||||
first: createMockDatasource('test').publicAPIMock,
|
||||
};
|
||||
});
|
||||
|
||||
describe('Dimension Editor', () => {
|
||||
test('shows the correct axis side options when in horizontal mode', () => {
|
||||
const state = testState();
|
||||
const component = mount(
|
||||
<DataDimensionEditor
|
||||
layerId={state.layers[0].layerId}
|
||||
frame={frame}
|
||||
setState={jest.fn()}
|
||||
accessor="bar"
|
||||
groupId="left"
|
||||
state={{
|
||||
...state,
|
||||
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' } as XYDataLayerConfig],
|
||||
}}
|
||||
formatFactory={jest.fn()}
|
||||
paletteService={chartPluginMock.createPaletteRegistry()}
|
||||
panelRef={React.createRef()}
|
||||
addLayer={jest.fn()}
|
||||
removeLayer={jest.fn()}
|
||||
datasource={{} as DatasourcePublicAPI}
|
||||
isDarkMode={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const options = component
|
||||
.find(EuiButtonGroup)
|
||||
.first()
|
||||
.prop('options') as EuiButtonGroupProps['options'];
|
||||
|
||||
expect(options!.map(({ label }) => label)).toEqual(['Bottom', 'Auto', 'Top']);
|
||||
});
|
||||
|
||||
test('shows the default axis side options when not in horizontal mode', () => {
|
||||
const state = testState();
|
||||
const component = mount(
|
||||
<DataDimensionEditor
|
||||
layerId={state.layers[0].layerId}
|
||||
frame={frame}
|
||||
setState={jest.fn()}
|
||||
accessor="bar"
|
||||
groupId="left"
|
||||
state={state}
|
||||
formatFactory={jest.fn()}
|
||||
paletteService={chartPluginMock.createPaletteRegistry()}
|
||||
panelRef={React.createRef()}
|
||||
addLayer={jest.fn()}
|
||||
removeLayer={jest.fn()}
|
||||
datasource={{} as DatasourcePublicAPI}
|
||||
isDarkMode={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const options = component
|
||||
.find(EuiButtonGroup)
|
||||
.first()
|
||||
.prop('options') as EuiButtonGroupProps['options'];
|
||||
|
||||
expect(options!.map(({ label }) => label)).toEqual(['Left', 'Auto', 'Right']);
|
||||
});
|
||||
|
||||
test('sets the color of a dimension to the color from palette service if not set explicitly', () => {
|
||||
const state = {
|
||||
...testState(),
|
||||
layers: [
|
||||
{
|
||||
seriesType: 'bar',
|
||||
layerType: LayerTypes.DATA,
|
||||
layerId: 'first',
|
||||
splitAccessor: undefined,
|
||||
xAccessor: 'foo',
|
||||
accessors: ['bar'],
|
||||
},
|
||||
],
|
||||
} as XYState;
|
||||
const component = mount(
|
||||
<DataDimensionEditor
|
||||
layerId={state.layers[0].layerId}
|
||||
frame={{
|
||||
...frame,
|
||||
activeData: {
|
||||
first: {
|
||||
type: 'datatable',
|
||||
columns: [],
|
||||
rows: [{ bar: 123 }],
|
||||
},
|
||||
},
|
||||
}}
|
||||
setState={jest.fn()}
|
||||
accessor="bar"
|
||||
groupId="left"
|
||||
state={state}
|
||||
formatFactory={jest.fn()}
|
||||
paletteService={chartPluginMock.createPaletteRegistry()}
|
||||
panelRef={React.createRef()}
|
||||
addLayer={jest.fn()}
|
||||
removeLayer={jest.fn()}
|
||||
datasource={{} as DatasourcePublicAPI}
|
||||
isDarkMode={false}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find(EuiColorPicker).prop('color')).toEqual('black');
|
||||
});
|
||||
|
||||
test('uses the overwrite color if set', () => {
|
||||
const state = {
|
||||
...testState(),
|
||||
layers: [
|
||||
{
|
||||
seriesType: 'bar',
|
||||
layerType: LayerTypes.DATA,
|
||||
layerId: 'first',
|
||||
splitAccessor: undefined,
|
||||
xAccessor: 'foo',
|
||||
accessors: ['bar'],
|
||||
yConfig: [{ forAccessor: 'bar', color: 'red' }],
|
||||
},
|
||||
],
|
||||
} as XYState;
|
||||
|
||||
const component = mount(
|
||||
<DataDimensionEditor
|
||||
layerId={state.layers[0].layerId}
|
||||
frame={{
|
||||
...frame,
|
||||
activeData: {
|
||||
first: {
|
||||
type: 'datatable',
|
||||
columns: [],
|
||||
rows: [{ bar: 123 }],
|
||||
},
|
||||
},
|
||||
}}
|
||||
setState={jest.fn()}
|
||||
accessor="bar"
|
||||
groupId="left"
|
||||
state={state}
|
||||
formatFactory={jest.fn()}
|
||||
paletteService={chartPluginMock.createPaletteRegistry()}
|
||||
panelRef={React.createRef()}
|
||||
addLayer={jest.fn()}
|
||||
removeLayer={jest.fn()}
|
||||
datasource={{} as DatasourcePublicAPI}
|
||||
isDarkMode={false}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find(EuiColorPicker).prop('color')).toEqual('red');
|
||||
});
|
||||
test('does not apply incorrect color', () => {
|
||||
jest.useFakeTimers();
|
||||
const setState = jest.fn();
|
||||
const state = {
|
||||
...testState(),
|
||||
layers: [
|
||||
{
|
||||
seriesType: 'bar',
|
||||
layerType: LayerTypes.DATA,
|
||||
layerId: 'first',
|
||||
splitAccessor: undefined,
|
||||
xAccessor: 'foo',
|
||||
accessors: ['bar'],
|
||||
yConfig: [{ forAccessor: 'bar', color: 'red' }],
|
||||
},
|
||||
],
|
||||
} as XYState;
|
||||
|
||||
const component = mount(
|
||||
<DataDimensionEditor
|
||||
layerId={state.layers[0].layerId}
|
||||
frame={{
|
||||
...frame,
|
||||
activeData: {
|
||||
first: {
|
||||
type: 'datatable',
|
||||
columns: [],
|
||||
rows: [{ bar: 123 }],
|
||||
},
|
||||
},
|
||||
}}
|
||||
setState={setState}
|
||||
accessor="bar"
|
||||
groupId="left"
|
||||
state={state}
|
||||
formatFactory={jest.fn()}
|
||||
paletteService={chartPluginMock.createPaletteRegistry()}
|
||||
panelRef={React.createRef()}
|
||||
addLayer={jest.fn()}
|
||||
removeLayer={jest.fn()}
|
||||
datasource={{} as DatasourcePublicAPI}
|
||||
isDarkMode={false}
|
||||
/>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
component
|
||||
.find('input[data-test-subj="euiColorPickerAnchor indexPattern-dimension-colorPicker"]')
|
||||
.simulate('change', {
|
||||
target: { value: 'INCORRECT_COLOR' },
|
||||
});
|
||||
});
|
||||
component.update();
|
||||
jest.advanceTimersByTime(256);
|
||||
expect(component.find(EuiColorPicker).prop('color')).toEqual('INCORRECT_COLOR');
|
||||
expect(setState).not.toHaveBeenCalled();
|
||||
|
||||
act(() => {
|
||||
component
|
||||
.find('input[data-test-subj="euiColorPickerAnchor indexPattern-dimension-colorPicker"]')
|
||||
.simulate('change', {
|
||||
target: { value: '666666' },
|
||||
});
|
||||
});
|
||||
component.update();
|
||||
jest.advanceTimersByTime(256);
|
||||
expect(component.find(EuiColorPicker).prop('color')).toEqual('666666');
|
||||
expect(setState).toHaveBeenCalled();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -15,11 +15,12 @@ import { LegendSize, XYLegendValue } from '@kbn/visualizations-plugin/common/con
|
|||
import type { LegendSettingsPopoverProps } from '../../../shared_components/legend/legend_settings_popover';
|
||||
import type { VisualizationToolbarProps, FramePublicAPI } from '../../../types';
|
||||
import { State, XYState, AxesSettingsConfig } from '../types';
|
||||
import { isHorizontalChart } from '../state_helpers';
|
||||
import { hasBarSeries, isHorizontalChart } from '../state_helpers';
|
||||
import { hasNumericHistogramDimension, LegendSettingsPopover } from '../../../shared_components';
|
||||
import { AxisSettingsPopover } from './axis_settings_popover';
|
||||
import { getAxesConfiguration, getXDomain, AxisGroupConfiguration } from '../axes_configuration';
|
||||
import { VisualOptionsPopover } from './visual_options_popover';
|
||||
import { TextPopover } from './titles_and_text_popover';
|
||||
import { getScaleType } from '../to_expression';
|
||||
import { getDefaultVisualValuesForLayer } from '../../../shared_components/datasource_default_values';
|
||||
import { getDataLayers } from '../visualization_helpers';
|
||||
|
@ -502,159 +503,25 @@ export const XyToolbar = memo(function XyToolbar(
|
|||
).truncateText;
|
||||
|
||||
const legendSize = state.legend.legendSize;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}>
|
||||
<VisualOptionsPopover
|
||||
<VisualOptionsPopover
|
||||
state={state}
|
||||
setState={setState}
|
||||
datasourceLayers={frame.datasourceLayers}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{hasBarSeries(state.layers) && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<TextPopover
|
||||
state={state}
|
||||
setState={setState}
|
||||
datasourceLayers={frame.datasourceLayers}
|
||||
/>
|
||||
|
||||
<LegendSettingsPopover
|
||||
legendOptions={legendOptions}
|
||||
mode={legendMode}
|
||||
location={state?.legend.isInside ? 'inside' : 'outside'}
|
||||
onLocationChange={(location) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
isInside: location === 'inside',
|
||||
},
|
||||
});
|
||||
}}
|
||||
titlePlaceholder={
|
||||
frame.activeData?.[dataLayers[0].layerId]?.columns.find(
|
||||
(col) => col.id === dataLayers[0].splitAccessor
|
||||
)?.name ?? defaultLegendTitle
|
||||
}
|
||||
legendTitle={state?.legend.title}
|
||||
onLegendTitleChange={({ title, visible }) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
title,
|
||||
isTitleVisible: visible,
|
||||
},
|
||||
});
|
||||
}}
|
||||
isTitleVisible={state?.legend.isTitleVisible}
|
||||
onDisplayChange={(optionId) => {
|
||||
const newMode = legendOptions.find(({ id }) => id === optionId)!.value;
|
||||
if (newMode === 'auto') {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
isVisible: true,
|
||||
showSingleSeries: false,
|
||||
},
|
||||
});
|
||||
} else if (newMode === 'show') {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
isVisible: true,
|
||||
showSingleSeries: true,
|
||||
},
|
||||
});
|
||||
} else if (newMode === 'hide') {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
isVisible: false,
|
||||
showSingleSeries: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
position={state?.legend.position}
|
||||
horizontalAlignment={state?.legend.horizontalAlignment}
|
||||
verticalAlignment={state?.legend.verticalAlignment}
|
||||
floatingColumns={state?.legend.floatingColumns}
|
||||
onFloatingColumnsChange={(val) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, floatingColumns: val },
|
||||
});
|
||||
}}
|
||||
maxLines={state?.legend.maxLines}
|
||||
onMaxLinesChange={(val) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, maxLines: val },
|
||||
});
|
||||
}}
|
||||
shouldTruncate={state?.legend.shouldTruncate ?? defaultParamsFromDatasources}
|
||||
onTruncateLegendChange={() => {
|
||||
const current = state?.legend.shouldTruncate ?? defaultParamsFromDatasources;
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, shouldTruncate: !current },
|
||||
});
|
||||
}}
|
||||
onPositionChange={(id) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, position: id as Position },
|
||||
});
|
||||
}}
|
||||
onAlignmentChange={(value) => {
|
||||
const [vertical, horizontal] = value.split('_');
|
||||
const verticalAlignment = vertical as LegendSettingsPopoverProps['verticalAlignment'];
|
||||
const horizontalAlignment =
|
||||
horizontal as LegendSettingsPopoverProps['horizontalAlignment'];
|
||||
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, verticalAlignment, horizontalAlignment },
|
||||
});
|
||||
}}
|
||||
allowedLegendStats={nonOrdinalXAxis ? xyLegendValues : undefined}
|
||||
legendStats={state?.legend.legendStats}
|
||||
onLegendStatsChange={(legendStats, hasConvertedToTable) => {
|
||||
if (hasConvertedToTable) {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
legendStats,
|
||||
legendSize: LegendSize.AUTO,
|
||||
isVisible: true,
|
||||
showSingleSeries: true,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
legendStats,
|
||||
isVisible: true,
|
||||
showSingleSeries: true,
|
||||
},
|
||||
});
|
||||
}}
|
||||
legendSize={legendSize}
|
||||
onLegendSizeChange={(newLegendSize) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
legendSize: newLegendSize,
|
||||
},
|
||||
});
|
||||
}}
|
||||
showAutoLegendSizeOption={true}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}>
|
||||
|
@ -766,6 +633,118 @@ export const XyToolbar = memo(function XyToolbar(
|
|||
</TooltipWrapper>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<LegendSettingsPopover
|
||||
legendOptions={legendOptions}
|
||||
mode={legendMode}
|
||||
location={state?.legend.isInside ? 'inside' : 'outside'}
|
||||
onLocationChange={(location) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
isInside: location === 'inside',
|
||||
},
|
||||
});
|
||||
}}
|
||||
titlePlaceholder={
|
||||
frame.activeData?.[dataLayers[0].layerId]?.columns.find(
|
||||
(col) => col.id === dataLayers[0].splitAccessor
|
||||
)?.name ?? defaultLegendTitle
|
||||
}
|
||||
legendTitle={state?.legend.title}
|
||||
onLegendTitleChange={({ title, visible }) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
title,
|
||||
isTitleVisible: visible,
|
||||
},
|
||||
});
|
||||
}}
|
||||
isTitleVisible={state?.legend.isTitleVisible}
|
||||
onDisplayChange={(optionId) => {
|
||||
const newMode = legendOptions.find(({ id }) => id === optionId)!.value;
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
isVisible: newMode !== 'hide',
|
||||
showSingleSeries: newMode === 'show',
|
||||
},
|
||||
});
|
||||
}}
|
||||
position={state?.legend.position}
|
||||
horizontalAlignment={state?.legend.horizontalAlignment}
|
||||
verticalAlignment={state?.legend.verticalAlignment}
|
||||
floatingColumns={state?.legend.floatingColumns}
|
||||
onFloatingColumnsChange={(val) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, floatingColumns: val },
|
||||
});
|
||||
}}
|
||||
maxLines={state?.legend.maxLines}
|
||||
onMaxLinesChange={(val) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, maxLines: val },
|
||||
});
|
||||
}}
|
||||
shouldTruncate={state?.legend.shouldTruncate ?? defaultParamsFromDatasources}
|
||||
onTruncateLegendChange={() => {
|
||||
const current = state?.legend.shouldTruncate ?? defaultParamsFromDatasources;
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, shouldTruncate: !current },
|
||||
});
|
||||
}}
|
||||
onPositionChange={(id) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, position: id as Position },
|
||||
});
|
||||
}}
|
||||
onAlignmentChange={(value) => {
|
||||
const [vertical, horizontal] = value.split('_');
|
||||
const verticalAlignment = vertical as LegendSettingsPopoverProps['verticalAlignment'];
|
||||
const horizontalAlignment =
|
||||
horizontal as LegendSettingsPopoverProps['horizontalAlignment'];
|
||||
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, verticalAlignment, horizontalAlignment },
|
||||
});
|
||||
}}
|
||||
allowedLegendStats={nonOrdinalXAxis ? xyLegendValues : undefined}
|
||||
legendStats={state?.legend.legendStats}
|
||||
onLegendStatsChange={(legendStats, hasConvertedToTable) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
legendStats,
|
||||
isVisible: true,
|
||||
showSingleSeries: true,
|
||||
...(hasConvertedToTable ? { legendSize: LegendSize.AUTO } : {}),
|
||||
},
|
||||
});
|
||||
}}
|
||||
legendSize={legendSize}
|
||||
onLegendSizeChange={(newLegendSize) => {
|
||||
setState({
|
||||
...state,
|
||||
legend: {
|
||||
...state.legend,
|
||||
legendSize: newLegendSize,
|
||||
},
|
||||
});
|
||||
}}
|
||||
showAutoLegendSizeOption={true}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ToolbarPopover, ValueLabelsSettings } from '../../../../shared_components';
|
||||
import { XYState } from '../../types';
|
||||
import type { FramePublicAPI } from '../../../../types';
|
||||
|
||||
export interface TextPopoverProps {
|
||||
state: XYState;
|
||||
setState: (newState: XYState) => void;
|
||||
datasourceLayers: FramePublicAPI['datasourceLayers'];
|
||||
}
|
||||
const PANEL_STYLE = {
|
||||
width: '460px',
|
||||
};
|
||||
|
||||
export const TextPopover: React.FC<TextPopoverProps> = ({ state, setState }) => {
|
||||
return (
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.shared.titlesAndTextLabel', {
|
||||
defaultMessage: 'Titles and text',
|
||||
})}
|
||||
type="titlesAndText"
|
||||
groupPosition="none"
|
||||
buttonDataTestSubj="lnsTextOptionsButton"
|
||||
data-test-subj="lnsTextOptionsPopover"
|
||||
panelStyle={PANEL_STYLE}
|
||||
>
|
||||
<ValueLabelsSettings
|
||||
label={i18n.translate('xpack.lens.shared.chartBarLabelVisibilityLabel', {
|
||||
defaultMessage: 'Bar values',
|
||||
})}
|
||||
valueLabels={state?.valueLabels ?? 'hide'}
|
||||
onValueLabelChange={(newMode) => {
|
||||
setState({ ...state, valueLabels: newMode });
|
||||
}}
|
||||
/>
|
||||
</ToolbarPopover>
|
||||
);
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue