-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
line-chart.js
133 lines (114 loc) · 3.94 KB
/
line-chart.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { max } from 'd3-array';
// eslint-disable-next-line no-unused-vars
import { select, selectAll, node } from 'd3-selection';
import { axisLeft, axisBottom } from 'd3-axis';
import { scaleLinear, scalePoint } from 'd3-scale';
import { line } from 'd3-shape';
import { LIGHT_AND_DARK_BLUE, SVG_DIMENSIONS, formatNumbers } from '../../utils/chart-helpers';
/**
* @module LineChart
* LineChart components are used to display data in a line plot with accompanying tooltip
*
* @example
* ```js
* <LineChart @dataset={dataset} />
* ```
* @param {string} xKey - string denoting key for x-axis data (data[xKey]) of dataset
* @param {string} yKey - string denoting key for y-axis data (data[yKey]) of dataset
*/
export default class LineChart extends Component {
// TODO CMB make just one tracked variable tooltipText?
@tracked tooltipTarget = '';
@tracked tooltipMonth = '';
@tracked tooltipTotal = '';
@tracked tooltipNew = '';
get yKey() {
return this.args.yKey || 'total';
}
get xKey() {
return this.args.xKey || 'month';
}
@action removeTooltip() {
this.tooltipTarget = null;
}
@action
renderChart(element, args) {
const dataset = args[0];
const chartSvg = select(element);
chartSvg.attr('viewBox', `-50 20 600 ${SVG_DIMENSIONS.height}`); // set svg dimensions
// DEFINE AXES SCALES
const yScale = scaleLinear()
.domain([0, max(dataset.map((d) => d[this.yKey]))])
.range([0, 100])
.nice();
const yAxisScale = scaleLinear()
.domain([0, max(dataset.map((d) => d[this.yKey]))])
.range([SVG_DIMENSIONS.height, 0])
.nice();
const xScale = scalePoint() // use scaleTime()?
.domain(dataset.map((d) => d[this.xKey]))
.range([0, SVG_DIMENSIONS.width])
.padding(0.2);
// CUSTOMIZE AND APPEND AXES
const yAxis = axisLeft(yAxisScale)
.ticks(4)
.tickPadding(10)
.tickSizeInner(-SVG_DIMENSIONS.width) // makes grid lines length of svg
.tickFormat(formatNumbers);
const xAxis = axisBottom(xScale).tickSize(0);
yAxis(chartSvg.append('g'));
xAxis(chartSvg.append('g').attr('transform', `translate(0, ${SVG_DIMENSIONS.height + 10})`));
chartSvg.selectAll('.domain').remove();
// PATH BETWEEN PLOT POINTS
const lineGenerator = line()
.x((d) => xScale(d[this.xKey]))
.y((d) => yAxisScale(d[this.yKey]));
chartSvg
.append('g')
.append('path')
.attr('fill', 'none')
.attr('stroke', LIGHT_AND_DARK_BLUE[1])
.attr('stroke-width', 0.5)
.attr('d', lineGenerator(dataset));
// LINE PLOTS (CIRCLES)
chartSvg
.append('g')
.selectAll('circle')
.data(dataset)
.enter()
.append('circle')
.attr('class', 'data-plot')
.attr('cy', (d) => `${100 - yScale(d[this.yKey])}%`)
.attr('cx', (d) => xScale(d[this.xKey]))
.attr('r', 3.5)
.attr('fill', LIGHT_AND_DARK_BLUE[0])
.attr('stroke', LIGHT_AND_DARK_BLUE[1])
.attr('stroke-width', 1.5);
// LARGER HOVER CIRCLES
chartSvg
.append('g')
.selectAll('circle')
.data(dataset)
.enter()
.append('circle')
.attr('class', 'hover-circle')
.style('cursor', 'pointer')
.style('opacity', '0')
.attr('cy', (d) => `${100 - yScale(d[this.yKey])}%`)
.attr('cx', (d) => xScale(d[this.xKey]))
.attr('r', 10);
const hoverCircles = chartSvg.selectAll('.hover-circle');
// MOUSE EVENT FOR TOOLTIP
hoverCircles.on('mouseover', (data) => {
// TODO: how to genericize this?
this.tooltipMonth = data[this.xKey];
this.tooltipTotal = `${data[this.yKey]} total clients`;
this.tooltipNew = `${data?.new_clients[this.yKey]} new clients`;
let node = hoverCircles.filter((plot) => plot[this.xKey] === data[this.xKey]).node();
this.tooltipTarget = node;
});
}
}