Pull the Area chart primitive out of the LineChart component

This commit is contained in:
Michael Lange 2021-02-12 15:22:17 -08:00
parent ce8b9c42f2
commit 7512fcbf0a
5 changed files with 74 additions and 66 deletions

View File

@ -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>

View File

@ -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);
}
}

View File

@ -4,14 +4,12 @@ import { computed } from '@ember/object';
import { assert } from '@ember/debug';
import { observes } from '@ember-decorators/object';
import { computed as overridable } from 'ember-overridable-computed';
import { guidFor } from '@ember/object/internals';
import { run } from '@ember/runloop';
import { htmlSafe } from '@ember/template';
import d3 from 'd3-selection';
import d3Scale from 'd3-scale';
import d3Axis from 'd3-axis';
import d3Array from 'd3-array';
import d3Shape from 'd3-shape';
import d3Format from 'd3-format';
import d3TimeFormat from 'd3-time-format';
import WindowResizable from 'nomad-ui/mixins/window-resizable';
@ -73,16 +71,6 @@ export default class LineChart extends Component.extend(WindowResizable) {
isActive = false;
@computed()
get fillId() {
return `line-chart-fill-${guidFor(this)}`;
}
@computed()
get maskId() {
return `line-chart-mask-${guidFor(this)}`;
}
activeDatum = null;
@computed('activeDatum', 'timeseries', 'xProp')
@ -252,35 +240,6 @@ export default class LineChart extends Component.extend(WindowResizable) {
return this.width - this.yAxisWidth;
}
@computed('data.[]', 'xScale', 'yScale', 'curveMethod')
get line() {
const { xScale, yScale, xProp, yProp, curveMethod } = this;
const line = d3Shape
.line()
.curve(d3Shape[curveMethod])
.defined(d => d[yProp] != null)
.x(d => xScale(d[xProp]))
.y(d => yScale(d[yProp]));
return line(this.data);
}
@computed('data.[]', 'xScale', 'yScale', 'curveMethod')
get area() {
const { xScale, yScale, xProp, yProp, curveMethod } = this;
const area = d3Shape
.area()
.curve(d3Shape[curveMethod])
.defined(d => d[yProp] != null)
.x(d => xScale(d[xProp]))
.y0(yScale(0))
.y1(d => yScale(d[yProp]));
return area(this.data);
}
@computed('annotations.[]', 'xScale', 'xProp', 'timeseries')
get processedAnnotations() {
const { xScale, xProp, annotations, timeseries } = this;
@ -319,7 +278,7 @@ export default class LineChart extends Component.extend(WindowResizable) {
didInsertElement() {
this.updateDimensions();
const canvas = d3.select(this.element.querySelector('.canvas'));
const canvas = d3.select(this.element.querySelector('.hover-target'));
const updateActiveDatum = this.updateActiveDatum.bind(this);
const chart = this;

View File

@ -14,16 +14,18 @@
overflow: visible;
}
.canvas {
.line {
fill: transparent;
stroke-width: 1.25;
}
.hover-target {
fill: transparent;
stroke: transparent;
}
.hover-target {
fill: transparent;
stroke: transparent;
}
.line {
fill: transparent;
stroke-width: 1.25;
}
.area {
fill: none;
}
.axis {
@ -60,7 +62,7 @@
@each $name, $pair in $colors {
$color: nth($pair, 1);
.canvas.is-#{$name} {
.area.is-#{$name} {
.line {
stroke: $color;
}

View File

@ -8,23 +8,20 @@
and Y-axis values range from {{this.yRange.firstObject}} to {{this.yRange.lastObject}}.
{{/if}}
</description>
<defs>
<linearGradient x1="0" x2="0" y1="0" y2="1" class="{{this.chartClass}}" 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="y-gridlines gridlines" transform="translate({{this.yAxisOffset}}, 0)"></g>
<g class="canvas {{this.chartClass}}">
<path class="line" d="{{this.line}}" />
<rect class="area" x="0" y="0" width="{{this.yAxisOffset}}" height="{{this.xAxisOffset}}" fill="url(#{{this.fillId}})" clip-path="url(#{{this.maskId}})" />
<rect class="hover-target" x="0" y="0" width="{{this.yAxisOffset}}" height="{{this.xAxisOffset}}" />
</g>
<ChartPrimitives::Area
@data={{this.data}}
@colorClass={{this.chartClass}}
@xScale={{this.xScale}}
@yScale={{this.yScale}}
@xProp={{this.xProp}}
@yProp={{this.yProp}}
@width={{this.yAxisOffset}}
@height={{this.xAxisOffset}}
@curveMethod={{this.curveMethod}} />
<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>
<rect class="hover-target" x="0" y="0" width="{{this.yAxisOffset}}" height="{{this.xAxisOffset}}" />
</svg>
<div data-test-annotations class="line-chart-annotations" style={{this.chartAnnotationsStyle}}>
{{#each this.processedAnnotations key=this.annotationKey as |annotation|}}

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB