Add annotations to the line chart component

This commit is contained in:
Michael Lange
2020-07-31 15:46:01 -07:00
parent 7360adba00
commit 9465fc6d9d
7 changed files with 128 additions and 2 deletions

View File

@@ -14,7 +14,7 @@ import d3Format from 'd3-format';
import d3TimeFormat from 'd3-time-format';
import WindowResizable from 'nomad-ui/mixins/window-resizable';
import styleStringProperty from 'nomad-ui/utils/properties/style-string';
import { classNames } from '@ember-decorators/component';
import { classNames, classNameBindings } from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
// Returns a new array with the specified number of points linearly
@@ -31,12 +31,25 @@ const lerp = ([low, high], numPoints) => {
// Round a number or an array of numbers
const nice = val => (val instanceof Array ? val.map(nice) : Math.round(val));
const iconFor = {
error: 'cancel-circle-fill',
info: 'info-circle-fill',
};
const iconClassFor = {
error: 'is-danger',
info: '',
};
@classic
@classNames('chart', 'line-chart')
@classNameBindings('annotations.length:with-annotations')
export default class LineChart extends Component.extend(WindowResizable) {
// Public API
data = null;
annotations = null;
onAnnotationClick() {}
xProp = null;
yProp = null;
timeseries = false;
@@ -100,6 +113,14 @@ export default class LineChart extends Component.extend(WindowResizable) {
tooltipPosition = null;
@styleStringProperty('tooltipPosition') tooltipStyle;
@computed('xAxisOffset')
get chartAnnotationBounds() {
return {
height: this.xAxisOffset,
};
}
@styleStringProperty('chartAnnotationBounds') chartAnnotationsStyle;
@computed('data.[]', 'xProp', 'timeseries', 'yAxisOffset')
get xScale() {
const xProp = this.xProp;
@@ -244,6 +265,26 @@ export default class LineChart extends Component.extend(WindowResizable) {
return area(this.data);
}
@computed('annotations.[]', 'xScale', 'xProp', 'timeseries')
get processedAnnotations() {
const { xScale, xProp, annotations, timeseries } = this;
if (!annotations || !annotations.length) return null;
return annotations.map(annotation => {
const x = xScale(annotation[xProp]);
const y = 0; // TODO: prevent overlap by staggering y-offset
const time = this.xFormat(timeseries)(annotation[xProp]);
return {
annotation,
style: `transform:translate(${x}px,${y}px)`,
icon: iconFor[annotation.type],
iconClass: iconClassFor[annotation.type],
label: `${annotation.type} event at ${time}`,
};
});
}
didInsertElement() {
this.updateDimensions();
@@ -344,6 +385,10 @@ export default class LineChart extends Component.extend(WindowResizable) {
}
}
annotationClick(annotation) {
this.onAnnotationClick(annotation);
}
windowResizeHandler() {
run.once(this, this.updateDimensions);
}

View File

@@ -3,6 +3,7 @@
@import './charts/line-chart';
@import './charts/tooltip';
@import './charts/colors';
@import './charts/chart-annotation.scss';
.inline-chart {
height: 1.5rem;

View File

@@ -0,0 +1,46 @@
.chart-annotation {
position: absolute;
height: 100%;
.indicator {
color: $grey;
display: block;
width: 20px;
height: 20px;
padding: 0;
border: none;
background: transparent;
margin-left: -10px;
margin-top: -10px;
cursor: pointer;
pointer-events: auto;
.icon {
width: 100%;
height: 100%;
}
}
@each $name, $pair in $colors {
$color: nth($pair, 1);
&.is-#{$name} .indicator {
color: $color;
&:hover,
&.is-hovered {
color: darken($color, 2.5%);
}
}
}
.line {
position: absolute;
left: 0;
top: 8px;
width: 1px;
height: calc(100% - 8px);
background: $grey;
z-index: -1;
}
}

View File

@@ -1,8 +1,13 @@
.chart.line-chart {
display: block;
height: 100%;
position: relative;
svg {
&.with-annotations {
margin-top: 1.5em;
}
& > svg {
display: block;
height: 100%;
width: 100%;
@@ -43,6 +48,15 @@
}
}
.line-chart-annotations {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
pointer-events: none;
}
@each $name, $pair in $colors {
$color: nth($pair, 1);

View File

@@ -72,6 +72,7 @@
}
.label {
white-space: nowrap;
font-weight: $weight-bold;
color: $black;
margin: 0;
@@ -80,6 +81,10 @@
color: rgba($grey, 0.6);
}
}
.value {
padding-left: 1em;
}
}
ol > li {

View File

@@ -26,6 +26,20 @@
<g aria-hidden="true" class="x-axis axis" transform="translate(0, {{this.xAxisOffset}})"></g>
<g aria-hidden="true" class="y-axis axis" transform="translate({{this.yAxisOffset}}, 0)"></g>
</svg>
<div data-test-annotations class="line-chart-annotations" style={{this.chartAnnotationsStyle}}>
{{#each this.processedAnnotations as |annotation|}}
<div class="chart-annotation {{annotation.iconClass}}" style={{annotation.style}}>
<button
type="button"
class="indicator"
title={{annotation.label}}
onclick={{action this.annotationClick annotation.annotation}}>
{{x-icon annotation.icon}}
</button>
<div class="line" />
</div>
{{/each}}
</div>
<div class="chart-tooltip is-snappy {{if this.isActive "active" "inactive"}}" style={{this.tooltipStyle}}>
<p>
<span class="label">

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1 @@
<LineChart @data={{this.data}} @annotations={{this.annotations}} @timeseries={{true}} />