grindarius
grindarius

Reputation: 119

Error: Type "SVGAnimatedLength' has no call signatures" when calling from class

I am working on Vue project with D3.js, TypeScript and Vue Property Decorator. I wanted to draw a heatmap but I get an error at when I wanted to call x() or y() function in return for a computed position of each cell in the heatmap. It throws the error

Type "SVGAnimatedLength" has no call signatures.

This is how I init my chart variables

  private svg: d3.Selection<SVGGElement, any, HTMLElement, any>
  private x: d3.ScaleBand<string>
  private xAxis: d3.Selection<SVGGElement, any, HTMLElement, any>
  private y: d3.ScaleBand<string>
  private yAxis: d3.Selection<SVGGElement, any, HTMLElement, any>

this is where it causes error

this.svg.selectAll()
  .data(this.getData().filter((datum: HeatmapData) => this.betweenTwoDates(datum)))
  .enter()
  .append('rect')
    .attr('x', function(d: HeatmapData) {
      return this.x(d.dayNumber)
    })
    .attr('y', function(d: HeatmapData) {
      return this.y(d.timeOfDay)
    })
    .attr('cx', 1)
    .attr('cy', 1)
    .attr('width', this.x.bandwidth())
    .attr('height', this.y.bandwidth())

at

.attr('x', function(d: HeatmapData) {
  return this.x(d.dayNumber)
})

the error happens at return this.x(d.dayNumber) stating Type "SVGAnimatedLength" has no call signatures. Same goes for .attr('y', ...) too.

The this at this.x() has a type of SVGRectElement.

Upvotes: 1

Views: 273

Answers (2)

Rodrigo Divino
Rodrigo Divino

Reputation: 1931

The this has a type of SVGRectElement because it is inside a regular anonymous function. In D3, methods for manipulating nodes replace the this context with the own DOM Element that is being manipulated, in your case a <rect>. The <rect> nodes don't have x or y methods, hence the type error.

Replacing the anonymous functions with arrow functions preserves the this from the outside:

.attr('x', (d: HeatmapData) => {
          return this.x(d.dayNumber)
 })
 .attr('y', (d: HeatmapData) => {
          return this.y(d.timeOfDay)
 })

Now, the this inside the function is the same this from the outside, which in your case is the class that contain svg, x, xAxis, y and yAxis.

Upvotes: 1

altocumulus
altocumulus

Reputation: 21578

This is a nice example of when to actually use arrow functions in favor of regular functions! Because regular functions—like in your code—establish their own this scope you no longer have access to the this scope you are interested in, namely the scope of your surroungding class.

Many D3 methods are called with this set to the current DOM element:

with this as the current DOM element (nodes[i])

To be able to use your class instance's methods by referring to them using this you can just use an arrow function which does not come with its own scope but captures the scope of its surrounding context, i.e. your class/instance. Your methods should therefore look like this:

.attr('x', d => this.x(d.dayNumber))

Upvotes: 3

Related Questions