123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- const d3 = require('../d3.js');
- const margin = { top: 10, right: 10, bottom: 10, left: 10 }
- const xAxisHeight = 20;
- const yAxisWidth = 20;
- const legendItemRectSize = 24;
- const legendItemTextMargin = 4;
- function iseq(array) {
- const seq = [];
- for (let i = 0; i < array.length; i++) seq.push(i);
- return seq;
- }
- class LineGraph {
- constructor({ container, height, colors=d3.schemePaired }) {
- this._container = d3.select(container);
- this._height = height;
- this._colors = colors;
- this._innerHeight = height - margin.top - margin.bottom - xAxisHeight;
- this._data = null;
- this._svg = this._container.append('svg')
- .attr('height', this._height)
- this._svg.node().setAttribute('xmlns', 'http://www.w3.org/2000/svg')
- this._svg.node().setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink')
- this._legend = this._container.append('div')
- .classed('legend', true)
- .style('margin-left', `${margin.left + yAxisWidth}px`)
- .style('margin-right', `${margin.right}px`);
- this._clipRect = this._svg
- .append('defs').append('clipPath')
- .attr('id', 'ar-line-graph-clip')
- .append('rect')
- .attr('height', this._innerHeight);
- this._graph = this._svg.append('g')
- .attr('transform',
- 'translate(' + (margin.left + yAxisWidth) + ',' + margin.top + ')');
- // Create background
- this._background = this._graph.append("rect")
- .attr("class", "background")
- .attr("height", this._innerHeight);
- // define scales
- this._colorScale = d3.scaleOrdinal();
- this._xScale = d3.scaleLinear()
- .domain([1, 0]);
- this._yScale = d3.scaleLinear()
- .domain([1, 0])
- .range([this._innerHeight, 0]);
- // create grid
- this._xGrid = d3.axisBottom(this._xScale)
- .ticks(8)
- .tickSize(-this._height);
- this._xGridElement = this._graph.append("g")
- .attr("class", "grid")
- .attr("transform", "translate(0," + this._innerHeight + ")");
- // create grid
- this._yGrid = d3.axisLeft(this._yScale)
- .ticks(8);
- this._yGridElement = this._graph.append("g")
- .attr("class", "grid");
- // define axis
- this._xAxis = d3.axisBottom(this._xScale)
- .ticks(4);
- this._xAxisElement = this._graph.append('g')
- .attr("class", "axis")
- .attr('transform', 'translate(0,' + this._innerHeight + ')');
- this._yAxis = d3.axisLeft(this._yScale)
- .ticks(4);
- this._yAxisElement = this._graph.append('g')
- .attr("class", "axis");
- this._yAxisTitle = this._graph.append('g')
- .attr("class", "axis-title");
- this._lines = this._graph.append("g")
- .classed('lines', true);
- this._lineDrawer = d3.line()
- .curve(d3.curveBasis)
- .x((d) => this._xScale(d[0]))
- .y((d) => this._yScale(d[1]));
- }
- setData(data) {
- this._data = data;
- }
- draw() {
- this._colorScale
- .domain(iseq(this._data.lines.length))
- .range(this._colors.slice(0, this._data.lines.length));
- this._yScale.domain(this._data.yDomain);
- this._xScale.domain(this._data.xDomain);
- this._drawLegend();
- this.resize();
- }
- _drawLegend() {
- const legendSelectPhase1 = this._legend.selectAll('svg.legend-item')
- .data(this._data.lines);
- legendSelectPhase1.select('.legend-color')
- .style('fill', (d, i) => this._colorScale(i));
- legendSelectPhase1.select('.legend-text')
- .text((d) => d.description);
- const lengedGroupEnter = legendSelectPhase1
- .enter().append('svg')
- .attr('height', legendItemRectSize)
- .classed('legend-item', true);
- lengedGroupEnter.append('rect')
- .classed('legend-background', true)
- .attr('width', legendItemRectSize)
- .attr('height', legendItemRectSize);
- lengedGroupEnter.append('text')
- .classed('legend-color', true)
- .style('fill', (d, i) => this._colorScale(i))
- .attr('x', legendItemRectSize / 2)
- .attr('y', legendItemRectSize / 2)
- .text('–');
- lengedGroupEnter.append('text')
- .classed('legend-text', true)
- .attr('x', legendItemRectSize + legendItemTextMargin)
- .attr('y', legendItemRectSize / 2)
- .text((d) => d.description);
- legendSelectPhase1.exit().remove();
- const legendSelectPhase2 = this._legend.selectAll('svg.legend-item')
- .data(this._colorScale.domain());
- legendSelectPhase2
- .attr('width', function () {
- return (
- legendItemRectSize +
- legendItemTextMargin +
- this.querySelector('.legend-text').getComputedTextLength()
- );
- })
- this._legend
- .style('grid-template-columns', function () {
- const maxWidth = Math.max(
- ...Array.from(this.querySelectorAll('svg.legend-item'))
- .map((elem) => Math.ceil(parseInt(elem.getAttribute('width'))))
- );
- return `repeat(auto-fit, minmax(${Math.ceil(maxWidth)}px, 1fr)`;
- });
- }
- resize() {
- const width = this._container.node().clientWidth;
- const innerWidth = width - (margin.left + margin.right + yAxisWidth);
- this._svg
- .attr('width', width);
- this._clipRect
- .attr('width', innerWidth);
- // set background
- this._background
- .attr("width", innerWidth);
- // set the ranges
- this._xScale.range([0, innerWidth]);
- // update grid
- this._yGrid.tickSize(-innerWidth);
- const yTicksMajors = this._yScale.ticks(4);
- this._yGridElement
- .call(this._yGrid);
- this._yGridElement
- .selectAll('.tick')
- .classed('minor', (d) => !yTicksMajors.includes(d));
- const xTicksMajors = [0, 6, 12, 18, 24];
- this._xGridElement.call(this._xGrid);
- this._xGridElement
- .selectAll('.tick')
- .classed('minor', (d) => !xTicksMajors.includes(d));
- // update axis
- this._xAxisElement.call(this._xAxis);
- this._yAxisElement
- .call(this._yAxis);
- // draw lines
- const curveSelect = this._lines.selectAll('path.mathline')
- .data(this._data.lines);
- curveSelect
- .attr('d', (d) => this._lineDrawer(d.line))
- .style("stroke", (d, i) => this._colorScale(i));
- curveSelect.enter().append("path")
- .attr('class', 'mathline')
- .attr('clip-path', 'url(#ar-line-graph-clip)')
- .attr('d', (d) => this._lineDrawer(d.line))
- .style("stroke", (d, i) => this._colorScale(i));
- curveSelect.exit().remove();
- }
- }
- module.exports = LineGraph;
|