Skip to content

Commit

Permalink
feat: l4e timeline (mock data only)
Browse files Browse the repository at this point in the history
  • Loading branch information
corteggiano authored and ssjagad committed Mar 29, 2024
1 parent 7248004 commit 829496c
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 0 deletions.
193 changes: 193 additions & 0 deletions packages/react-components/src/components/l4eWidget/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { MockData } from './fakeData';
import { AnomalyValue } from './types';
import { formatTooltip } from './utils/formatTooltip';
const mappedEvents = MockData.map((ev) => [ev.value.timestamp, 100, ev.value]);

// defines 2 x-axes:
// 1. x axis for main timeline, which shows timie value
// 2. hides the x-axis for the smaller timeline and hides any styling on it
const L4E_X_AXIS = {
xAxis: [
{
name: 'l4e-timeline-axis',
type: 'time',
boundaryGap: false,
show: true,
axisLabel: {
hideOverlap: true,
color: '#5f6b7a',
},
axisLine: {
lineStyle: {
color: '#e9ebed',
width: 2,
},
},
splitNumber: 6,
gridIndex: 0,
},
{
name: 'l4e-selection-axis',
type: 'time',
gridIndex: 1,
boundaryGap: false,
axisTick: { show: false },
splitLine: { show: false },
axisLabel: { show: false },
axisLine: {
onZero: false,
lineStyle: {
color: '#e9ebed',
width: 2,
},
},
},
],
};

const L4E_Y_AXIS = {
yAxis: [
{
type: 'value',
show: false,
min: 0,
gridIndex: 0,
},
{
gridIndex: 1,
axisLabel: { show: false },
axisLine: { show: false },
axisTick: { show: false },
splitLine: { show: false },
},
],
};

const L4E_TOOLTIP = {
tooltip: {
show: true,
trigger: 'item',
confine: true,
borderColor: '#C5C5C5',
},
};

// defines 3 data zooms:
// 1. inside zoom for main timeline, which handles gestures
// 2. slider zoom for main timeline, which overlaps the smaller timeline
// 3. disables zoom for the smaller timeline, which will always show 100% of the data
const L4E_DATA_ZOOM = {
dataZoom: [
{
type: 'inside',
xAxisIndex: [0],
start: 0,
end: 100,
zoomOnMouseWheel: true,
moveOnMouseMove: 'shift',
moveOnMouseWheel: false,
},
{
type: 'slider',
backgroundColor: 'rgba(255,255,255,0)',
xAxisIndex: [0],
fillerColor: 'rgba(255, 110, 110, .25)',
dataBackground: {
lineStyle: { opacity: 0 },
areaStyle: { opacity: 0 },
},
selectedDataBackground: {
lineStyle: { color: '#ffffff', opacity: 0 },
areaStyle: { color: '#ffffff', opacity: 0 },
},
moveHandleStyle: { color: '#C5C5C5' },
handleStyle: { borderColor: '#C5C5C5' },
height: 32,
bottom: 12,
},
{
type: 'inside',
disabled: true,
xAxisIndex: [1],
start: 0,
end: 100,
},
],
};

// grid layout with 2 sections:
// 1. top section is the large timeline
// 2. bottom section is a smaller version of the timeline
// which is overlapped by the data zoom slider
const L4E_GRID = {
grid: [
{
left: '10px',
right: '10px',
top: '30px',
show: true,
borderColor: '#C5C5C5',
},
{
left: '12px',
right: '12px',
height: 30,
bottom: 8,
},
],
};

// defines 2 series:
// 1. large timeline, which a user directly interacts with
// 2. smaller timeline, which sits below the slider data zoom and always shows 100% of the data
const L4E_SERIES = {
series: [
{
id: 'l4e_timeline',
type: 'bar',
color: '#D13212',
barMinWidth: 5,
barMaxWidth: 10,
xAxisIndex: 0,
yAxisIndex: 0,
tooltip: {
formatter: ({ data }: { data: [number, number, AnomalyValue] }) =>
formatTooltip(data),
},
encode: {
x: 'time',
y: 'value',
},
},
{
id: 'l4e_slider',
type: 'bar',
color: '#D13212',
silent: true,
barMinWidth: 5,
barMaxWidth: 5,
xAxisIndex: 1,
yAxisIndex: 1,
tooltip: {
show: false,
},
encode: {
x: 'time',
y: 'value',
},
},
],
};

