尝试在一个Ionic2页面中放置两个d3线图

时间:2017-03-19 21:06:11

标签: angularjs typescript d3.js ionic-framework ionic2

我试图在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>

1 个答案:

答案 0 :(得分:0)

在没有看到堆栈跟踪的情况下难以调试,但由于您选择元素的方式,它看起来很失败。我的回答将基于这个假设。

当您需要查看特定元素的DOM内部以及确定只有一个具有该ID的元素时,通过ID查询非常方便。由于您位于Angular元素中,因此您已经拥有了所需的引用,它就是元素本身,无需动态创建ID引用。

我根本不是ng2的专家,但请看看如何to select the raw element in Angular并为框架选择最佳方法。假设您选择the example shown on this answer

  constructor(public element: ElementRef) {
    this.element.nativeElement // <- your direct element reference 
  }

NB - 看起来有各种各样的方式来实现这一目标...不确定这是最好的/正确的,但目标是无论如何得到参考

然后只需通过传递原始参考

就可以通过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")...