Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating zoom behavior from v3 to v5

The problem i am facing is that i am not able to write the function following in my code because of version mismatch. Code is

var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 10])
.on("zoom", zoomed);

I have tried by this way

  const zoom = d3.zoom()
  .scaleExtent([1, 4])
  .x(this.xScale)
  .on('zoom', () => {})

But it does not work for me.

How to write same function in d3 version 5? I want to make line chart scrollable in x axis with y axis as fixed position using d3 version 5

This is my implementation Basic Code

    private createLineChart() {
    this.width = 2000 - this.margin.left - this.margin.right;
    this.height = 600 - this.margin.top - this.margin.bottom;

// X AXIS
this.xScale = d3.scaleBand()
  .domain(this.dataset[0].fluencyData.map((data) => {
    return new Date(data.date);
  }))
  .range([0, this.width]);

// Y AXIS
this.yScale = d3.scaleLinear()
  .domain([0, 110])
  .range([this.height, 0]);

// Line Generator
this.line = d3.line()
  .x((data) => this.xScale(new Date(data.date)))
  .y((data) => this.yScale(data.wcpm));
// .curve(d3.curveMonotoneX);

// Add SVG to Div
this.svg = d3.select('#displayChart').append('svg')
  .attr('preserveAspectRatio', 'xMinYMin meet')
  .attr(
    'viewBox',
    '0 0 ' +
    (this.width + this.margin.left + this.margin.right) +
    ' ' +
    (this.height + this.margin.top + this.margin.bottom))
  // .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 + ')');

// Define the div for the tooltip
this.toolTipDiv = d3.select('#displayChart').append('div')
  .attr('class', 'tooltip')
  .style('opacity', 0);

// Append XAXIS to the SVG
this.svg.append('g')
  .attr('class', 'xAxis')
  .attr('transform', 'translate(0,' + this.height + ')')
  .call(d3.axisBottom(this.xScale).tickSizeOuter(0).tickFormat(d3.timeFormat('%b %d')));

const zoom = d3.zoom()
  .scaleExtent([1, 4])
  .extent([100, 100], [this.width - 100, this.height - 100])
  .x(this.xScale)
  .on('zoom', () => {
    console.log(d3.event.transform);
    // this.svg.select('#displayChart').attr('d', this.line);
  });
this.svg.call(zoom);

// Append YAXIS to SVG
this.svg.append('g')
  .attr('class', 'yAxis')
  .call(d3.axisLeft(this.yScale).tickSize(-this.width)
  );

// Make a Path for Dataset
this.svg.append('path')
  .datum(this.dataset[0].fluencyData)
  .attr('class', 'line')
  .attr('d', this.line)
  .attr('transform', 'translate(' + this.margin.left + ',0)');

// Text Heading of DATE in chart
this.svg.append('text')
  .attr('transform', 'translate(' + (-20) + ',' + (this.height + 13) + ')')
  .attr('dy', '.35em')
  .attr('class', ' xAxis')
  .text('Date');
 }
 }

Error I am getting is

LineChartComponent_Host.ngfactory.js? [sm]:1 ERROR TypeError: d3__WEBPACK_IMPORTED_MODULE_2__.zoom(...).scaleExtent(...).x is not a function
    at LineChartComponent.push../src/app/line-chart/line-chart.component.ts
like image 653
JAYDIP HIRAPARA Avatar asked Mar 27 '19 12:03

JAYDIP HIRAPARA


1 Answers

With d3v3 and before, the zoom could track a scale's state. From the documentation, scale.x(): "Specifies an x-scale whose domain should be automatically adjusted when zooming." (docs). This modifies the original scale.

D3v4+ does not have zoom.x or zoom.y methods.

With d3v4+, the zoom does not track or modifiy a d3 scale's state. Infact, for d3v4+, the zoom behavior doesn't even track the current zoom state: "Zoom behaviors no longer store the active zoom transform (i.e., the visible region; the scale and translate) internally. The zoom transform is now stored on any elements to which the zoom behavior has been applied.(change log)".

As part of this, and more importantly, "Zoom behaviors are no longer dependent on scales, but you can use transform.rescaleX, transform.rescaleY, transform.invertX or transform.invertY to transform a scale’s domain(change log)".

So rather than have the zoom update the d3 scale, we need to do this ourselves. The most common way this is done is through a reference scale, which remains unchanged, and a scale to which we apply the zoom transform:

var zoom = d3.zoom()
  .on("zoom",zoomed)

var x = d3.scaleLinear()....   // working scale
var x2 = x.copy(); // reference scale.

function zoomed() {
  x = d3.event.transform.rescaleX(x2) // update the working scale.
  // do something...
}

So, something like this:

var x = d3.scale.linear()
  .domain([0,1])
  .range([0,500]);

var zoom = d3.behavior.zoom()
.x(x)
.scaleExtent([1, 10])
.on("zoom", zoomed);

var svg = d3.select("svg")
  .call(zoom);
  
var axis = d3.svg.axis()
   .orient("bottom")
   .scale(x);
  
var axisG = svg.append("g")
    .attr("transform", "translate(0,30)")
    .call(axis);
  
function zoomed() {
   axisG.call(axis);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

<svg width="500" height="200"></svg>

Becomes something like that:

var x = d3.scaleLinear()
  .domain([0,1])
  .range([0,500]);
var x2 = x.copy(); // reference.

var zoom = d3.zoom()
  .scaleExtent([1, 10])
  .on("zoom", zoomed);

var svg = d3.select("svg")
  .call(zoom);
  
var axis = d3.axisBottom().scale(x)
  
var axisG = svg.append("g")
    .attr("transform", "translate(0,30)")
    .call(axis);
  
function zoomed() {
   x = d3.event.transform.rescaleX(x2)
   axis.scale(x);
   axisG.call(axis);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<svg width="500" height="200"></svg>

Note that d3.event.transform.rescaleX is for continuous scales - you have an ordinal band scale, so we'll need to use a slightly modified approach for band and/or point scales:

var x = d3.scaleBand()
  .domain(d3.range(10).map(function(d) { return d/10; }))
  .range([0,500]);
var x2 = x.copy(); // reference.

var zoom = d3.zoom()
  .scaleExtent([1, 10])
  .on("zoom", zoomed);

var svg = d3.select("svg")
  .call(zoom);
  
var axis = d3.axisBottom().scale(x)
  
var axisG = svg.append("g")
    .attr("transform", "translate(0,30)")
    .call(axis);
  
function zoomed() {
   // Rescale the range of x using the reference range of x2.
   x.range(x2.range().map(function(d) {
     return d3.event.transform.applyX(d);
   }))
   axisG.call(axis);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<svg width="500" height="200"></svg>

This is band/point scale solution is based on this issue and Bostock's proposed solution to it

like image 170
Andrew Reid Avatar answered Oct 17 '22 07:10

Andrew Reid