export const DEFAULT_L4E_WIDGET_SETTINGS = {
...L4E_GRID,
...L4E_DATA_ZOOM,
...L4E_TOOLTIP,
...L4E_X_AXIS,
...L4E_Y_AXIS,
...L4E_SERIES,
dataset: {
dimensions: ['time', 'value', 'extraData'],
source: mappedEvents,
},
};
35 changes: 35 additions & 0 deletions packages/react-components/src/components/l4eWidget/fakeData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { AnomalyResult } from './types';

const BaseAnomalyResult = {
quality: 'GOOD',
value: {
anomalyScore: 50,
predictionReason: 'bad stuff idk',
diagnostics: [
{ name: 'Average Power', value: 0.4 },
{ name: 'Average Wind Speed', value: 0.22 },
{ name: 'RPM', value: 0.18 },
{ name: 'Torque', value: 0.1 },
{ name: 'Wind Direction', value: 0.05 },
{ name: 'Wind Speed', value: 0.05 },
],
},
};

function getRandomDate(from: Date, to: Date) {
const fromTime = from.getTime();
const toTime = to.getTime();
return new Date(fromTime + Math.random() * (toTime - fromTime)).getTime();
}

// creates array of 10 random dates within last 7 days
const times = new Array(10)
.fill(null)
.map(() =>
getRandomDate(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), new Date())
);

export const MockData: AnomalyResult[] = times.map((time) => ({
...BaseAnomalyResult,
value: { ...BaseAnomalyResult.value, timestamp: time },
}));
32 changes: 32 additions & 0 deletions packages/react-components/src/components/l4eWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { useEffect } from 'react';
import { useECharts } from '../../hooks/useECharts';
import { DEFAULT_L4E_WIDGET_SETTINGS } from './constants';
import { MockData } from './fakeData';

export const L4EWidget = () => {
const { ref, chartRef } = useECharts();

useEffect(() => {
const l4e = chartRef.current;
l4e?.setOption(DEFAULT_L4E_WIDGET_SETTINGS);
}, [chartRef]);

return (
<div style={{ background: 'grey' }}>
<div
ref={ref}
style={{ background: 'white', width: '1000px', height: '300px' }}
/>
<div
style={{
background: 'lightGrey',
width: '1000px',
height: '500px',
overflow: 'scroll',
}}
>
<pre>{JSON.stringify(MockData, null, 2)}</pre>
</div>
</div>
);
};
16 changes: 16 additions & 0 deletions packages/react-components/src/components/l4eWidget/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type AnomalyDiagnostic = {
name: string;
value: number;
};

export type AnomalyValue = {
anomalyScore: number;
predictionReason: string;
diagnostics: AnomalyDiagnostic[];
timestamp: number;
};

export type AnomalyResult = {
quality: string;
value: AnomalyValue;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AnomalyValue } from '../types';

export const formatTooltip = (data: [number, number, AnomalyValue]) => {
const { anomalyScore, diagnostics, predictionReason, timestamp } = data[2];
const div = document.createElement('div');
const timestampString = `<div><b>${new Date(
timestamp
).toLocaleString()}</b></div>`;
const anomalyScoreString = `<div><b>Anomaly Score</b> ${anomalyScore}</div>`;
const predictionReasonString = `<div><b>Prediction reason</b> ${predictionReason}</div>`;
const diagnosticString = diagnostics
.map(
({ name, value }: { name: string; value: number }) =>
`<div><span>${name}</span> - <span>${value * 100}%</span></div>`
)
.join('');

div.innerHTML = `${timestampString}<hr/>${anomalyScoreString}${predictionReasonString}<div><b>Contributing Properties</b></div><div>${diagnosticString}</div>`;
return div;
};
21 changes: 21 additions & 0 deletions packages/react-components/stories/l4e/l4e.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { L4EWidget } from '../../src/components/l4eWidget';

export default {
title: 'Widgets/L4E',
component: L4EWidget,
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof L4EWidget>;

export const MockDataKPI: ComponentStory<typeof L4EWidget> = () => {
return (
<div style={{ background: 'grey' }}>
<div style={{ height: '100%', width: '100%', padding: '20px' }}>
<L4EWidget />
</div>
</div>
);
};

0 comments on commit 829496c

Please sign in to comment.