[Uptime] Use elastic charts donut (#70364)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Shahzad 2020-07-03 12:58:42 +02:00 committed by GitHub
parent d1e6aa7206
commit fa2f60e57b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 442 additions and 89 deletions

View file

@ -123,7 +123,6 @@ exports[`ChartWrapper component renders the component with loading false 1`] = `
down={8}
height={144}
up={4}
width={144}
/>
</div>
</EuiErrorBoundary>
@ -252,7 +251,6 @@ exports[`ChartWrapper component renders the component with loading true 1`] = `
down={8}
height={144}
up={4}
width={144}
/>
</div>
<EuiFlexGroup

View file

@ -13,11 +13,361 @@ exports[`DonutChart component passes correct props without errors for valid prop
}
}
>
<svg
<Chart
aria-label="Pie chart showing the current status. 32 of 127 monitors are down."
height={125}
width={125}
/>
baseTheme={
Object {
"arcSeriesStyle": Object {
"arc": Object {
"opacity": 1,
"stroke": "black",
"strokeWidth": 1,
"visible": true,
},
},
"areaSeriesStyle": Object {
"area": Object {
"opacity": 0.3,
"visible": true,
},
"line": Object {
"opacity": 1,
"strokeWidth": 1,
"visible": true,
},
"point": Object {
"fill": "white",
"opacity": 1,
"radius": 2,
"strokeWidth": 1,
"visible": false,
},
},
"axes": Object {
"axisLineStyle": Object {
"stroke": "#eaeaea",
"strokeWidth": 1,
},
"axisTitleStyle": Object {
"fill": "#333",
"fontFamily": "sans-serif",
"fontSize": 12,
"fontStyle": "bold",
"padding": 8,
},
"gridLineStyle": Object {
"horizontal": Object {
"dash": Array [
0,
0,
],
"opacity": 1,
"stroke": "#D3DAE6",
"strokeWidth": 1,
"visible": true,
},
"vertical": Object {
"dash": Array [
0,
0,
],
"opacity": 1,
"stroke": "#D3DAE6",
"strokeWidth": 1,
"visible": true,
},
},
"tickLabelStyle": Object {
"fill": "#777",
"fontFamily": "sans-serif",
"fontSize": 10,
"fontStyle": "normal",
"padding": 4,
},
"tickLineStyle": Object {
"stroke": "#eaeaea",
"strokeWidth": 1,
"visible": true,
},
},
"background": Object {
"color": "transparent",
},
"barSeriesStyle": Object {
"displayValue": Object {
"fill": "#777",
"fontFamily": "sans-serif",
"fontSize": 8,
"fontStyle": "normal",
"offsetX": 0,
"offsetY": 0,
"padding": 0,
},
"rect": Object {
"opacity": 1,
},
"rectBorder": Object {
"strokeWidth": 0,
"visible": false,
},
},
"bubbleSeriesStyle": Object {
"point": Object {
"fill": "white",
"opacity": 1,
"radius": 2,
"strokeWidth": 1,
"visible": true,
},
},
"chartMargins": Object {
"bottom": 10,
"left": 10,
"right": 10,
"top": 10,
},
"chartPaddings": Object {
"bottom": 0,
"left": 0,
"right": 0,
"top": 0,
},
"colors": Object {
"defaultVizColor": "red",
"vizColors": Array [
"#1EA593",
"#2B70F7",
"#CE0060",
"#38007E",
"#FCA5D3",
"#F37020",
"#E49E29",
"#B0916F",
"#7B000B",
"#34130C",
],
},
"crosshair": Object {
"band": Object {
"fill": "#F5F5F5",
"visible": true,
},
"line": Object {
"dash": Array [
5,
5,
],
"stroke": "#777",
"strokeWidth": 1,
"visible": true,
},
},
"legend": Object {
"horizontalHeight": 64,
"spacingBuffer": 10,
"verticalWidth": 200,
},
"lineSeriesStyle": Object {
"line": Object {
"opacity": 1,
"strokeWidth": 1,
"visible": true,
},
"point": Object {
"fill": "white",
"opacity": 1,
"radius": 2,
"strokeWidth": 1,
"visible": true,
},
},
"scales": Object {
"barsPadding": 0.25,
"histogramPadding": 0.05,
},
"sharedStyle": Object {
"default": Object {
"opacity": 1,
},
"highlighted": Object {
"opacity": 1,
},
"unhighlighted": Object {
"opacity": 0.25,
},
},
}
}
renderer="canvas"
size={125}
theme={
Object {
"areaSeriesStyle": Object {
"area": Object {
"opacity": 0.3,
},
"line": Object {
"strokeWidth": 2,
},
"point": Object {
"fill": "rgba(255, 255, 255, 1)",
"radius": 3,
"strokeWidth": 2,
"visible": false,
},
},
"axes": Object {
"axisLineStyle": Object {
"stroke": "rgba(238, 240, 243, 1)",
},
"axisTitleStyle": Object {
"fill": "rgba(52, 55, 65, 1)",
"fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont,
'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
"fontSize": 12,
"padding": 10,
},
"gridLineStyle": Object {
"horizontal": Object {
"dash": Array [
0,
0,
],
"opacity": 1,
"stroke": "rgba(238, 240, 243, 1)",
"strokeWidth": 1,
"visible": true,
},
"vertical": Object {
"dash": Array [
4,
4,
],
"opacity": 1,
"stroke": "rgba(238, 240, 243, 1)",
"strokeWidth": 1,
"visible": true,
},
},
"tickLabelStyle": Object {
"fill": "rgba(105, 112, 125, 1)",
"fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont,
'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
"fontSize": 10,
"padding": 8,
},
"tickLineStyle": Object {
"stroke": "rgba(238, 240, 243, 1)",
"strokeWidth": 1,
"visible": false,
},
},
"barSeriesStyle": Object {
"displayValue": Object {
"fill": "rgba(105, 112, 125, 1)",
"fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont,
'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
"fontSize": 8,
},
},
"chartMargins": Object {
"bottom": 0,
"left": 0,
"right": 0,
"top": 0,
},
"colors": Object {
"defaultVizColor": "#6092C0",
"vizColors": Array [
"#54B399",
"#6092C0",
"#9170B8",
"#CA8EAE",
"#D36086",
"#E7664C",
"#AA6556",
"#DA8B45",
"#B9A888",
"#D6BF57",
],
},
"crosshair": Object {
"band": Object {
"fill": "rgba(245, 247, 250, 1)",
},
"line": Object {
"dash": Array [
4,
4,
],
"stroke": "rgba(105, 112, 125, 1)",
"strokeWidth": 1,
},
},
"lineSeriesStyle": Object {
"line": Object {
"strokeWidth": 2,
},
"point": Object {
"fill": "rgba(255, 255, 255, 1)",
"radius": 3,
"strokeWidth": 2,
},
},
"scales": Object {
"barsPadding": 0.25,
"histogramPadding": 0.05,
},
}
}
>
<Connect(SpecInstance) />
<Connect(SpecInstance)
config={
Object {
"circlePadding": 4,
"emptySizeRatio": 0.4,
"idealFontSizeJump": 1.1,
"linkLabel": Object {
"maximumSection": Infinity,
},
"margin": Object {
"bottom": 0,
"left": 0,
"right": 0,
"top": 0,
},
"outerSizeRatio": 0.9,
"partitionLayout": "sunburst",
}
}
data={
Array [
Object {
"label": "Down",
"value": 32,
},
Object {
"label": "Up",
"value": 95,
},
]
}
id="spec_1"
layers={
Array [
Object {
"groupByRollup": [Function],
"nodeLabel": [Function],
"shape": Object {
"fillColor": [Function],
},
},
]
}
valueAccessor={[Function]}
/>
</Chart>
</EuiFlexItem>
<EuiFlexItem>
<DonutChartLegend
@ -39,7 +389,7 @@ exports[`DonutChart component renders a donut chart 1`] = `
}
.c0 {
max-width: 260px;
max-width: 150px;
min-width: 100px;
}
@ -57,11 +407,26 @@ exports[`DonutChart component renders a donut chart 1`] = `
class="euiFlexItem euiFlexItem--flexGrowZero"
style="position:relative"
>
<svg
aria-label="Pie chart showing the current status. 32 of 127 monitors are down."
height="125"
width="125"
/>
<div
class="echChart"
style="width:125px;height:125px"
>
<div
class="echChartBackground"
style="background-color:transparent"
/>
<div
class="echChartStatus"
data-ech-render-complete="false"
data-ech-render-count="0"
/>
<div
class="echChartResizer"
/>
<div
class="echContainer"
/>
</div>
</div>
<div
class="euiFlexItem"
@ -164,7 +529,7 @@ exports[`DonutChart component renders a green check when all monitors are up 1`]
}
.c1 {
max-width: 260px;
max-width: 150px;
min-width: 100px;
}
@ -191,11 +556,26 @@ exports[`DonutChart component renders a green check when all monitors are up 1`]
class="euiFlexItem euiFlexItem--flexGrowZero"
style="position:relative"
>
<svg
aria-label="Pie chart showing the current status. 0 of 95 monitors are down."
height="125"
width="125"
/>
<div
class="echChart"
style="width:125px;height:125px"
>
<div
class="echChartBackground"
style="background-color:transparent"
/>
<div
class="echChartStatus"
data-ech-render-complete="false"
data-ech-render-count="0"
/>
<div
class="echChartResizer"
/>
<div
class="echContainer"
/>
</div>
<div
class="c0 greenCheckIcon"
data-euiicon-type="checkInCircleFilled"

View file

@ -12,7 +12,6 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { ChartWrapper } from '../chart_wrapper';
import { SnapshotHeading } from '../../../overview/snapshot/snapshot_heading';
import { DonutChart } from '../donut_chart';
const SNAPSHOT_CHART_WIDTH = 144;
const SNAPSHOT_CHART_HEIGHT = 144;
describe('ChartWrapper component', () => {
it('renders the component with loading false', () => {
@ -20,7 +19,7 @@ describe('ChartWrapper component', () => {
<ChartWrapper loading={false}>
<SnapshotHeading total={12} />
<EuiSpacer size="xs" />
<DonutChart up={4} down={8} height={SNAPSHOT_CHART_HEIGHT} width={SNAPSHOT_CHART_WIDTH} />
<DonutChart up={4} down={8} height={SNAPSHOT_CHART_HEIGHT} />
</ChartWrapper>
);
expect(component).toMatchSnapshot();
@ -31,7 +30,7 @@ describe('ChartWrapper component', () => {
<ChartWrapper loading={true}>
<SnapshotHeading total={12} />
<EuiSpacer size="xs" />
<DonutChart up={4} down={8} height={SNAPSHOT_CHART_HEIGHT} width={SNAPSHOT_CHART_WIDTH} />
<DonutChart up={4} down={8} height={SNAPSHOT_CHART_HEIGHT} />
</ChartWrapper>
);
expect(component).toMatchSnapshot();
@ -42,7 +41,7 @@ describe('ChartWrapper component', () => {
<ChartWrapper loading={true}>
<SnapshotHeading total={12} />
<EuiSpacer size="xs" />
<DonutChart up={4} down={8} height={SNAPSHOT_CHART_HEIGHT} width={SNAPSHOT_CHART_WIDTH} />
<DonutChart up={4} down={8} height={SNAPSHOT_CHART_HEIGHT} />
</ChartWrapper>
);
@ -64,7 +63,7 @@ describe('ChartWrapper component', () => {
<ChartWrapper loading={true}>
<SnapshotHeading total={12} />
<EuiSpacer size="xs" />
<DonutChart up={4} down={8} height={SNAPSHOT_CHART_HEIGHT} width={SNAPSHOT_CHART_WIDTH} />
<DonutChart up={4} down={8} height={SNAPSHOT_CHART_HEIGHT} />
</ChartWrapper>
);

View file

@ -5,10 +5,10 @@
*/
import { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
import React, { useContext, useEffect, useRef } from 'react';
import * as d3 from 'd3';
import React, { useContext } from 'react';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import { Chart, Datum, Partition, Settings, PartitionLayout } from '@elastic/charts';
import { DonutChartLegend } from './donut_chart_legend';
import { UptimeThemeContext } from '../../../contexts';
@ -16,7 +16,6 @@ interface DonutChartProps {
down: number;
height: number;
up: number;
width: number;
}
export const GreenCheckIcon = styled(EuiIcon)`
@ -28,72 +27,56 @@ export const GreenCheckIcon = styled(EuiIcon)`
position: absolute;
`;
export const DonutChart = ({ height, down, up, width }: DonutChartProps) => {
const chartElement = useRef<SVGSVGElement | null>(null);
export const DonutChart = ({ height, down, up }: DonutChartProps) => {
const {
colors: { danger, gray },
chartTheme,
} = useContext(UptimeThemeContext);
let upCount = up;
if (up === 0 && down === 0) {
upCount = 1;
}
useEffect(() => {
if (chartElement.current !== null) {
// we must remove any existing paths before painting
d3.select(chartElement.current).selectAll('g').remove();
const svgElement = d3
.select(chartElement.current)
.append('g')
.attr('transform', `translate(${width / 2}, ${height / 2})`);
const color = d3.scale.ordinal().domain(['up', 'down']).range([gray, danger]);
const pieGenerator = d3.layout
.pie()
.value(({ value }: any) => value)
// these start/end angles will reverse the direction of the pie,
// which matches our design
.startAngle(2 * Math.PI)
.endAngle(0);
svgElement
.selectAll('g')
.data(
// @ts-ignore pie generator expects param of type number[], but only works with
// output of d3.entries, which is like Array<{ key: string, value: number }>
pieGenerator(d3.entries({ up: upCount, down }))
)
.enter()
.append('path')
.attr(
'd',
// @ts-ignore attr does not expect a param of type Arc<Arc> but it behaves as desired
d3.svg
.arc()
.innerRadius(width * 0.28)
.outerRadius(Math.min(width, height) / 2 - 10)
)
.attr('fill', (d: any) => color(d.data.key) as any);
}
}, [danger, down, gray, height, upCount, width]);
return (
<EuiFlexGroup alignItems="center" responsive={false}>
<EuiFlexItem grow={false} style={{ position: 'relative' }}>
<svg
<Chart
size={height}
aria-label={i18n.translate('xpack.uptime.snapshot.donutChart.ariaLabel', {
defaultMessage:
'Pie chart showing the current status. {down} of {total} monitors are down.',
values: { down, total: up + down },
})}
ref={chartElement}
width={width}
height={height}
/>
{/* When all monitors are up we show green check icon in middle of donut to indicate, all is well */}
{...chartTheme}
>
<Settings />
<Partition
id="spec_1"
data={[
{ value: down, label: 'Down' },
{ value: up, label: 'Up' },
]}
valueAccessor={(d: Datum) => d.value as number}
layers={[
{
groupByRollup: (d: Datum) => d.label,
nodeLabel: (d: Datum) => d,
shape: {
fillColor: (d: Datum) => {
return d.dataName === 'Down' ? danger : gray;
},
},
},
]}
config={{
partitionLayout: PartitionLayout.sunburst,
linkLabel: {
maximumSection: Infinity,
},
margin: { top: 0, bottom: 0, left: 0, right: 0 },
idealFontSizeJump: 1.1,
outerSizeRatio: 0.9,
emptySizeRatio: 0.4,
circlePadding: 4,
}}
/>
</Chart>
{down === 0 && <GreenCheckIcon className="greenCheckIcon" type="checkInCircleFilled" />}
</EuiFlexItem>
<EuiFlexItem>

View file

@ -12,7 +12,7 @@ import { DonutChartLegendRow } from './donut_chart_legend_row';
import { UptimeThemeContext } from '../../../contexts';
const LegendContainer = styled.div`
max-width: 260px;
max-width: 150px;
min-width: 100px;
@media (max-width: 767px) {
min-width: 0px;

View file

@ -14,7 +14,6 @@ exports[`Snapshot component renders without errors 1`] = `
down={2}
height={144}
up={8}
width={144}
/>
</ChartWrapper>
`;

View file

@ -11,7 +11,6 @@ import { ChartWrapper } from '../../common/charts/chart_wrapper';
import { SnapshotHeading } from './snapshot_heading';
import { Snapshot as SnapshotType } from '../../../../common/runtime_types';
const SNAPSHOT_CHART_WIDTH = 144;
const SNAPSHOT_CHART_HEIGHT = 144;
interface SnapshotComponentProps {
@ -29,11 +28,6 @@ export const SnapshotComponent: React.FC<SnapshotComponentProps> = ({ count, hei
<ChartWrapper loading={loading} height={height}>
<SnapshotHeading total={count.total} />
<EuiSpacer size="xs" />
<DonutChart
up={count.up}
down={count.down}
height={SNAPSHOT_CHART_HEIGHT}
width={SNAPSHOT_CHART_WIDTH}
/>
<DonutChart up={count.up} down={count.down} height={SNAPSHOT_CHART_HEIGHT} />
</ChartWrapper>
);