我正在尝试在Ionic2应用程序内的一页上有两个(或更多(相似的图表。我使用 d3-ng2-service 来包装 Angular2 的 d3 类型。我的问题是:当我尝试将两个图形分别放置在两个不同的div 元素中时,两个元素的绘制都失败了。当我选择页面中的第一个div 时,第二个图表覆盖了第一个图表,但它确实被绘制了。
有没有一种聪明的方法可以放置图表而不是一个图表?这些示例总是为外部容器提供一个唯一的 id,这也是我尝试做的事情:
import { Component, Input, OnInit, ElementRef } from '@angular/core';
import { D3Service, D3, Selection, ScaleLinear, ScaleTime, Axis, Line } from 'd3-ng2-service'; // <-- import the D3 Service, the type alias for the d3 variable and the Selection interface
@Component({
selector: 'd3-test-app',
templateUrl: 'd3-test-app.html',
providers: [D3Service],
})
export class D3TestAppComponent {
//Time is on x-axis, value is on y-axis
@Input('timeSeries') timeSeries: Array<{isoDate: string | Date | number | {valueOf(): number}, value: number}>;
@Input('ref') ref: string;
/* the size input defines, how the component is drawn */
@Input('size') size: string;
private d3: D3;
private margin: {top: number, right: number, bottom: number, left: number};
private width: number;
private height: number;
private d3ParentElement: Selection<any, any, any, any>; // <-- Use the Selection interface (very basic here for illustration only)
constructor(element: ElementRef,
d3Service: D3Service) { // <-- pass the D3 Service into the constructor
this.d3 = d3Service.getD3(); // <-- obtain the d3 object from the D3 Service
this.d3ParentElement = element.nativeElement;
}
ngOnInit() {
let x: ScaleTime<number, number>;
let y: ScaleLinear<number, number>;
let minDate: number;
let maxDate: number;
let minValue: number = 0;
let maxValue: number;
// set the dimensions and margins of the graph
switch (this.size) {
case "large":
this.margin = {top: 20, right: 20, bottom: 30, left: 50};
this.width = 640 - this.margin.left - this.margin.right;
this.height = 480 - this.margin.top - this.margin.bottom;
break;
case "medium":
this.margin = {top: 20, right: 0, bottom: 20, left: 20};
//golden ratio
this.width = 420 - this.margin.left - this.margin.right;
this.height = 260 - this.margin.top - this.margin.bottom;
break;
case "small":
this.margin = {top: 2, right: 2, bottom: 3, left: 5};
this.width = 120 - this.margin.left - this.margin.right;
this.height = 80 - this.margin.top - this.margin.bottom;
break;
default:
this.margin = {top: 20, right: 20, bottom: 30, left: 50};
this.width = 640 - this.margin.left - this.margin.right;
this.height = 480 - this.margin.top - this.margin.bottom;
}
// ...
if (this.d3ParentElement !== null) {
let d3 = this.d3; // <-- for convenience use a block scope variable
//THIS FAILS...
let selector: string = '#' + this.ref + ' .graphContainer';
console.log(selector);
let svg = d3.select( selector).append("svg")
.attr("width", this.width + this.margin.left + this.margin.right)
.attr("height", this.height + this.margin.top + this.margin.bottom)
.append("g")
.attr("transform",
"translate(" + this.margin.left + "," + this.margin.top + ")");
this.timeSeries.forEach((d) => {
d.isoDate = +d3.isoParse(d.isoDate as string);
d.value = +d.value;
if (minDate == null || minDate >= d.isoDate) {
minDate = d.isoDate as number;
}
if (maxDate == null || maxDate <= d.isoDate) {
maxDate = d.isoDate as number;
}
// if (minValue == null || minValue >= d.value) {
// minValue = d.value as number;
// }
if (maxValue == null || maxValue <= d.value) {
maxValue = d.value as number;
}
});
// TODO magic numbers to real min max
x = d3.scaleTime().domain([minDate, maxDate]).range([0,this.width]);
y = d3.scaleLinear().domain([0, maxValue]).range([this.height, 0]);
let xAxis: Axis<number | Date | {valueOf() : number;}> = d3.axisBottom(x);
let yAxis: Axis<number | {valueOf(): number;}> = d3.axisLeft(y);
let valueLine: Line<{isoDate: number; value: number}> = d3.line<{ isoDate: number; value: number }>()
.x(function (d) { return x(d.isoDate)})
.y(function (d) { return y(d.value)});
// Add the valueline path.
svg.append("path")
.data([this.timeSeries as {isoDate: number, value: number}[]])
.attr("class", "line")
.attr("d", valueLine);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + this.height + ")")
.call(xAxis)
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
}
}
myParser() : (string) => Date {
return this.d3.utcParse("%Y-%m-%dT%H:%M:%S.%LZ");
}
}
该 HTML:
<div class='graphContainer'>
</div>
使用自定义组件的 HTML 文件:
<ion-header>
<ion-navbar #dashboardNav>
<ion-title>Dashboard</ion-title>
<button ion-button menuToggle="favMenu" right>
<ion-icon name="menu"></ion-icon>
</button>
</ion-navbar>
</ion-header>
<ion-content>
<ion-item *ngFor="let entry of dashboard">
{{ entry.name }}
<d3-test-app [id]='entry.name' [timeSeries]='entry.timeSeries' [ref]='entry.name' size='medium'></d3-test-app>
</ion-item>
</ion-content>
很难在没有看到堆栈跟踪的情况下进行调试,但由于您选择元素的方式,这看起来失败了。我的回答将基于这一假设。
当您必须在 DOM 内部查找特定元素并且确定只有一个元素具有该 ID 时,按 ID 查询很方便。由于您在 Angular 元素中,因此您已经拥有了所需的引用,它是元素本身,无需动态创建 ID 引用。
我根本不是 ng2 的专家,但看看如何在 Angular 中选择原始元素并为框架选择最佳方法。假设你选择类似这个答案上显示的例子:
constructor(public element: ElementRef) {
this.element.nativeElement // <- your direct element reference
}
注意 - 看起来有多种方法可以实现这一点......不确定这是最好/正确的,但无论如何目标都是获得参考
然后只需通过 D3 dsl 选择它,就像您已经通过传递原始引用所做的那样
// At this point this.element.nativeElement
// should contain the raw element reference
if (this.d3ParentElement !== null) {
const { d3, element } = this; // <-- for convenience use block scope variables
const svg = d3.select(element.nativeElement).append("svg")...