本文介绍了Angular 2/Typescript函数在运行时出现“未定义不是函数"错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个有角度的(angular2/angular4不是angularJS)组件,该组件创建了一个D3.js导航栏.我没有与其他D3图表有任何问题,它最初呈现就很好,但是在执行画笔"时尝试访问类变量之一时出现运行时错误. (旁注:有关常规Javascript D3画笔实现的示例,请参见此: https://bl.ocks. org/mbostock/34f08d5e11952a80609169b7917d4172 )

该错误似乎与Angular/Typescript有关,而不与D3有关:尝试访问下面代码(靠近底部)中"brushed()"函数中的"this.x"时,未定义不是函数".

任何人都可以解释我需要做什么才能访问"brushed()"函数中的"this.x"和"this.x.invert"

import { Component, OnInit, OnChanges, ViewChild, ElementRef, Input, ViewEncapsulation } from '@angular/core';
import * as d3 from 'd3';
import {StockData} from "../../dataServices/stockData";

@Component({
  selector: 'app-navchart',
  templateUrl: './navchart.component.html',
  styleUrls: ['./navchart.component.css'],
  encapsulation: ViewEncapsulation.None
})
export class NavchartComponent implements OnInit, OnChanges {
  @ViewChild('chart') public chartContainer: ElementRef;
  @Input() public stockdata: StockData;

  public margin: any = { top: 20, bottom: 20, left: 20, right: 20};
  public width : number;
  public height: number;
  public svg: any;
  public g: any;
  public chart: any;
  public x: any;
  public y: any;
  public navline: any;
  public navarea: any;
  public data: any;
  public brush: any;


  constructor() { }

  ngOnInit() {
    console.log("Inside the charting - updating the data");
    if (this.stockdata) {
      //console.log(JSON.stringify(this.stockdata));

      this.data = this.stockdata.stocklist;

      setTimeout(() => {
        this.initChart();
        this.drawAxis();
        this.drawRange();
      }, 500);
    }
  }

  ngOnChanges() {

  }

  public initChart(): void {
    let element = this.chartContainer.nativeElement;
    this.width = element.offsetWidth - this.margin.left - this.margin.right;
    this.height = element.offsetHeight - this.margin.top - this.margin.bottom;

    this.svg = d3.select(element).append('svg')
      .attr('width', element.offsetWidth)
      .attr('height', element.offsetHeight);

    this.g = this.svg.append('g')
      .attr("transform", "translate(" + this.margin.left + "," + this.margin.top +")");


    // x and y scale functions - called every time a value needs converted to pixel location
    //this will need moved to a "redraw" function when adjustments to overall chart size are allowed
    this.x = d3.scaleTime()
            .range([0, this.width]);

    this.x.domain(d3.extent(this.data, (d: any) => new Date(d.date )));

    this.y = d3.scaleLinear()
      .range([this.height, 0]);


      //sets the limits of x and y data.
      // this will need to be moved to a redraw when changes to dataset ranges are allowed
      this.y.domain([
        d3.min(this.data, (d: any) => d.close),
        d3.max(this.data, (d: any) => d.close)
      ]);
      console.log ("Min = " + d3.min(this.data, (d: any) => d.close) );

      // line drawing functions
      this.navline = d3.line()
        .curve(d3.curveBasis)
        .x( (d: any) => this.x(new Date(d.date)) )
        .y( (d: any) => this.y(d.close) );

      this.navarea = d3.area()
        .curve(d3.curveBasis)
        .x( (d: any) => this.x(new Date(d.date)) )
        .y1( (d: any) => this.y(d.close) )
        .y0( (d: any) => this.height );



      this.g.append("g")
        .attr("class", "brush")
        .call(d3.brushX().on("end",  this.brushed));
  }


 /* Error is in this function.  It cannot find "this.x" from the class,
  * and gives an undefined error.
  *  Right now the function is just setting debug content, but when
  * this.x is working, I will add .invert() to it to get the original
  * date values associated with the pixel location on the x-axis.
  */
  public brushed(): void {
    console.log(JSON.stringify(d3.event.selection));
    //returns proper [x0,x1] pixel values such as [102,500] on a svg 800 pixels wide.

    let dat: any = d3.event.selection.map( this.x);
    //let dat: any = d3.event.selection.map( this.x.invert) also fails with "invert does not exist on undefined"
    console.log(JSON.stringify(dat));

    //The error appears to be because it can't find this.x, even though that is declared and works in
    // other sections of the same class.
  }

  //draw x and y axes
  public drawAxis(): void {
      this.g.append("g")
        .attr("class", "axis axis--x")
        .attr("transform", "translate(0," + this.height + ")")
        .call(d3.axisBottom(this.x));

    }

    public drawRange(): void {

      this.g.append("path")
        .attr("class", "area")
        .attr("d",  this.navarea(this.data) );


      this.g.append("path")
        .attr("class", "line")
        .attr("d",  this.navline(this.data) );
    }


}

如果重要,则数据只是格式为以下每日库存条目的数组:

[{日期,开盘价,最高价,最低价,收盘价... ...]{"date":"2017-06-07 13:02:00","open":"72.6350","high":"72.7700","low":"71.9500","close":"72.0800", "volume":"9247460","adjClose":"72.6350"}

D3喜欢使用"d"引用

解决方案

注意,这个问题实际上是.我通常会以这种方式关闭它,但是该问题并不能解释发生了什么,所以我在这里进行拍摄.


您正在将回调函数传递给.call.在该回调d3中,已更改了变量this所引用的内容.它不再引用您的类,而是引用选择中的g节点.

因此,您有两种选择.一种,将其包装在ES6粗箭头功能中:

.call( d3.brushX().on("end", () => this.brushed() ) );

这会围绕this创建一个闭包,并保留对您的类的引用.

或者两个,使用.bind保留它. .bind强制保留此内容.

.call( d3.brushX().on("end", this.brushed.bind(this) ) );

以下是有关此主题的一些精读./p>

I am building an angular (angular2/angular4 not angularJS) component which creates a D3.js Navigation bar. I have not had any issues with other D3 charts, and it renders just fine initially, but I get a runtime error when trying to access one of the class variables when a "brush" is executed. (side note: see this for an example of general Javascript D3 brush implementation: https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172 )

The error appears to be related to Angular/Typescript and not D3 though:'undefined is not a function' when trying to access "this.x" in the "brushed()" function in the code below (near the bottom).

Can anyone explain what I need to do to be able to access "this.x" and "this.x.invert" in the "brushed()" function

import { Component, OnInit, OnChanges, ViewChild, ElementRef, Input, ViewEncapsulation } from '@angular/core';
import * as d3 from 'd3';
import {StockData} from "../../dataServices/stockData";

@Component({
  selector: 'app-navchart',
  templateUrl: './navchart.component.html',
  styleUrls: ['./navchart.component.css'],
  encapsulation: ViewEncapsulation.None
})
export class NavchartComponent implements OnInit, OnChanges {
  @ViewChild('chart') public chartContainer: ElementRef;
  @Input() public stockdata: StockData;

  public margin: any = { top: 20, bottom: 20, left: 20, right: 20};
  public width : number;
  public height: number;
  public svg: any;
  public g: any;
  public chart: any;
  public x: any;
  public y: any;
  public navline: any;
  public navarea: any;
  public data: any;
  public brush: any;


  constructor() { }

  ngOnInit() {
    console.log("Inside the charting - updating the data");
    if (this.stockdata) {
      //console.log(JSON.stringify(this.stockdata));

      this.data = this.stockdata.stocklist;

      setTimeout(() => {
        this.initChart();
        this.drawAxis();
        this.drawRange();
      }, 500);
    }
  }

  ngOnChanges() {

  }

  public initChart(): void {
    let element = this.chartContainer.nativeElement;
    this.width = element.offsetWidth - this.margin.left - this.margin.right;
    this.height = element.offsetHeight - this.margin.top - this.margin.bottom;

    this.svg = d3.select(element).append('svg')
      .attr('width', element.offsetWidth)
      .attr('height', element.offsetHeight);

    this.g = this.svg.append('g')
      .attr("transform", "translate(" + this.margin.left + "," + this.margin.top +")");


    // x and y scale functions - called every time a value needs converted to pixel location
    //this will need moved to a "redraw" function when adjustments to overall chart size are allowed
    this.x = d3.scaleTime()
            .range([0, this.width]);

    this.x.domain(d3.extent(this.data, (d: any) => new Date(d.date )));

    this.y = d3.scaleLinear()
      .range([this.height, 0]);


      //sets the limits of x and y data.
      // this will need to be moved to a redraw when changes to dataset ranges are allowed
      this.y.domain([
        d3.min(this.data, (d: any) => d.close),
        d3.max(this.data, (d: any) => d.close)
      ]);
      console.log ("Min = " + d3.min(this.data, (d: any) => d.close) );

      // line drawing functions
      this.navline = d3.line()
        .curve(d3.curveBasis)
        .x( (d: any) => this.x(new Date(d.date)) )
        .y( (d: any) => this.y(d.close) );

      this.navarea = d3.area()
        .curve(d3.curveBasis)
        .x( (d: any) => this.x(new Date(d.date)) )
        .y1( (d: any) => this.y(d.close) )
        .y0( (d: any) => this.height );



      this.g.append("g")
        .attr("class", "brush")
        .call(d3.brushX().on("end",  this.brushed));
  }


 /* Error is in this function.  It cannot find "this.x" from the class,
  * and gives an undefined error.
  *  Right now the function is just setting debug content, but when
  * this.x is working, I will add .invert() to it to get the original
  * date values associated with the pixel location on the x-axis.
  */
  public brushed(): void {
    console.log(JSON.stringify(d3.event.selection));
    //returns proper [x0,x1] pixel values such as [102,500] on a svg 800 pixels wide.

    let dat: any = d3.event.selection.map( this.x);
    //let dat: any = d3.event.selection.map( this.x.invert) also fails with "invert does not exist on undefined"
    console.log(JSON.stringify(dat));

    //The error appears to be because it can't find this.x, even though that is declared and works in
    // other sections of the same class.
  }

  //draw x and y axes
  public drawAxis(): void {
      this.g.append("g")
        .attr("class", "axis axis--x")
        .attr("transform", "translate(0," + this.height + ")")
        .call(d3.axisBottom(this.x));

    }

    public drawRange(): void {

      this.g.append("path")
        .attr("class", "area")
        .attr("d",  this.navarea(this.data) );


      this.g.append("path")
        .attr("class", "line")
        .attr("d",  this.navline(this.data) );
    }


}

If it is important, the data is simply an array of daily stock entries in the format:

[ {date, open, high, low, close} ...]{"date":"2017-06-07 13:02:00","open":"72.6350","high":"72.7700","low":"71.9500","close":"72.0800","volume":"9247460","adjClose":"72.6350"}

D3 likes to use the "d" reference

解决方案

Note, this question is really a duplicate of this one. I'd usually close it as such but that question doesn't explain what's going on, so I'll take a shot here.


You are passing a callback function to .call. In that callback d3 has changed what the variable this is referencing. It is no longer referencing your class but rather the g node from the selection.

So you have two choices. One, wrap it in an ES6 fat arrow function:

.call( d3.brushX().on("end", () => this.brushed() ) );

This creates a closure around this and preserves the reference to your class.

Or, two, use .bind to preserve this. .bind forces the preservation of this.

.call( d3.brushX().on("end", this.brushed.bind(this) ) );

Here's some excellent reading on this subject.

这篇关于Angular 2/Typescript函数在运行时出现“未定义不是函数"错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-31 18:22