I created a scatter plot using D3.js.I would like to add the functionality to zoom and pan.
I have declare zoom behaviour for scatter plot.Try to use it in svg element.
But it throws error says argument of type zoom behavior is not assignable to parameter of type selection:selection SVGElement
I am just a beginner with using D3.Can anyone help or have any suggestions?
var zoom = d3.zoom()
.scaleExtent([0.3, 2])
.on("zoom", function () {
svg.attr("transform", d3.event.transform)
});
var svg = d3.select(component).append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
Welcome to Stack Overflow.
You need to provide a function to the onzoom handler
.on("zoom", function () {
svg.attr("transform", d3.event.transform)
});
should look more like this, where you are calling your own function and passing it the transform as a parameter
d3.select(context.canvas).call(d3.zoom()
.scaleExtent([1, 8])
.on("zoom", () => zoomed(d3.event.transform)));
The zoomed function could be this:
function zoomed(transform) {
context.save();
context.clearRect(0, 0, width, height);
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);
context.beginPath();
for (const [x, y] of data) {
context.moveTo(x + r, y);
context.arc(x, y, r, 0, 2 * Math.PI);
}
context.fill();
context.restore();
}
These snippets were taken from this example:
https://observablehq.com/#d3/zoom-canvas
Related
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
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
I do not know why wheelDelta not working . it says wheeldelta is not a function. I want to control zooming level on scroll up and down of mouse wheel. if I can defined delta value then I can define how much zoom will happen on a single tick rotation of scroll. the default behavior is too fast. I am using this reference
https://github.com/d3/d3-zoom/blob/master/README.md#_zoom
svg = d3.select("#treeSection").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.call(d3.zoom()
.wheelDelta([2])
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed));
g = svg.append("g")
.attr("transform", "translate(" +
margin.left + "," + margin.top + ")");
function zoomed() {
g.attr("transform", d3.event.transform);
}
Define the wheelDelta function
Make a wheel delta function
function myDelta() {
return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1) / 1500;
}
You may increase the constant 1500 to any number of your choice for regulating the delta.
Now in zoom define the wheelDelta like this:
var zoom = d3.zoom()
.scaleExtent([1, 32])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.wheelDelta(myDelta)//your function
.on("zoom", zoomed);
Reference here
Working code here
I define a zoom function:
var zoom = d3.zoom().on("zoom", function () {
svg.attr('transform', d3.event.transform);
});
and call it on this svg variable:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.append("g")
.attr("transform", "translate("
+ width/10 + "," + height/2 + ")");
(where width and height happen to be the size of the screen).
This works great, except for the first time the user zooms. The zoom state is still at the origin, as opposed to the width/10 and height/2 translation.
How do I change the zoom state programmatically to fix this?
Just after writing this I found a very helpful answer here
What worked for me was this:
d3.select('svg').call(zoom.translateBy, width/10, height/2);
I would like to take advantage of D3's zoom behavior functionality, but I need to do all translations/scaling of my SVG using the viewBox property instead of the transform method as shown in the D3 example: http://bl.ocks.org/mbostock/3680999
How can I achieve this same scale/translate using only the viewBox? Here's my code so far, which doesn't work well like the transform method.
function zoomed(d) {
if (!scope.drawLine) {
var scale = d3.event.scale;
var translation = d3.event.translate;
//This works, but I can't use it for reason's I won't go into now
//mapSVG_G.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
var newViewBox = [
initialViewBox[0] - translation[0],
initialViewBox[1] - translation[1],
initialViewBox[2]/scale,
initialViewBox[3]/scale
];
mapSVG.attr('viewBox', newViewBox);
}
}
a bit off, but could serve you as a start:
main piece:
var newViewBox = [
-translate[0] / scale,
-translate[1] / scale,
width / scale,
height / scale
].join(" ");
whole example:
var width = 960,
height = 500;
var randomX = d3.random.normal(width / 2, 80),
randomY = d3.random.normal(height / 2, 80);
var data = d3.range(2000).map(function() {
return [
randomX(),
randomY()
];
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height].join(" "))
var vis = svg.append("g")
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
.append("g");
vis.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height);
vis.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", 2.5)
.attr("transform", function(d) {
return "translate(" + d + ")";
});
function zoom() {
var scale = d3.event.scale;
var translate = d3.event.translate;
var newViewBox = [
-translate[0] / scale,
-translate[1] / scale,
width / scale,
height / scale
].join(" ");
svg.attr('viewBox', newViewBox);
}
.overlay {
fill: none;
pointer-events: all;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I would like to link the zoom and pan controls of multiple charts together so they all pan and zoom together when one of the charts pan and zoom controls are engaged.
I tried creating a single zoom object and passing it to the charts, but only the last chart actually pan and zoomed. The others remained static even though I was zooming in their areas.
Here's a snapshot of the charts. Each chart has an overview chart with a viewport that can be moved as well. I would like to link all the controls together so the viewports are also the same on each chart.
So, how can I link the pan and zoom controls on multiple charts?
Here is a jsfiddle for this code: https://jsfiddle.net/babazaroni/a52oukzn/
Here is my code:
The overall chart creator. This code makes 3 charts.
define([
'd3',
'components/sl',
'MockData',
'components/candlestickSeries',
'Chart'
], function (d3, sl, MockData,candlestickSeries,Chart) {
'use strict';
function generateData()
{
var data = new MockData(0.1, 0.1, 100, 50, function (moment) {
return !(moment.day() === 0 || moment.day() === 6);
})
.generateOHLC(new Date(2014, 1, 1), new Date(2014, 8, 1));
return data;
}
var data = generateData();
d3.select('#chart1')
.datum(data)
.call(Chart());
data = generateData();
d3.select('#chart1')
.datum(data)
.call(Chart());
data = generateData();
d3.select('#chart1')
.datum(data)
.call(Chart());
});
Here is the chart code:
define([
'd3',
'components/sl',
'MockData',
'components/candlestickSeries'
], function (d3, sl, MockData) {
'use strict';
function timeSeriesChart() {
function chart(selection)
{
selection.each(function(data) {
var minDate = new Date(d3.min(data, function (d) { return d.date; }).getTime() - 8.64e7);
var maxDate = new Date(d3.max(data, function (d) { return d.date; }).getTime() + 8.64e7);
var yMin = d3.min(data, function (d) { return d.low; });
var yMax = d3.max(data, function (d) { return d.high; });
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The primary chart
// Set up the drawing area
var margin = {top: 20, right: 20, bottom: 30, left: 35},
width = 600 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var plotChart = d3.select(this).classed('chart', true).append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var plotArea = plotChart.append('g')
.attr('clip-path', 'url(#plotAreaClip)');
plotArea.append('clipPath')
.attr('id', 'plotAreaClip')
.append('rect')
.attr({ width: width, height: height });
// Scales
var xScale = d3.time.scale(),
yScale = d3.scale.linear();
// Set scale domains
xScale.domain([minDate, maxDate]);
yScale.domain([yMin, yMax]).nice();
// Set scale ranges
xScale.range([0, width]);
yScale.range([height, 0]);
// Axes
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(10);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
plotChart.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
plotChart.append('g')
.attr('class', 'y axis')
.call(yAxis);
plotChart.append("text")
.attr("x", (width / 2))
.attr("y", 1 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "underline")
.text("Your title goes here");
// Data series
var series = sl.series.candlestick()
.xScale(xScale)
.yScale(yScale);
var dataSeries = plotArea.append('g')
.attr('class', 'series')
.datum(data)
.call(series);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Navigation chart
var navWidth = width,
navHeight = 100 - margin.top - margin.bottom;
// Set up the drawing area
var navChart = d3.select(this).classed('chart', true).append('svg')
.classed('navigator', true)
.attr('width', navWidth + margin.left + margin.right)
.attr('height', navHeight + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Scales
var navXScale = d3.time.scale()
.domain([
new Date(minDate.getTime() - 8.64e7),
new Date(maxDate.getTime() + 8.64e7)
])
.range([0, navWidth]),
navYScale = d3.scale.linear()
.domain([yMin, yMax])
.range([navHeight, 0]);
// Axes
var navXAxis = d3.svg.axis()
.scale(navXScale)
.orient('bottom');
navChart.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + navHeight + ')')
.call(navXAxis);
// Data series
var navData = d3.svg.area()
.x(function (d) { return navXScale(d.date); })
.y0(navHeight)
.y1(function (d) { return navYScale(d.close); });
var navLine = d3.svg.line()
.x(function (d) { return navXScale(d.date); })
.y(function (d) { return navYScale(d.close); });
navChart.append('path')
.attr('class', 'data')
.attr('d', navData(data));
navChart.append('path')
.attr('class', 'line')
.attr('d', navLine(data));
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Viewport
function redrawChart() {
dataSeries.call(series);
plotChart.select('.x.axis').call(xAxis);
}
function updateZoomFromChart() {
var fullDomain = maxDate - minDate,
currentDomain = xScale.domain()[1] - xScale.domain()[0];
var minScale = currentDomain / fullDomain,
maxScale = minScale * 20;
zoom.x(xScale)
.scaleExtent([minScale, maxScale]);
}
var viewport = d3.svg.brush()
.x(navXScale)
.on("brush", function () {
xScale.domain(viewport.empty() ? navXScale.domain() : viewport.extent());
redrawChart();
})
.on("brushend", function () {
updateZoomFromChart();
});
navChart.append("g")
.attr("class", "viewport")
.call(viewport)
.selectAll("rect")
.attr("height", navHeight);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Zooming and panning
function updateViewpointFromChart() {
if ((xScale.domain()[0] <= minDate) && (xScale.domain()[1] >= maxDate)) {
viewport.clear();
}
else {
viewport.extent(xScale.domain());
}
navChart.select('.viewport').call(viewport);
}
var zoom = d3.behavior.zoom()
.x(xScale)
.on('zoom', function() {
if (xScale.domain()[0] < minDate) {
zoom.translate([zoom.translate()[0] - xScale(minDate) + xScale.range()[0], 0]);
} else if (xScale.domain()[1] > maxDate) {
zoom.translate([zoom.translate()[0] - xScale(maxDate) + xScale.range()[1], 0]);
}
redrawChart();
updateViewpointFromChart();
});
var overlay = d3.svg.area()
.x(function (d) { return xScale(d.date); })
.y0(0)
.y1(height);
plotArea.append('path')
.attr('class', 'overlay')
.attr('d', overlay(data))
.call(zoom);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Setup
var daysShown = 30;
xScale.domain([
data[data.length - daysShown - 1].date,
data[data.length - 1].date
]);
redrawChart();
updateViewpointFromChart();
updateZoomFromChart();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper methods
});
}
// alert("here we are again and again");
return chart;
}
return timeSeriesChart;
});
From this example : http://bl.ocks.org/mbostock/6123708
Create the zoom:
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
Call zoom on your container :
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom); //<<<<<HERE
That is what it will look like on a single canvas, but in your case it will look something like this :
Create the zoom:
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
transform each of your canvas's/containers
function zoomed() {
container1.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
container2.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
container3.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
Call zoom on all of your containers :
var svg1 = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom); //<<<<<HERE
var svg2 = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom); //<<<<<HERE
var svg3 = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom); //<<<<<HERE
Something along those lines should work but can't test it without any example.