UNPKG

ng-d3-graphs

Version:

<img src="./assets/ng-d3.png" alt="drawing" width="250" height="250"/>

1,268 lines (1,241 loc) 45.6 kB
import { __decorate, __param } from 'tslib'; import { ɵɵdefineInjectable, Injectable, ElementRef, Input, HostListener, Component, ViewEncapsulation, NgModule, ChangeDetectionStrategy, Inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { axisLeft, axisBottom, axisRight, axisTop, line, scaleLinear, extent, scaleBand, timeFormat, select, timeParse, scaleTime, min, max, curveStep, area, utcParse, schemeSet2, scaleOrdinal, pie, entries, arc, interpolateCool } from 'd3'; import { Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; const axisConfig = { color: 'lightgrey', opacity: 1, rendering: 'crispEdges', strokeWidth: '1px', xAxisTimeParser: '%Y-%m-%dT%H:%M:%S.%LZ', xAxisTimeFormat: '%m/%d/%y', xAxisTicks: 5, }; var AxisDirection; (function (AxisDirection) { AxisDirection["top"] = "top"; AxisDirection["right"] = "right"; AxisDirection["bottom"] = "bottom"; AxisDirection["left"] = "left"; })(AxisDirection || (AxisDirection = {})); let D3Service = class D3Service { constructor() { } translate(x, y) { return `translate(${x}, ${y})`; } factoryAxis(scale, direction) { switch (direction) { case AxisDirection.top: return axisTop(scale); case AxisDirection.right: return axisRight(scale); case AxisDirection.bottom: return axisBottom(scale); case AxisDirection.left: return axisLeft(scale); default: return new Error('No axis Direction Provided'); } } factoryLine() { return line().x((d) => d.x).y((d) => d.y); } // ==== Axis ===== scaleLinearX(labels, width) { return scaleLinear() .domain(extent(labels)) // does the magic for adjustable axis .range([0, width]); } scaleLinearY(data, height) { return scaleLinear() .domain(extent(data)) // does the magic for adjustable axis .range([height, 0]); } scaleBandX(labels, width) { return scaleBand().domain(labels).rangeRound([0, width]).padding(0.1); } scaleLinearYRangeRound(data, height) { return scaleLinear().domain([0, Math.max(...data)]).rangeRound([ height, 0 ]); } addLabelAxisY(svg, height, options) { svg.append('text') .attr('transform', 'rotate(-90)') .attr('y', 0 - options.margin.left) .attr('x', 0 - height / 2) .attr('dy', '1em') .style('text-anchor', 'middle') .text(options.yAxisLabel); } addLabelAxisX(svg, width, height, options) { svg.append('text') .attr('transform', 'translate(' + width / 2 + ' ,' + (height + options.margin.top) + ')') .style('text-anchor', 'middle') .text(options.xAxisLabel); } getViewBoxDefault(options) { const res = { minX: -options.margin.left, minY: -25, width: options.width, height: options.height - options.margin.top, }; return res; } removeAxisTicks(axis) { axis.selectAll('.tick').selectAll('line').remove(); } changeAxisColor(axis, config) { axis.select('path') .attr('color', config.color) .attr('opacity', config.opacity) .attr('rendering', config.rendering) .attr('stroke-width', config.strokeWidth); } getXaxisTime(svg, height, x, timeFormat$1, xAxisTicks) { return svg.append('g') .attr('transform', `translate(0,${height})`) .call(axisBottom(x) .tickFormat(timeFormat(timeFormat$1)) .ticks(xAxisTicks)); } hideTooltip(tooltipText, tooltip) { tooltipText.selectAll('tspan').remove(); tooltip.attr('visibility', 'hidden'); } showTooltip(d, xScale, yScale, tooltip, tooltipRect, tooltipText, formatTime) { const xPos = xScale(d.x) - 150 / 2; const yPos = yScale(d.y) + 10; tooltip.attr('transform', `translate(${xPos}, ${yPos})`) .attr('is', true) .attr('visibility', 'visible'); tooltipRect.attr('opacity', 0.7); tooltipText.attr('tranform', 'translate(75,30)') .attr('fill', 'white') .attr('font-size', 10) .attr('font-family', `'Roboto', 'sans-serif'`); tooltipText.append('tspan') .attr('text-anchor', 'middle') .attr('is', true) .attr('x', 25) .attr('y', -5) .text(`${formatTime(d.x)}`); tooltipText.append('tspan') .attr('text-anchor', 'middle') .attr('is', true) .attr('x', 20) .attr('dy', 15) .text(`${d.y}`); } addTooltip(container) { const tooltipConfig = { width: 100, height: 40, fill: '#333', opacity: 0.7, rx: 15, text: { translateX: 10, translateY: 20, }, }; const tooltip = select(container.nativeElement).select('svg').append('g'); const tooltipRect = tooltip.append('rect') .attr('width', tooltipConfig.width) .attr('height', tooltipConfig.height) .attr('fill', tooltipConfig.fill) .attr('opacity', 0) .attr('rx', tooltipConfig.rx); const tooltipText = tooltip.append('text').attr('transform', `translate( ${tooltipConfig.text.translateX}, ${tooltipConfig.text.translateY})`); return { tooltip, tooltipRect, tooltipText, tooltipConfig }; } }; D3Service.ngInjectableDef = ɵɵdefineInjectable({ factory: function D3Service_Factory() { return new D3Service(); }, token: D3Service, providedIn: "root" }); D3Service = __decorate([ Injectable({ providedIn: 'root' }) ], D3Service); let BandComponent = class BandComponent { constructor(container, d3Service) { this.container = container; this.d3Service = d3Service; this.data = []; this.labels = []; this.options = {}; this.labelsAndData = []; this.viewBox = {}; this._options = { width: 879, height: 804, margin: { top: 50, right: 50, bottom: 50, left: 50 }, yAxisLabel: '', gridTicks: 0, timeParser: axisConfig.xAxisTimeParser, timeFormat: axisConfig.xAxisTimeFormat, xAxisTicks: axisConfig.xAxisTicks, }; this.parseTime = timeParse(this.options.timeParser); this.formatTime = timeFormat(this.options.timeFormat); this.onResize$ = new Subject(); } onResize() { this.onResize$.next(); } ngOnInit() { this.options = Object.assign({}, this._options, this.options); this.viewBox = { minX: -this.options.margin.left, minY: -10, width: this.options.width + this.options.margin.left + this.options.margin.right, height: this.options.height + this.options.margin.top, }; this.parseTime = timeParse(this.options.timeParser); this.formatTime = timeFormat(this.options.timeFormat); this.labels = this.formatLabels(); this.labelsAndData = this.combineLabelsDataToOne(); this.onResizeEvent(); this.render(); } formatLabels() { return this.labels.map(d => this.parseTime(d)); } combineLabelsDataToOne() { const N = this.labels.length; const result = []; for (let index = 0; index < N; index++) { result.push({ x: this.labels[index], low: this.data[index].low, high: this.data[index].high, }); } return result; } render() { const currentWidth = parseInt(select(this.container.nativeElement).select('div').style('width'), 10); const currentHeight = parseInt(select(this.container.nativeElement).select('div').style('height'), 10); const width = this.options.width - this.options.margin.left - this.options.margin.right; const height = this.options.height - this.options.margin.top - this.options.margin.bottom; this.viewBox = { minX: -this.options.margin.left, minY: -10, width: this.options.width, height: this.options.height - this.options.margin.top, }; const svg = select(this.container.nativeElement) .select('div') .append('svg') .attr('width', currentWidth) .attr('height', currentHeight) .attr('viewBox', `${this.viewBox.minX} ${this.viewBox.minY} ${this.viewBox.width} ${this.viewBox.height}`) .classed('svg-content', true) .append('g'); const x = scaleTime() .domain(extent(this.labels, (d) => new Date(d))) .range([0, width]); const y = scaleLinear() .domain([ min(this.data, (d) => d.low), max(this.data, (d) => d.high) ]) .nice(this.options.gridTicks) .range([height, 0]); // add the X gridlines svg.append('g') .attr('class', 'grid') .call(this.make_x_gridlines(x).tickSize(height) // .tickFormat('') ); // add the Y gridlines svg.append('g') .attr('class', 'grid') .call(this.make_y_gridlines(y).tickSize(-width) // .tickFormat('') ); const xAxis = this.d3Service.getXaxisTime(svg, height, x, this.options.timeFormat, this.options.xAxisTicks); const yAxis = (g) => g.attr('transform', `translate(${0},0)`).call(axisLeft(y)); const curve = curveStep; const area$1 = area() .curve(curve) .x((d) => x(d.x)) .y0((d) => y(d.low)) .y1((d) => y(d.high)); const _yAxis = svg.append('g').call(yAxis); // this.d3Service.addLabelAxisX(svg, width, height, this.options); // text label for the x axis this.addLabelAxisX(svg, width, height); // text label for the y axis this.addLabelAxisY(svg, height); svg.append('path') .datum(this.labelsAndData) .attr('fill', 'steelblue') .attr('d', area$1); this.removeAxisTicks(xAxis); this.removeAxisTicks(_yAxis); this.changeAxisColor(xAxis, axisConfig); this.changeAxisColor(_yAxis, axisConfig); } changeAxisColor(axis, config) { this.d3Service.changeAxisColor(axis, config); } removeAxisTicks(axis) { this.d3Service.removeAxisTicks(axis); } addLabelAxisY(svg, height) { svg.append('text') .attr('transform', 'rotate(0)') .attr('y', 0 - this.options.margin.top / 2) .attr('x', 0) .attr('dy', '1em') .style('text-anchor', 'start') .text(this.options.yAxisLabel); } addLabelAxisX(svg, width, height) { svg.append('text') .attr('transform', 'translate(' + width / 2 + ' ,' + (height + this.options.margin.top - 15) + ')') .style('text-anchor', 'middle') .text(this.options.xAxisLabel); } // gridlines in x axis function make_x_gridlines(x) { return axisBottom(x).ticks(this.options.gridTicks); } // gridlines in y axis function make_y_gridlines(y) { return axisLeft(y).ticks(this.options.gridTicks); } onResizeEvent() { this.onResize$.pipe(debounceTime(200)).subscribe(() => { const svgExist = select(this.container.nativeElement).select('svg'); if (svgExist) { svgExist.remove(); } this.render(); }); } }; BandComponent.ctorParameters = () => [ { type: ElementRef }, { type: D3Service } ]; __decorate([ Input() ], BandComponent.prototype, "data", void 0); __decorate([ Input() ], BandComponent.prototype, "labels", void 0); __decorate([ Input() ], BandComponent.prototype, "options", void 0); __decorate([ HostListener('window:resize') ], BandComponent.prototype, "onResize", null); BandComponent = __decorate([ Component({ selector: 'ng-band', template: "<div class=\"svg-container\"></div>\n", encapsulation: ViewEncapsulation.None, styles: [".svg-container{display:inline-block;position:relative;width:100%;height:100%;padding-bottom:100%;vertical-align:top;overflow:hidden}.svg-content{display:inline-block;position:absolute;top:0;left:0}.grid line{stroke:#d3d3d3;stroke-opacity:.4;shape-rendering:crispEdges}.grid path{stroke-width:0}.grid text{display:none}.area{fill:#4682b4}"] }) ], BandComponent); let BandModule = class BandModule { }; BandModule = __decorate([ NgModule({ declarations: [BandComponent], imports: [ CommonModule ], exports: [BandComponent], }) ], BandModule); let BarComponent = class BarComponent { constructor(container, d3Service) { this.container = container; this.d3Service = d3Service; this.data = []; this.labels = []; this.options = {}; this.graph = { xAxis: [], yAxis: [], xAxisPath: '', yAxisPath: '', rectanglesData: [], }; this.labelsAndData = []; this.parseTime = timeParse('%d-%b-%y'); this._options = { width: 879, height: 804, margin: { top: 50, right: 50, bottom: 50, left: 50 }, gridTicks: 0, }; this.viewBox = {}; this.onResize$ = new Subject(); } onResize() { this.onResize$.next(); } ngOnInit() { this.options = Object.assign({}, this._options, this.options); this.viewBox = { minX: -this.options.margin.left, minY: -10, width: this.options.width + this.options.margin.left + this.options.margin.right, height: this.options.height + this.options.margin.top, }; this.labelsAndData = this.combineLabelsDataToOne(); this.onResizeEvent(); this.render(); } render() { const currentWidth = parseInt(select(this.container.nativeElement).select('div').style('width'), 10); const currentHeight = parseInt(select(this.container.nativeElement).select('div').style('height'), 10); const width = this.options.width - this.options.margin.left - this.options.margin.right; const height = this.options.height - this.options.margin.top - this.options.margin.bottom; this.viewBox = { minX: -this.options.margin.left, minY: -10, width: this.options.width, height: this.options.height - this.options.margin.top, }; const svg = select(this.container.nativeElement) .select('div') .append('svg') .attr('width', currentWidth) .attr('height', currentHeight) .attr('viewBox', `${this.viewBox.minX} ${this.viewBox.minY} ${this.viewBox.width} ${this.viewBox.height}`) .classed('svg-content', true) .append('g'); const x = scaleBand().rangeRound([0, width]).padding(0.1).domain(this.labels); const y = scaleLinear().rangeRound([height, 0]).domain([ 0, Math.max(...this.data.map((d) => Number(d))) ]); const xAxis = (g) => g.call(axisBottom(x)) .attr('transform', 'translate(0,' + height + ')'); const yAxis = (g) => g.call(axisLeft(y)); // add the X gridlines svg.append('g') .attr('class', 'grid') .call(this.make_x_gridlines(x).tickSize(height) // .tickFormat('') ); // add the Y gridlines svg.append('g') .attr('class', 'grid') .call(this.make_y_gridlines(y).tickSize(-width) // .tickFormat('') ); svg.selectAll('.bar') .data(this.labelsAndData) .enter() .append('rect') .attr('class', 'bar') .attr('x', (d) => { return x(d.x); }) .attr('y', (d) => { return y(Number(d.y)); }) .attr('width', x.bandwidth()) .attr('height', (d) => { return height - y(Number(d.y)); }); const _xAxis = svg.append('g').call(xAxis); // text label for the x axis this.addLabelAxisX(svg, width, height); const _yAxis = svg.append('g').call(yAxis); // text label for the y axis this.addLabelAxisY(svg, height); this.removeAxisTicks(_xAxis); this.removeAxisTicks(_yAxis); this.changeAxisColor(_xAxis, axisConfig); this.changeAxisColor(_yAxis, axisConfig); } changeAxisColor(axis, config) { this.d3Service.changeAxisColor(axis, config); } removeAxisTicks(axis) { this.d3Service.removeAxisTicks(axis); } addLabelAxisY(svg, height) { svg.append('text') .attr('transform', 'rotate(0)') .attr('y', 0 - this.options.margin.top / 2) .attr('x', 0) .attr('dy', '1em') .style('text-anchor', 'start') .text(this.options.yAxisLabel); } addLabelAxisX(svg, width, height) { svg.append('text') .attr('transform', 'translate(' + width / 2 + ' ,' + (height + this.options.margin.top - 15) + ')') .style('text-anchor', 'middle') .text(this.options.xAxisLabel); } combineLabelsDataToOne() { const result = []; const N = this.data.length; for (let index = 0; index < N; index++) { result.push({ x: this.labels[index], y: this.data[index] }); } return result; } // gridlines in x axis function make_x_gridlines(x) { return axisBottom(x).ticks(this.options.gridTicks); } // gridlines in y axis function make_y_gridlines(y) { return axisLeft(y).ticks(this.options.gridTicks); } onResizeEvent() { this.onResize$.pipe(debounceTime(200)).subscribe(() => { const svgExist = select(this.container.nativeElement).select('svg'); if (svgExist) { svgExist.remove(); } this.render(); }); } }; BarComponent.ctorParameters = () => [ { type: ElementRef }, { type: D3Service } ]; __decorate([ Input() ], BarComponent.prototype, "data", void 0); __decorate([ Input() ], BarComponent.prototype, "labels", void 0); __decorate([ Input() ], BarComponent.prototype, "options", void 0); __decorate([ HostListener('window:resize') ], BarComponent.prototype, "onResize", null); BarComponent = __decorate([ Component({ selector: 'ng-bar', template: "\n<div class=\"svg-container\"></div>\n", encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".svg-container{display:inline-block;position:relative;width:100%;height:100%;padding-bottom:100%;vertical-align:top;overflow:hidden}.svg-content{display:inline-block;position:absolute;top:0;left:0}.grid line{stroke:#d3d3d3;stroke-opacity:.4;shape-rendering:crispEdges}.grid path{stroke-width:0}.grid text{display:none}.bar{fill:#4682b4}.bar:hover{fill:brown}"] }) ], BarComponent); let BarService = class BarService { constructor() { } }; BarService = __decorate([ Injectable() ], BarService); let BarModule = class BarModule { }; BarModule = __decorate([ NgModule({ declarations: [BarComponent], imports: [ CommonModule ], providers: [ BarService ], exports: [BarComponent] }) ], BarModule); let LineComponent = class LineComponent { constructor(container, d3Service) { this.container = container; this.d3Service = d3Service; this.data = []; this.labels = []; this.options = {}; this._options = { width: 879, height: 804, margin: { top: 50, right: 50, bottom: 50, left: 50 }, gridTicks: 0, yAxisLabel: '', xAxisLabel: '', timeParser: axisConfig.xAxisTimeParser, timeFormat: axisConfig.xAxisTimeFormat, xAxisTicks: axisConfig.xAxisTicks, }; this.parseTime = timeParse(this.options.timeParser); this.formatTime = timeFormat(this.options.timeFormat); this.viewBox = {}; this.labelsAndData = []; this.AxisDataX = []; this.onResize$ = new Subject(); } onResize() { this.onResize$.next(); } ngOnInit() { this.options = Object.assign({}, this._options, this.options); this.viewBox = this.d3Service.getViewBoxDefault(this.options); this.parseTime = timeParse(this.options.timeParser); this.formatTime = timeFormat(this.options.timeFormat); this.labels = this.labels.map(d => this.parseTime(d)); this.labelsAndData = this.combineLabelsDataToOne(); this.onResizeEvent(); this.render(); } render() { const currentWidth = parseInt(select(this.container.nativeElement).select('div').style('width'), 10); const currentHeight = parseInt(select(this.container.nativeElement).select('div').style('height'), 10); const width = this.options.width - this.options.margin.left - this.options.margin.right; const height = this.options.height - this.options.margin.top - this.options.margin.bottom; this.viewBox = { minX: -this.options.margin.left, minY: -10, width: this.options.width, height: this.options.height - this.options.margin.top, }; const svg = select(this.container.nativeElement) .select('div') .append('svg') .attr('width', currentWidth) .attr('height', currentHeight) .attr('viewBox', `${this.viewBox.minX} ${this.viewBox.minY} ${this.viewBox.width} ${this.viewBox.height}`) .classed('svg-content', true) .append('g'); const x = scaleTime().range([0, width]); const y = scaleLinear().range([height, 0]).nice(); const valueline = line().x((d) => x(d.x)).y((d) => y(d.y)); x.domain(extent(this.labels, (d) => (d))); y.domain([0, max(this.data, (d) => d)]); // add the X gridlines svg.append('g') .attr('class', 'grid') .call(this.make_x_gridlines(x).tickSize(height) // .tickFormat('') ); // add the Y gridlines svg.append('g') .attr('class', 'grid') .call(this.make_y_gridlines(y).tickSize(-width) // .tickFormat('') ); svg.append('path') .datum(this.labelsAndData) .attr('class', 'line') .attr('d', valueline); // add the X Axis const xAxis = this.d3Service.getXaxisTime(svg, height, x, this.options.timeFormat, this.options.xAxisTicks); // text label for the x axis this.addLabelAxisX(svg, width, height); // add the Y Axis const yAxis = svg.append('g').call(axisLeft(y)); // text label for the y axis this.addLabelAxisY(svg, height); this.removeAxisTicks(xAxis); this.removeAxisTicks(yAxis); this.changeAxisColor(xAxis, axisConfig); this.changeAxisColor(yAxis, axisConfig); this.addDots(svg, x, y); } addDots(svg, x, y) { const dotRadius = 3; const dotColor = '#4682b4'; // add tootlip const { tooltip, tooltipRect, tooltipText, tooltipConfig } = this.d3Service.addTooltip(this.container); svg.selectAll('dot') .data(this.labelsAndData) .enter() .append('circle') .attr('r', dotRadius) .attr('fill', dotColor) .attr('cx', (d) => { return x(d.x); }) .attr('cy', (d) => { return y(d.y); }) .on('mouseover', (d) => { this.onMouseOver(d, x, y, tooltip, tooltipRect, tooltipText); }) .on('mouseout', (d) => { this.onMouseOut(d, tooltip, tooltipText, tooltipConfig); }); } onMouseOver(d, xScale, yScale, tooltip, tooltipRect, tooltipText) { // show tooltip if (tooltip) { this.d3Service.showTooltip(d, xScale, yScale, tooltip, tooltipRect, tooltipText, this.formatTime); } } onMouseOut(d, tooltip, tooltipText, tooltipConfig) { // hide tooltip if (tooltip) { this.d3Service.hideTooltip(tooltipText, tooltip); } } changeAxisColor(axis, config) { this.d3Service.changeAxisColor(axis, config); } removeAxisTicks(axis) { this.d3Service.removeAxisTicks(axis); } addLabelAxisY(svg, height) { svg.append('text') .attr('transform', 'rotate(0)') .attr('y', 0 - this.options.margin.top / 2) .attr('x', 0) .attr('dy', '1em') .style('text-anchor', 'start') .text(this.options.yAxisLabel); } addLabelAxisX(svg, width, height) { svg.append('text') .attr('transform', 'translate(' + width / 2 + ' ,' + (height + this.options.margin.top - 15) + ')') .style('text-anchor', 'middle') .text(this.options.xAxisLabel); } combineLabelsDataToOne() { const result = []; const N = this.data.length; for (let index = 0; index < N; index++) { result.push({ x: this.labels[index], y: this.data[index] }); } return result; } // gridlines in x axis function make_x_gridlines(x) { return axisBottom(x).ticks(this.options.gridTicks); } // gridlines in y axis function make_y_gridlines(y) { return axisLeft(y).ticks(this.options.gridTicks); } onResizeEvent() { this.onResize$.pipe(debounceTime(200)).subscribe(() => { const svgExist = select(this.container.nativeElement).select('svg'); if (svgExist) { svgExist.remove(); } this.render(); }); } }; LineComponent.ctorParameters = () => [ { type: ElementRef }, { type: D3Service } ]; __decorate([ Input() ], LineComponent.prototype, "data", void 0); __decorate([ Input() ], LineComponent.prototype, "labels", void 0); __decorate([ Input() ], LineComponent.prototype, "options", void 0); __decorate([ HostListener('window:resize') ], LineComponent.prototype, "onResize", null); LineComponent = __decorate([ Component({ selector: 'ng-line', template: "<div class=\"svg-container\"></div>\n", encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".svg-container{display:inline-block;position:relative;width:100%;height:100%;padding-bottom:100%;vertical-align:top;overflow:hidden}.svg-content{display:inline-block;position:absolute;top:0;left:0}.grid line{stroke:#d3d3d3;stroke-opacity:.4;shape-rendering:crispEdges}.grid path{stroke-width:0}.grid text{display:none}.line{fill:none;stroke:#4682b4;stroke-width:2px}div.tooltip{position:absolute;text-align:center;min-width:60px;min-height:28px;padding:5px;font:12px sans-serif;background:#b0c4de;border:0;border-radius:5px;pointer-events:none;color:#000}"] }) ], LineComponent); let LineService = class LineService { constructor(config) { this.config = config; } showConfig() { console.log(this.config); } }; LineService.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: ['config',] }] } ]; LineService = __decorate([ Injectable(), __param(0, Inject('config')) ], LineService); var LineModule_1; let LineModule = LineModule_1 = class LineModule { static forRoot(config) { return { ngModule: LineModule_1, providers: [ LineService, { provide: 'config', useValue: config }, ] }; } }; LineModule = LineModule_1 = __decorate([ NgModule({ declarations: [LineComponent,], imports: [ CommonModule ], exports: [LineComponent], providers: [ LineService ] }) ], LineModule); let MultilineComponent = class MultilineComponent { constructor(container, d3Service) { this.container = container; this.d3Service = d3Service; this.data = []; this.labels = []; this.options = {}; this.labelsAndData = []; this.utcParse = utcParse('%Y-%m'); this.viewBox = {}; this._options = { width: 879, height: 804, yAxisLabel: '', xAxisLabel: '', margin: { top: 50, right: 50, bottom: 50, left: 50 }, timeParser: axisConfig.xAxisTimeParser, timeFormat: axisConfig.xAxisTimeFormat, xAxisTicks: axisConfig.xAxisTicks, }; this.parseTime = timeParse(this.options.timeParser); this.formatTime = timeFormat(this.options.timeFormat); this.onResize$ = new Subject(); } onResize() { this.onResize$.next(); } ngOnInit() { this.options = Object.assign({}, this._options, this.options); this.viewBox = { minX: -this.options.margin.left, minY: -25, width: this.options.width + this.options.margin.left + this.options.margin.right, height: this.options.height + this.options.margin.top, }; this.parseTime = timeParse(this.options.timeParser); this.formatTime = timeFormat(this.options.timeFormat); this.labels = this.formatData(); this.labelsAndData = this.combineLabelsDataToOne(); this.onResizeEvent(); this.render(); } formatData() { return this.labels.map(d => this.parseTime(d)); } combineLabelsDataToOne() { const result = []; const N = this.data.length; for (let index = 0; index < N; index++) { result.push({ x: this.labels, y: this.data[index] }); } return result; } render() { const currentWidth = parseInt(select(this.container.nativeElement).select('div').style('width'), 10); const currentHeight = parseInt(select(this.container.nativeElement).select('div').style('height'), 10); const width = this.options.width - this.options.margin.left - this.options.margin.right; const height = this.options.height - this.options.margin.top - this.options.margin.bottom; this.viewBox = { minX: -this.options.margin.left, minY: -10, width: this.options.width, height: this.options.height - this.options.margin.top, }; const svg = select(this.container.nativeElement) .select('div') .append('svg') .attr('width', currentWidth) .attr('height', currentHeight) .attr('viewBox', `${this.viewBox.minX} ${this.viewBox.minY} ${this.viewBox.width} ${this.viewBox.height}`) .classed('svg-content', true) .append('g'); const xDomain = this.getXdomain(); const x = scaleTime().domain(xDomain).range([0, width]); const y = scaleLinear() .domain([0, max(this.data, (d) => max(d.values))]) .range([height, 0]) .nice(); // const xAxis = (g) => // g.attr('transform', `translate(0,${height})`).call(d3.axisBottom(x)); const xAxis = this.d3Service.getXaxisTime(svg, height, x, this.options.timeFormat, this.options.xAxisTicks); const yAxis = (g) => g.call(axisLeft(y)); const line$1 = line() .defined((d) => !isNaN(d)) .x((d, i) => x(this.labels[i])) .y((d) => y(d)); // add the X gridlines svg.append('g') .attr('class', 'grid') .call(this.make_x_gridlines(x).tickSize(height) // .tickFormat('') ); // add the Y gridlines svg.append('g') .attr('class', 'grid') .call(this.make_y_gridlines(y).tickSize(-width) // .tickFormat('') ); const _yAxis = svg.append('g').call(yAxis); // text label for the x axis this.addLabelAxisX(svg, width, height); // text label for the y axis this.addLabelAxisY(svg, height); const path = svg.append('g') .attr('fill', 'none') .attr('stroke', 'steelblue') .attr('stroke-width', 1.5) .attr('stroke-linejoin', 'round') .attr('stroke-linecap', 'round') .selectAll('path') .data(this.data) .join('path') .style('mix-blend-mode', 'multiply') .attr('d', (d) => line$1(d.values)) .text('this is '); this.removeAxisTicks(xAxis); this.removeAxisTicks(_yAxis); this.changeAxisColor(xAxis, axisConfig); this.changeAxisColor(_yAxis, axisConfig); // TODO: comment in when issue #61 is fixed /* svg.call(hover, path, this); function hover(svg, path, _this) { if ('ontouchstart' in document) { svg.style('-webkit-tap-highlight-color', 'transparent') .on('touchmove', moved) .on('touchstart', entered) .on('touchend', left); } else { svg.on('mousemove', moved) .on('mouseenter', entered) .on('mouseleave', left); } const dot = svg.append('g').attr('display', 'none'); dot.append('circle').attr('r', 2.5); dot.append('text') .attr('font-family', 'sans-serif') .attr('font-size', 10) .attr('text-anchor', 'middle') .attr('y', -8); function moved() { d3.event.preventDefault(); const ym = y.invert(d3.event.layerY) as any; const xm = x.invert(d3.event.layerX) as any; const i1 = d3.bisectLeft(_this.labels, xm, 1); const i0 = i1 - 1; const i = xm - _this.labels[i0] > _this.labels[i1] - xm ? i1 : i0; // const s = d3.least(_this.data, d => Math.abs(d.values[i] - ym)); const s = _this.least(_this.data, d => Math.abs(d.values[i] - ym), i, ym); path.attr('stroke', d => d === s ? null : '#ddd') .filter(d => d === s) .raise(); dot.attr( 'transform', `translate(${x(_this.labels[i])},${y(s.values[i])})`); dot.select('text').text(s.name); } function entered() { path.style('mix-blend-mode', null).attr('stroke', '#ddd'); dot.attr('display', null); } function left() { path.style('mix-blend-mode', 'multiply').attr('stroke', null); dot.attr('display', 'none'); } } */ } changeAxisColor(axis, config) { this.d3Service.changeAxisColor(axis, config); } removeAxisTicks(axis) { this.d3Service.removeAxisTicks(axis); } addLabelAxisY(svg, height) { svg.append('text') .attr('transform', 'rotate(0)') .attr('y', 0 - this.options.margin.top / 2) .attr('x', 0) .attr('dy', '1em') .style('text-anchor', 'start') .text(this.options.yAxisLabel); } addLabelAxisX(svg, width, height) { svg.append('text') .attr('transform', 'translate(' + width / 2 + ' ,' + (height + this.options.margin.top - 15) + ')') .style('text-anchor', 'middle') .text(this.options.xAxisLabel); } getXdomain() { const domainExtent = extent(this.labels, (d) => d); return domainExtent.map((d) => new Date(d)); } least(arr, filterFun, pos, ym) { const tempValues = arr.map((d) => filterFun(d)); const minNum = Math.min(...tempValues); let graphHovered; let minimax = tempValues[0]; let minPos = 0; for (let i = 1; i < tempValues.length; i++) { const element = tempValues[i]; if (element >= minimax) { minPos = i; minimax = element; } } graphHovered = arr[minPos]; return graphHovered; } // gridlines in x axis function make_x_gridlines(x) { return axisBottom(x).ticks(this.options.gridTicks); } // gridlines in y axis function make_y_gridlines(y) { return axisLeft(y).ticks(this.options.gridTicks); } onResizeEvent() { this.onResize$.pipe(debounceTime(200)).subscribe(() => { const svgExist = select(this.container.nativeElement).select('svg'); if (svgExist) { svgExist.remove(); } this.render(); }); } }; MultilineComponent.ctorParameters = () => [ { type: ElementRef }, { type: D3Service } ]; __decorate([ Input() ], MultilineComponent.prototype, "data", void 0); __decorate([ Input() ], MultilineComponent.prototype, "labels", void 0); __decorate([ Input() ], MultilineComponent.prototype, "options", void 0); __decorate([ HostListener('window:resize') ], MultilineComponent.prototype, "onResize", null); MultilineComponent = __decorate([ Component({ selector: 'ng-multiline', template: "<div class=\"svg-container\"></div>\n", encapsulation: ViewEncapsulation.None, styles: [".svg-container{display:inline-block;position:relative;width:100%;height:100%;padding-bottom:100%;vertical-align:top;overflow:hidden}.svg-content{display:inline-block;position:absolute;top:0;left:0}.grid line{stroke:#d3d3d3;stroke-opacity:.4;shape-rendering:crispEdges}.grid path{stroke-width:0}.grid text{display:none}"] }) ], MultilineComponent); let MultilineModule = class MultilineModule { }; MultilineModule = __decorate([ NgModule({ declarations: [MultilineComponent], imports: [ CommonModule, ], exports: [MultilineComponent], }) ], MultilineModule); let PieComponent = class PieComponent { constructor(container) { this.container = container; this.labels = []; this.data = []; this.backgroundColors = schemeSet2; this.radius = 100; this.options = {}; this.color = this.interpolateColor(); // range [0,1] -> builtin range of colors. this.defaultSliceColor = 'steerblue'; this.labelsAndData = []; this.viewBox = {}; this._options = { width: 300, height: 300, margin: { top: 50, right: 50, bottom: 50, left: 50 }, }; this.onResize$ = new Subject(); } onResize() { this.onResize$.next(); } ngOnInit() { this.options = Object.assign({}, this._options, this.options); this.viewBox = { minX: -this.options.margin.left, minY: 0, width: Number(this.options.width) + Number(this.options.margin.left) + Number(this.options.margin.right), height: this.options.height, }; this.onBgdColorUndefined(); this.onResizeEvent(); this.render(); } onBgdColorUndefined() { if (this.backgroundColors.length === 0) { // TODO: check linter // for (let index = 0; index < this.data.length; index++) { // this.backgroundColors.push(this.defaultSliceColor); // } for (const iterator of this.data) { this.backgroundColors.push(this.defaultSliceColor); } } } render() { const currentWidth = parseInt(select(this.container.nativeElement).select('div').style('width'), 10); const currentHeight = parseInt(select(this.container.nativeElement).select('div').style('height'), 10); const radius = Math.min(this.options.width, this.options.height) / 2 - this.options.margin.top; const svg = select(this.container.nativeElement) .select('div') .append('svg') .attr('width', currentWidth) .attr('height', currentHeight) .attr('viewBox', `${this.viewBox.minX} ${this.viewBox.minY} ${this.viewBox.width} ${this.viewBox.height}`) .classed('svg-content', true) .append('g') .attr('transform', 'translate(' + this.options.width / 2 + ',' + this.options.height / 2 + ')'); const color = scaleOrdinal().domain(this.data).range(this.backgroundColors); const pie$1 = pie().value((d) => d.value); const pieData = pie$1(entries(this.data)); const arcGenerator = arc().innerRadius(0).outerRadius(radius); svg .selectAll('slices') .data(pieData) .enter() .append('path') .attr('d', arcGenerator) .attr('fill', (d) => this.backgroundColors[d.index]) .attr('stroke', 'black') .style('stroke-width', '2px') .style('opacity', 0.7); svg .selectAll('slices') .data(pieData) .enter() .append('text') .text((d) => this.labels[d.index]) .attr('transform', (d) => { return ('translate(' + arcGenerator.centroid({ startAngle: d.startAngle, endAngle: d.endAngle, }) + ')'); }) .style('text-anchor', 'middle') .style('font-size', 17); this.addLabelAxisX(svg, this.options.width, this.options.height); } addLabelAxisX(svg, width, height) { svg .append('text') .attr('transform', `translate(${0}, ${this.options.margin.top * 2.5})`) .style('text-anchor', 'middle') .text(this.options.xAxisLabel); } /** * range [0, 1] */ interpolateColor() { return interpolateCool; } combineLabelsDataToOne() { const result = []; const N = this.data.length; for (let index = 0; index < N; index++) { result.push({ x: this.labels[index], y: this.data[index] }); } return result; } onResizeEvent() { this.onResize$.pipe(debounceTime(200)).subscribe(() => { const svgExist = select(this.container.nativeElement).select('svg'); if (svgExist) { svgExist.remove(); } this.render(); }); } }; PieComponent.ctorParameters = () => [ { type: ElementRef } ]; __decorate([ Input() ], PieComponent.prototype, "labels", void 0); __decorate([ Input() ], PieComponent.prototype, "data", void 0); __decorate([ Input() ], PieComponent.prototype, "backgroundColors", void 0); __decorate([ Input() ], PieComponent.prototype, "radius", void 0); __decorate([ Input() ], PieComponent.prototype, "options", void 0); __decorate([ HostListener('window:resize') ], PieComponent.prototype, "onResize", null); PieComponent = __decorate([ Component({ selector: 'ng-pie', template: "<div class=\"svg-container\"></div>\n", encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".svg-container{display:inline-block;position:relative;width:100%;height:100%;padding-bottom:100%;vertical-align:top;overflow:hidden}.svg-content{display:inline-block;position:absolute;top:0;left:0}.grid line{stroke:#d3d3d3;stroke-opacity:.4;shape-rendering:crispEdges}.grid path{stroke-width:0}.grid text{display:none}svg{background-color:transparent!important}.slice text{font-size:16pt;font-family:Arial}"] }) ], PieComponent); let PieService = class PieService { constructor() { } }; PieService = __decorate([ Injectable() ], PieService); let PieModule = class PieModule { }; PieModule = __decorate([ NgModule({ declarations: [PieComponent], imports: [ CommonModule ], providers: [ PieService, ], exports: [PieComponent] }) ], PieModule); // TODO: comment in when issue #96 is done. /** * Generated bundle index. Do not edit. */ export { BandModule, BarModule, LineModule, MultilineModule, PieModule, BandComponent as ɵa, D3Service as ɵb, BarComponent as ɵc, BarService as ɵd, LineComponent as ɵe, LineService as ɵf, MultilineComponent as ɵg, PieComponent as ɵh, PieService as ɵi }; //# sourceMappingURL=ng-d3-graphs.js.map