From 9465fc6d9db31fa2269119bf5f4d51979a94b783 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Fri, 31 Jul 2020 15:46:01 -0700 Subject: [PATCH] Add annotations to the line chart component --- ui/app/components/line-chart.js | 47 ++++++++++++++++++- ui/app/styles/charts.scss | 1 + ui/app/styles/charts/chart-annotation.scss | 46 ++++++++++++++++++ ui/app/styles/charts/line-chart.scss | 16 ++++++- ui/app/styles/charts/tooltip.scss | 5 ++ ui/app/templates/components/line-chart.hbs | 14 ++++++ .../components/scale-events-chart.hbs | 1 + 7 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 ui/app/styles/charts/chart-annotation.scss create mode 100644 ui/app/templates/components/scale-events-chart.hbs diff --git a/ui/app/components/line-chart.js b/ui/app/components/line-chart.js index 191e36062..76467454e 100644 --- a/ui/app/components/line-chart.js +++ b/ui/app/components/line-chart.js @@ -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); } diff --git a/ui/app/styles/charts.scss b/ui/app/styles/charts.scss index 9d7d4a47e..bdb259dd2 100644 --- a/ui/app/styles/charts.scss +++ b/ui/app/styles/charts.scss @@ -3,6 +3,7 @@ @import './charts/line-chart'; @import './charts/tooltip'; @import './charts/colors'; +@import './charts/chart-annotation.scss'; .inline-chart { height: 1.5rem; diff --git a/ui/app/styles/charts/chart-annotation.scss b/ui/app/styles/charts/chart-annotation.scss new file mode 100644 index 000000000..7283247b2 --- /dev/null +++ b/ui/app/styles/charts/chart-annotation.scss @@ -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; + } +} diff --git a/ui/app/styles/charts/line-chart.scss b/ui/app/styles/charts/line-chart.scss index eff9ec539..cc40991e4 100644 --- a/ui/app/styles/charts/line-chart.scss +++ b/ui/app/styles/charts/line-chart.scss @@ -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); diff --git a/ui/app/styles/charts/tooltip.scss b/ui/app/styles/charts/tooltip.scss index 028b7b944..2b5fd6419 100644 --- a/ui/app/styles/charts/tooltip.scss +++ b/ui/app/styles/charts/tooltip.scss @@ -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 { diff --git a/ui/app/templates/components/line-chart.hbs b/ui/app/templates/components/line-chart.hbs index eb62fb0c2..a4918d549 100644 --- a/ui/app/templates/components/line-chart.hbs +++ b/ui/app/templates/components/line-chart.hbs @@ -26,6 +26,20 @@ +
+ {{#each this.processedAnnotations as |annotation|}} +
+ +
+
+ {{/each}} +

diff --git a/ui/app/templates/components/scale-events-chart.hbs b/ui/app/templates/components/scale-events-chart.hbs new file mode 100644 index 000000000..47a2784b1 --- /dev/null +++ b/ui/app/templates/components/scale-events-chart.hbs @@ -0,0 +1 @@ +