Skip to content

Commit

Permalink
feat: heatmap/swimlane chart type (opensearch-project#831)
Browse files Browse the repository at this point in the history
Generic chart for handling the Heat map and Swim lane. The main difference between those two is X-axis scaling, categorical vs. time-based. 

Currently supported:
* Linear, threshold, quantile, and quantize color scaling
* Time scaling for X-axis, with custom boundaries and interval 
* Tooltip for cells and Y-axis values
* Highlighting of the brushed area with customizable styling
* Legend based on the color scaling and handling the deselected series 
* Basic styling for axis, grid, and cells.
Events:
* onElementClick callback for the clicked cell on the chart
* Providing X, Y ranges and selected cells with onBrushEnd callback as part of the chart config

fix opensearch-project#752

Co-authored-by: Marco Vettorello <[email protected]>
  • Loading branch information
darnautov and markov00 authored Oct 2, 2020
1 parent 0486c49 commit 7cd9a6b
Show file tree
Hide file tree
Showing 74 changed files with 4,661 additions and 104 deletions.
35 changes: 28 additions & 7 deletions packages/osd-charts/.playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,38 @@
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<style>
html,
body,
#root {
body {
background: blanchedalmond !important;
width: 100%;
height: 100%;
padding: 10px;
overflow: hidden;
}

.chart {
position: relative;
background: white;
width: 800px;
height: 500px;
/*display: inline-block;
position: relative;
*/
width: 100%;
height: 100%;
overflow: auto;
}

.testing {
background: aquamarine;
position: relative;
width: 100%;
overflow: auto;
}

.page {
padding: 10px;
width: 100%;
height: 100%;
}
#root {
height: 100%;
wwidth: 100%;
}

label {
Expand All @@ -29,7 +48,9 @@
</head>

<body>
<div id="root"></div>
<div class="page">
<div id="root"></div>
</div>
<script src="bundle.js" type="text/javascript"></script>
</body>
</html>
160 changes: 152 additions & 8 deletions packages/osd-charts/.playground/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,159 @@
* specific language governing permissions and limitations
* under the License.
*/

/* eslint-disable no-console */
import React from 'react';

import { Example } from '../stories/bar/1_basic';
import { Chart, Heatmap, HeatmapConfig, RecursivePartial, ScaleType, Settings } from '../src';
import { HeatmapSpec } from '../src/chart_types/heatmap/specs';
import { BABYNAME_DATA } from '../src/utils/data_samples/babynames';
import { SWIM_LANE_DATA } from '../src/utils/data_samples/test_anomaly_swim_lane';

export class Playground extends React.Component<any, { highlightedData?: HeatmapSpec['highlightedData'] }> {
constructor(props: any) {
super(props);
this.state = {
highlightedData: {
x: [1572908400000, 1572910200000],
y: ['i-ca80c01a'],
},
};
}

onBrushEnd: HeatmapConfig['onBrushEnd'] = (e) => {
console.log('___onBrushEnd___', e);
this.setState({
highlightedData: { x: e.x as any[], y: e.y as any[] },
});
};

export function Playground() {
return (
<div className="chart">
<Example />
</div>
);
render() {
const heatmapConfig: RecursivePartial<HeatmapConfig> = {
grid: {
cellHeight: {
min: 20,
max: 20, // 'fill',
},
stroke: {
width: 1,
color: '#D3DAE6',
},
},
cell: {
maxWidth: 'fill',
maxHeight: 'fill',
label: {
visible: false,
},
border: {
stroke: '#D3DAE6',
strokeWidth: 0,
},
},
brushArea: {
fill: 'red',
},
yAxisLabel: {
visible: true,
width: { max: 50 },
padding: 5,
fill: '#6a717d',
},
xAxisLabel: {
fill: '#6a717d',
},
onBrushEnd: this.onBrushEnd,
};
console.log(
BABYNAME_DATA.filter(([year]) => year > 1950).map((d) => {
return [d[0], d[1], d[2], -d[3]];
}),
);
return (
<div>
<div className="chart" style={{ height: '500px', overflow: 'auto' }}>
<Chart>
<Settings
onElementClick={console.log}
showLegend
legendPosition="top"
brushAxis="both"
xDomain={{ min: 1572825600000, max: 1572912000000, minInterval: 1800000 }}
/>
<Heatmap
id="heatmap1"
ranges={[0, 3, 25, 50, 75]}
colorScale={ScaleType.Threshold}
colors={['#ffffff', '#d2e9f7', '#8bc8fb', '#fdec25', '#fba740', '#fe5050']}
data={SWIM_LANE_DATA.map((v) => ({ ...v, time: v.time * 1000 }))}
highlightedData={this.state.highlightedData}
xAccessor="time"
yAccessor={(d) => d.laneLabel}
valueAccessor="value"
valueFormatter={(d) => d.toFixed(0.2)}
ySortPredicate="numAsc"
xScaleType={ScaleType.Time}
config={heatmapConfig}
/>
</Chart>
</div>
<br />
<div className="chart" style={{ height: '500px' }}>
<Chart>
<Settings
onElementClick={console.log}
showLegend
legendPosition="left"
onBrushEnd={console.log}
brushAxis="both"
/>
<Heatmap
id="heatmap2"
colorScale={ScaleType.Linear}
colors={['yellow', 'red']}
data={
BABYNAME_DATA.filter(([year]) => year > 1950)
// .map((d, i) => {
// return [d[0], d[1], d[2], d[3] > 20000 ? -d[3] : d[3]];
// })
}
xAccessor={(d) => d[2]}
yAccessor={(d) => d[0]}
valueAccessor={(d) => d[3]}
valueFormatter={(value) => value.toFixed(0.2)}
xSortPredicate="alphaAsc"
config={{
grid: {
cellHeight: {
min: 40,
max: 40, // 'fill',
},
stroke: {
width: 0,
},
},
cell: {
maxWidth: 'fill',
maxHeight: 20,
label: {
visible: true,
},
border: {
stroke: 'white',
strokeWidth: 1,
},
},
yAxisLabel: {
visible: true,
width: 'auto',
textColor: '#6a717d',
},
}}
/>
</Chart>
</div>
</div>
);
}
}
/* eslint-enable no-console */
Loading

0 comments on commit 7cd9a6b

Please sign in to comment.