Skip to content

Commit

Permalink
Merge pull request #10069 from hashicorp/f-ui/line-chart-decomposed
Browse files Browse the repository at this point in the history
UI: Line chart decomposed
  • Loading branch information
DingoEatingFuzz authored Feb 24, 2021
2 parents e708d9b + 85257f6 commit 342a905
Show file tree
Hide file tree
Showing 20 changed files with 470 additions and 415 deletions.
13 changes: 13 additions & 0 deletions ui/app/components/chart-primitives/area.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<defs>
<linearGradient x1="0" x2="0" y1="0" y2="1" class="{{this.colorClass}}" id="{{this.fillId}}">
<stop class="start" offset="0%" />
<stop class="end" offset="100%" />
</linearGradient>
<clipPath id="{{this.maskId}}">
<path class="fill" d="{{this.area}}" />
</clipPath>
</defs>
<g class="area {{this.colorClass}}" ...attributes>
<path class="line" d="{{this.line}}" />
<rect class="fill" x="0" y="0" width="{{@width}}" height="{{@height}}" fill="url(#{{this.fillId}})" clip-path="url(#{{this.maskId}})" />
</g>
37 changes: 37 additions & 0 deletions ui/app/components/chart-primitives/area.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Component from '@glimmer/component';
import { default as d3Shape, area, line } from 'd3-shape';
import uniquely from 'nomad-ui/utils/properties/uniquely';

export default class ChartPrimitiveArea extends Component {
get colorClass() {
return this.args.colorClass || `${this.args.colorScale}-${this.args.index}`;
}

@uniquely('area-mask') maskId;
@uniquely('area-fill') fillId;

get line() {
const { xScale, yScale, xProp, yProp, curveMethod } = this.args;

const builder = line()
.curve(d3Shape[curveMethod])
.defined(d => d[yProp] != null)
.x(d => xScale(d[xProp]))
.y(d => yScale(d[yProp]));

return builder(this.args.data);
}

get area() {
const { xScale, yScale, xProp, yProp, curveMethod } = this.args;

const builder = area()
.curve(d3Shape[curveMethod])
.defined(d => d[yProp] != null)
.x(d => xScale(d[xProp]))
.y0(yScale(0))
.y1(d => yScale(d[yProp]));

return builder(this.args.data);
}
}
17 changes: 17 additions & 0 deletions ui/app/components/chart-primitives/v-annotations.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div data-test-annotations class="line-chart-annotations" style={{this.chartAnnotationsStyle}} ...attributes>
{{#each this.processed key=@key as |annotation|}}
<div data-test-annotation class="chart-annotation {{annotation.iconClass}} {{annotation.staggerClass}}" style={{annotation.style}}>
<button
type="button"
title={{annotation.label}}
class="indicator {{if (or
(and @key (eq-by @key annotation.annotation this.activeAnnotation))
(and (not @key) (eq annotation.annotation this.activeAnnotation))
) "is-active"}}"
{{on "click" (fn this.selectAnnotation annotation.annotation)}}>
{{x-icon annotation.icon}}
</button>
<div class="line" />
</div>
{{/each}}
</div>
62 changes: 62 additions & 0 deletions ui/app/components/chart-primitives/v-annotations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Component from '@glimmer/component';
import { htmlSafe } from '@ember/template';
import { action } from '@ember/object';
import styleString from 'nomad-ui/utils/properties/glimmer-style-string';

const iconFor = {
error: 'cancel-circle-fill',
info: 'info-circle-fill',
};

const iconClassFor = {
error: 'is-danger',
info: '',
};

export default class ChartPrimitiveVAnnotations extends Component {
@styleString
get chartAnnotationsStyle() {
return {
height: this.args.height,
};
}

get processed() {
const { scale, prop, annotations, timeseries, format } = this.args;

if (!annotations || !annotations.length) return null;

let sortedAnnotations = annotations.sortBy(prop);
if (timeseries) {
sortedAnnotations = sortedAnnotations.reverse();
}

let prevX = 0;
let prevHigh = false;
return sortedAnnotations.map(annotation => {
const x = scale(annotation[prop]);
if (prevX && !prevHigh && Math.abs(x - prevX) < 30) {
prevHigh = true;
} else if (prevHigh) {
prevHigh = false;
}
const y = prevHigh ? -15 : 0;
const formattedX = format(timeseries)(annotation[prop]);

prevX = x;
return {
annotation,
style: htmlSafe(`transform:translate(${x}px,${y}px)`),
icon: iconFor[annotation.type],
iconClass: iconClassFor[annotation.type],
staggerClass: prevHigh ? 'is-staggered' : '',
label: `${annotation.type} event at ${formattedX}`,
};
});
}

@action
selectAnnotation(annotation) {
if (this.args.annotationClick) this.args.annotationClick(annotation);
}
}
Loading

0 comments on commit 342a905

Please sign in to comment.