zooming and panning - upgrading d3 code to d3.v4 - javascript

I have been trying to upgrade this code to v4 of d3 with no luck.
function xnr(selector) {
var coordinates = [
{"id": 1, "x": 120.16353869437225, "y": 160.974180892502466},
{"id": 2, "x": 190.285414932883366, "y": 259.116836781737214},
{"id": 3, "x": 310.218762385111142, "y": 170.033297729284202}
];
var x = d3.scale.linear()
.domain([0, 100])
.range([0, 100]);
var y = d3.scale.linear()
.domain([0, 100])
.range([0, 100]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select(selector).append("svg:svg")
.attr("width", 400)
.attr("height", 400)
.call(d3.behavior.zoom().x(x).y(y).scaleExtent([1, 100]).on("zoom", zoom));
var groups = svg.selectAll("g.leaf")
.data(coordinates)
.enter()
.append("svg:g")
.attr("class", "leaf")
.attr("transform", transform)
.append("svg:circle")
.attr("r", 5)
.attr('stroke', '#aaaaaa')
.attr('stroke-width', '2px');
function zoom() {
svg.selectAll("g.leaf").attr("transform", transform);
}
function transform(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ")";
}
}
Old code jsfiddle - works as i need it (semantic zoom).
New code jsfiddle - only works as geometic zoom.
Mike Bostock's examples for SVG geometric and semantic zooming worked for me in d3 v3, but since the zoom.x() and zoom.y() functions had been removed, i am lost.

Mike Bostock's Pan & Zoom III example can be modified to use semantic zooming as per this jsfiddle. The important bits are:
var radius = 3;
var circles = g.selectAll("circle")
.data(points)
.enter().append("circle")
.attr("cx", function(d) { return d[0]; })
.attr("cy", function(d) { return d[1]; })
.attr("r", radius);
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.call(d3.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed));
function zoomed() {
var transform = d3.zoomTransform(this);
circles.attr("transform", transform);
circles.attr("r", radius/transform.k)
}
zoomTransform is a linear transformation of the plane which increases area as it increases distances. If we want the circles to have the same area after zooming, we need to rescale their radii by the scale factor of the transformation, transform.k.

I have an example fiddle for semantic zoom for d3 version 4 with click controls. also displaying scale for the reference. you can use this scale to add any semantic information for the same.
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var randomX = d3.randomNormal(width / 2, 80),
randomY = d3.randomNormal(height / 2, 80),
data = d3.range(200).map(function() {
return [randomX(), randomY()];
});
var scale;
var circle;
var _zoom = d3.zoom()
.scaleExtent([1, 8])
.on("zoom", zoom);
circle = svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("transform", transform(d3.zoomIdentity));
svg.append("rect")
.attr("fill", "none")
.attr("pointer-events", "all")
.attr("width", width)
.attr("height", height)
.call(_zoom);
function zoom() {
circle.attr("transform", transform(d3.event.transform));
scale = d3.event.transform.k;
console.log(scale);
document.getElementById('scale').value = scale;
}
// semantic zoom
function semanticZoom() {
circle.attr("transform", transform(d3.event.transform));
}
function transform(t) {
return function(d) {
return "translate(" + t.apply(d) + ")";
}
}
var gui = d3.select("#gui");
gui.append("span")
.classed("zoom-in", true)
.text("+")
.on("click", function() {
_zoom.scaleBy(circle, 1.2);
});
gui.append("span")
.classed("zoom-out", true)
.text("-")
.on("click", function() {
_zoom.scaleBy(circle, 0.8);
});
below is the js fiddle link:
https://jsfiddle.net/sagarbhanu/5jLbLpac/36/

Related

Scattered plot zooming not working correctly in d3.js

I am trying to create a scatterplot with a zooming. The following code I have used in my angular application and its working till a certain extent when I run it in my local server. However putting the same code in Stackblitz, the zooming is not working. I want to achieve a zooming where the zooming is limited to just the values on the graph. There should be no zooming of the axis accept the value changes in both the axis. Something exactly like : http://bl.ocks.org/peterssonjonas/4a0e7cb8d23231243e0e .
Here in the example, on zooming, only the values are zoomed and the axis values changed correspondingly. It doesn't zoom the whole graph plot area. How do I achieve it? Here is my Stackblitz code:
https://stackblitz.com/edit/angular-hu2thj
ANSWER :
Finally I figure out the graph for this problem in case of any future reference:
import { Component, OnInit, Input, ViewChild, ElementRef } from '#angular/core';
import * as d3 from 'd3';
#Component({
selector: 'app-scatterplot',
templateUrl: './scatterplot.component.html',
styleUrls: ['./scatterplot.component.css']
})
export class ScatterplotComponent implements OnInit {
#ViewChild('chart1') private chartContainer: ElementRef;
dataValue = [{ x: "67", y: "188", },
{ x: "200", y: "163" },
{ x: "254", y: "241" },
{ x: "175", y: "241" },
];
ngOnInit() {
this.graph();
}
graph() {
const element = this.chartContainer.nativeElement;
var svgWidth = 400;
var svgHeight = 400;
var margin = { top: 30, right: 40, bottom: 50, left: 60 };
var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;
var originalCircle = {
"cx": -150,
"cy": -15,
"r": 20
};
var svgViewport = d3.select(element)
.append('svg')
.attr('width', svgWidth)
.attr('height', svgHeight)
// create scale objects
var x = d3.scaleLinear()
.domain([1, 500])
.range([0, width]);
var y = d3.scaleLinear()
.domain([1, 500])
.range([height, 0]);
// create axis objects
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
// Zoom Function
var zoom = d3.zoom()
.on("zoom", zoomFunction);
// Inner Drawing Space
var innerSpace = svgViewport.append("g")
.attr("class", "inner_space")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
// append some dummy data
var data = innerSpace.selectAll("circle")
.data(this.dataValue)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function (d) {
return x(d.x)
;
})
.attr("cy", function (d) {
return y(d.y);
})
.attr("r", 2);
// Draw Axis
var gX = innerSpace.append("g")
.attr("class", "axis")
.attr("transform", "translate(0, " + height + ")")
.call(xAxis);
var gY = innerSpace.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
// append zoom area
var view = innerSpace.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height - 10)
.attr("fill", "transparent")
.attr("fill-opacity", 0.1)
.call(zoom)
function zoomFunction() {
// create new scale ojects based on event
var new_xScale = d3.event.transform.rescaleX(x)
var new_yScale = d3.event.transform.rescaleY(y)
console.log(d3.event.transform)
// update axes
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
// update circle
data.attr("transform", d3.event.transform)
};
}
}
The problem on stackblitz is that d3.event is null.
Try this to zoom the points in your local server.
You need to add a clip path and animate the axis, see the second example (heatmap)
var svg = d3.select(element)
.append("svg:svg")
.attr("width", w + left_padding)
.attr("height", h + top_padding);
var g = svg.append("g");
var zoom = d3.zoom().on("zoom", function () {
console.log("zoom", d3, d3.event);
g.attr("transform", d3.event.transform);
});
svg.call(zoom);
g.selectAll("circle")
.data(this.dataValue)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", d => x(d.x) )
.attr("cy", d => y(d.y) )
.attr("r", 2);

semantic zoom and panning d3.js v4

I'm tying to implement semantic zoom with d3.js v4. Most examples and questions on Stackoverflow are for v3. So i tried to alter one of them, like from this answer. Example from the answer: bl.ocks.org example
I tried to adept the example for d3 v4:
var xOld, yOld;
var width = document.querySelector('body').clientWidth,
height = document.querySelector('body').clientHeight;
var randomX = d3.randomNormal(width / 2, 80),
randomY = d3.randomNormal(height / 2, 80);
var data = d3.range(2000).map(function() {
return [
randomX(),
randomY()
];
});
var xScale = d3.scaleLinear()
.domain(d3.extent(data, function (d) {
return d[0];
}))
.range([0, width]);
var yScale = d3.scaleLinear()
.domain(d3.extent(data, function (d) {
return d[1];
}))
.range([0, height]);
var xExtent = xScale.domain();
var yExtent = yScale.domain();
var zoomer = d3.zoom().scaleExtent([1, 8]).on("zoom", zoom);
var svg0 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
var svg = svg0.append('g')
.attr("width", width)
.attr("height", height)
var circle = svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", 2.5)
.attr("transform", transform_);
svg0
.call(zoomer)
.call(zoomer.transform, d3.zoomIdentity);
function zoom(e) {
var transform = d3.zoomTransform(this);
var x = 0;
var y = 0;
if(d3.event.sourceEvent) {
var x = d3.event.sourceEvent.layerX;
var y = d3.event.sourceEvent.layerY;
}
var scale = Math.pow(transform.k, .8);
xScale = d3.scaleLinear()
.domain([xExtent[0], xExtent[1] / scale])
.range([0, width]);
yScale = d3.scaleLinear()
.domain([yExtent[0], yExtent[1] / scale])
.range([0, height]);
circle.attr('transform', transform_)
svg.attr("transform", "translate(" + d3.event.transform.x + "," + d3.event.transform.y + ")");
}
function transform_(d) {
var x = xScale(d[0]);
var y = yScale(d[1]);
return "translate(" + x + "," + y + ")";
}
The zoom itself works - basically. Like the normal zoom it should zoom to the position of the mouse pointer, which it doesn't. Also the panning looks a little bit unsmooth.
I tried to use the mouse position from the d3.event.sourceEvent as offset for the translation, but it didn't work.
So, how could the zoom use the mouse position? It would be also great to get smoother panning gesture.
The zoom on mouse pointer can be added using pointer-events attribute.
Also, I have an example for a semantic zoom for d3 version 4 with the mouse pointer and click controls and also displaying the scale value for reference.[enter link description here][1]
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var randomX = d3.randomNormal(width / 2, 80),
randomY = d3.randomNormal(height / 2, 80),
data = d3.range(20).map(function() {
return [randomX(), randomY()];
});
var scale;
console.log(data);
var circle;
var _zoom = d3.zoom()
.scaleExtent([1, 8])
.on("zoom", zoom);
circle = svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("transform", transform(d3.zoomIdentity));
svg.append("rect")
.attr("fill", "none")
.attr("pointer-events", "all")
.attr("width", width)
.attr("height", height)
.call(_zoom);
function zoom() {
circle.attr("transform", transform(d3.event.transform));
scale = d3.event.transform.k;
console.log(scale);
document.getElementById('scale').value = scale;
}
function transform(t) {
return function(d) {
return "translate(" + t.apply(d) + ")";
}
}
var gui = d3.select("#gui");
gui.append("span")
.classed("zoom-in", true)
.text("+")
.on("click", function() {
_zoom.scaleBy(circle, 1.2);
});
gui.append("span")
.classed("zoom-out", true)
.text("-")
.on("click", function() {
_zoom.scaleBy(circle, 0.8);
});
please find the link to fiddle:
[1]: https://jsfiddle.net/sagarbhanu/5jLbLpac/3/

Apply zoom pan and axis rescale in d3

I have created scattered chart in D3.
It's working fine but I have a requirement to add zooming and axis rescaling to the chart.
Since I am pretty much new to d3 I am not able to do it.I have seen some example about it but I am able apply the zooming, panning etc code in my chart.
Here is my code-
var margin = {
top: 35,
right: 10,
bottom: 40,
left: 80
},
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
var xValue = function(d){
return d[measureArray[1]];
},
x = d3.scale.linear()
.range([0, width*.98]),
xMap = function(d,i) {
return x(xValue(d));
},
make_x_axis = function() {
return d3.svg.gridaxis()
.scale(x)
.orient("bottom")
},
xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(function(d) {
return d;
});
var yValue = function(d){
return d[measureArray[0]];
},
y = d3.scale.linear()
.range([height*.98, 0]),
yMap = function(d,i){
return y(yValue(d));
},
make_y_axis = function() {
return d3.svg.gridaxis()
.scale(y)
.orient("left")
},
yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(d) {
// if(typeof displayY !=="undefined" && displayY =="Yes"){
// if(yAxisFormat==""){
return d;
});
var zValue = function(d){
return d[measureArray[2]];
},
zScale = d3.scale.linear()
.range([height*.98, 0]),
zMap = function(d) {
return zScale(zValue(d));
};
var color = d3.scale.category10();
var svg = d3.select("body") .append("svg")
.attr("id", "svg_" + div)
.attr("viewBox", "0 0 "+(width + margin.left + margin.right)+" "+(height + margin.top + margin.bottom+ 17.5 )+" ")
.classed("svg-content-responsive", true)
.append("g")
.attr("transform", "translate(" + (margin.left*.7) + "," + (margin.top+3) + ")");
var tooltip = d3.select("#"+divId).append("div")
.attr("class", "tooltip")
.style("opacity", 0);
data.forEach(function(d) {
d[measureArray[2]] = +d[measureArray[2]]
d[measureArray[1]] = +d[measureArray[1]];
d[measureArray[0]] = +d[measureArray[0]];
});
x.domain([d3.min(data, xValue)-1, d3.max(data, xValue)+1]);
y.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);
// }
if(typeof chartData[divId]["displayX"]!="undefined" && chartData[divId]["displayX"]!="" && chartData[divId]["displayX"]!="Yes"){}else{
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text(measureArray[1]);
}
// y-axis
if(typeof chartData[divId]["displayY"]!="undefined" && chartData[divId]["displayY"]!="" && chartData[divId]["displayY"]!="Yes"){}else{
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(measureArray[0]);
}
var max = maximumValue(data, measureArray[2]);
var min = minimumValue(data, measureArray[2]);
var temp = {};
temp["min"] = min;
temp["max"] = max;
svg.selectAll(".circle")
.data(data)
.enter().append("circle")
.attr("index_value", function(d, i) {
return "index-" + d[columns[1]].replace(/[^a-zA-Z0-9]/g, '', 'gi');
})
.attr("class", function(d, i) {
return "bars-Bubble-index-" + d[columns[1]].replace(/[^a-zA-Z0-9]/g, '', 'gi')+div;
})
.attr("r", function(d,i){
// var scale = d3.scale.linear().domain([temp["max"], temp["min"]]).range(["38", "12"]);
// var radius = scale(data[i][measureArray[2]]);
return 6;
})
.attr("cx", xMap)
.attr("cy", yMap)
.attr("opacity",".6")
.attr("fill", 'red')
.attr("id",function(d,i) {
return d[columns[0]]+":"+d[columns[1]];
})
.attr("onclick", fun);
Working fiddle.
You can do it like this:
//define zoom behavior
var zoom = d3.behavior.zoom()
.on("zoom", draw);
//make a rectangle for receiving all the zoom/pan action.
svg.append("rect")
.attr("class", "pane")
.attr("width", width)
.attr("height", height)
.call(zoom);
//make a clip path so that the circle don't go out of the graph.
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", x(0))
.attr("y", y(1))
.attr("width", x(1) - x(0))
.attr("height", y(0) - y(1));
Add the following class to the style(so that the rectangle pane is not visible) note: that the fill is none:
rect.pane {
cursor: move;
fill: none;
pointer-events: all;
}
After defining the domain, set the zoom x and y
x.domain([d3.min(data, xValue) - 1, d3.max(data, xValue) + 1]);
y.domain([d3.min(data, yValue) - 1, d3.max(data, yValue) + 1]);
// set the zoom for x and y
zoom.x(x);
zoom.y(y);
Make a group for all the circle so that its within the clippath
circlegroup = svg.append("g").attr("clip-path", "url(#clip)");
circlegroup.selectAll(".circle")...
Define the draw function which will be called on zoom and pan:
function draw() {
svg.select("g.x.axis").call(xAxis);//zoom of x axis
svg.select("g.y.axis").call(yAxis);//zoom of y axis
//update the position of the circle on zoom/pan
svg.selectAll("circle").attr("cx", xMap)
.attr("cy", yMap)
}
working code here

D3.js- Dynamically updating the axis in a line graph

I recently started working on d3.js. I am trying to develop a interactive multiple line graph. Please take a look at my work so far: http://jsfiddle.net/dalapati/cdkn14j3/6/
data1 = [
{"date": 1357717800000,"value": "5.6"},
{"date": 1357718400000,"value": "5.6"},
{"date": 1357719000000,"value": "6"},
{"date": 1357719600000,"value": "5.1"},
{"date": 1357720200000,"value": "5.3"},
// {"date": 1357720800000,"value": "5.4"}
];
data2 = [
{"date": 1357714800000,"value": "5.2"},
{"date": 1357715400000,"value": "5.2"},
{"date": 1357716000000,"value": "5.2"},
{"date": 1357716600000,"value": "5.1"},
{"date": 1357717200000,"value": "5.5"},
];
// date manipulation to format UTC to js Date obj
data1.forEach(function(d){ d.time = new Date(d.time * 1000);});
data2.forEach(function(d){ d.time = new Date(d.time * 1000);});
// helpers and constants
var margin = {"top": 50, "right": 50, "bottom": 50, "left": 100, "axis": 55};
var width = 1500 - margin.left - margin.right;
var height = 580 - margin.top - margin.bottom;
var timeFormat = d3.time.format("%X");
// find data range
var findXMin = function(_data){
return d3.min(_data, function(d){
return Math.min(d.date);
});
}
var findXMax = function(_data){
return d3.max(_data, function(d){
return Math.max(d.date);
});
}
var findYMin = function(_data){
return d3.min(_data, function(d){
return Math.min(d.value);
});
}
var findYMax = function(_data){
return d3.max(_data, function(d){
return Math.max(d.value);
});
}
var x1Min = findXMin(data1);
var x1Max = findXMax(data1);
var x2Min = findXMin(data2);
var x2Max = findXMax(data2);
var y1Min = findYMin(data1);
var y1Max = findYMax(data1);
var y2Min = findYMin(data2);
var y2Max = findYMax(data2);
var yMin = (y1Min < y2Min) ? y1Min:y2Min;
var yMax = (y1Max > y2Max) ? y1Max:y2Max;
// scales
var x1Scale = d3.time.scale()
.domain([x1Min,x1Max])
.range([0, width]);
var x2Scale = d3.time.scale()
.domain([x2Min,x2Max])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([yMin,yMax]).range([height, 0]);
var renderXAxis = function(scale, className, tickFormat, index0, index1, orient){
var axis = d3.svg.axis()
.scale(scale)
.orient(orient)
.ticks(5)
.tickPadding(5)
.tickFormat(tickFormat);
svg.append("g")
.attr("class", className)
.attr("transform", function() {
return "translate(" +index0+", " +index1+ ")";})
.call(axis);
}
var renderYAxis = function(scale, className, tickFormat, index0, index1, orient){
var axis = d3.svg.axis()
.scale(scale)
.orient(orient)
.ticks(5)
.tickPadding(5)
.tickFormat(tickFormat);
svg.append("g")
.attr("class", className)
.attr("transform", function(){
return "translate(" +index0+", " +index1+ ")";})
.call(axis);
// grid plot
svg.append("g")
.attr("class", "y grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat(""));
}
var make_y_axis = function () {
return d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)
.tickPadding(5);
};
// Set up chart type
// create a line function that can convert data into x and y points
var line1 = d3.svg.line().interpolate("basis")
.x(function (d) {
return x1Scale(d.date);
})
.y(function (d) {
return yScale(d.value);
});
var line2 = d3.svg.line().interpolate("basis")
.x(function (d) {
return x2Scale(d.date);
})
.y(function (d) {
return yScale(d.value);
});
// Create Zoom feature
var zoomBottom = d3.behavior.zoom()
.x(x1Scale)
.scaleExtent([1,10]);
var zoom = d3.behavior.zoom()
.x(x2Scale)
.scaleExtent([1,10])
.on("zoom",zoomed);
// Create Drag behaviour
var drag = d3.behavior.drag()
.origin(function(d){
var t = d3.select(this);
return {
x: t.attr("x"),
y: t.attr("y")
};
})
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
// create svg container
var svg = d3.select('#chart')
.append("svg:svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var plot = svg.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("class", "plot")
.call(zoom);
//Draw Axes
var x1Axis = renderXAxis(x1Scale, "x1Axis", timeFormat, 0, height, "bottom");
var x2Axis = renderXAxis(x2Scale, "x2Axis", timeFormat, 0, 0, "top");
var yAxis = renderYAxis(yScale, "yAxis", function(d){return d;}, 0, 0, "left");
// add lines
// do this AFTER the axes above so that the line is above the tick-lines
var clip = svg.append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("x1Scale", 0)
.attr("x2Scale", 0)
.attr("yScale", 0)
.attr("width", width)
.attr("height", height);
var chartBody = svg.append("g")
.attr("clip-path", "url(#clip)");
chartBody.append("svg:path")
.datum(data1)
.attr("class", "data1")
.attr("d", line1(data1))
.attr("cursor", "move")
.call(drag);
chartBody.append("svg:path")
.datum(data2)
.attr("class", "data2")
.attr("d", line2(data2))
.attr("cursor", "move")
.call(drag);
/************************** ADDING ZOOMING FEATURE****************************************/
function zoomed() {
zoomBottom.scale(zoom.scale()).translate(zoom.translate());
d3.select(".x1Axis").remove();
renderXAxis(x1Scale, "x1Axis", timeFormat, 0, height, "bottom");
d3.select(".x2Axis").remove();
renderXAxis(x2Scale, "x2Axis", timeFormat, 0, 0, "top");
d3.select(".yAxis").remove();
renderYAxis(yScale, "yAxis", function(d){return d;}, 0, 0, "left");
svg.select(".data1").attr("d",line1(data1));
svg.select(".data2").attr("d",line2(data2));
}
/***************** Adding Dragging feature*****************************************************/
function dragstarted(d){
d3.event.sourceEvent.stopPropagation();
//d3.event.preventDefault();
//d3.select(this).attr( 'pointer-events', 'none' );
d3.select(this).classed("dragging", true);
console.log(d);
}
function dragged(d){
var lineToMove = d3.event.x;
d3.select(this)
.transition()
.ease("linear")
.attr("transform", "translate(" +lineToMove+ ",0)");
d3.select(".x1Axis").remove();
renderXAxis(x1Scale, "x1Axis", timeFormat, lineToMove, height, "bottom");
d3.select(".x2Axis").remove();
renderXAxis(x2Scale, "x2Axis", timeFormat, lineToMove, 0, "top");
d3.select(".yAxis").remove();
renderYAxis(yScale, "yAxis", function(d){return d;}, 0, 0, "left");}
function dragended(d){
d3.select(this).classed("dragging", false);
}
I am trying to implement a dragging behavior to each line graph with respect to its axis. I could able to achieve implementing the dragging behavior to a particular line graph but, I am not sure how to dynamically update its respective axis. Can anyone please share your idea to solve this problem.
Thank you in advance.
This is the general comment I gave to solve the problem:
I would not completely re-render each axis with the update function. Instead, update its existing properties as needed and draw the axis a single time.
You can access these by just using a method like axis.scale(scale).tickFormat(tickFormat). This adds efficiency. Then you can do something like: d3.select(".x.axis").transition().duration(1600).call(axis)

Is there a way to zoom into a D3 force layout graph?

D3 has a force directed layout here. Is there a way to add zooming to this graph? Currently, I was able to capture the mouse wheel event but am not really sure how to write the redraw function itself. Any suggestions?
var vis = d3.select("#graph")
.append("svg:svg")
.call(d3.behavior.zoom().on("zoom", redraw)) // <-- redraw function
.attr("width", w)
.attr("height", h);
Update 6/4/14
See also Mike Bostock's answer here for changes in D3 v.3 and the related example. I think this probably supersedes the answer below.
Update 2/18/2014
I think #ahaarnos's answer is preferable if you want the entire SVG to pan and zoom. The nested g elements in my answer below are really only necessary if you have non-zooming elements in the same SVG (not the case in the original question). If you do apply the behavior to a g element, then a background rect or similar element is required to ensure that the g receives pointer events.
Original Answer
I got this working based on the zoom-pan-transform example - you can see my jsFiddle here: http://jsfiddle.net/nrabinowitz/QMKm3/
It was a bit more complex than I had hoped - you have to nest several g elements to get it to work, set the SVG's pointer-events attribute to all, and then append a background rectangle to receive the pointer events (otherwise it only works when the pointer is over a node or link). The redraw function is comparatively simple, just setting a transform on the innermost g:
var vis = d3.select("#chart")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('svg:g');
vis.append('svg:rect')
.attr('width', w)
.attr('height', h)
.attr('fill', 'white');
function redraw() {
console.log("here", d3.event.translate, d3.event.scale);
vis.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
This effectively scales the entire SVG, so it scales stroke width as well, like zooming in on an image.
There is another example that illustrates a similar technique.
Why the nested <g>'s?
This code below worked well for me (only one <g>, with no random large white <rect>:
var svg = d3.select("body")
.append("svg")
.attr({
"width": "100%",
"height": "100%"
})
.attr("viewBox", "0 0 " + width + " " + height )
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("pointer-events", "all")
.call(d3.behavior.zoom().on("zoom", redraw));
var vis = svg
.append('svg:g');
function redraw() {
vis.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
Where all the elements in your svg are then appended to the vis element.
The provided answers work in D3 v2 but not in v3. I've synthesized the responses into a clean solution and resolved the v3 issue using the answer provided here: Why does d3.js v3 break my force graph when implementing zooming when v2 doesn't?
First the main code. This is a cleaned up version of #ahaarnos' answer:
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().on("zoom", redraw))
.append('g');
function redraw() {
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
Now you have pan and zoom, but you won't be able to drag nodes because the pan functionality will override the drag functionality. So we need to do this:
var drag = force.stop().drag()
.on("dragstart", function(d) {
d3.event.sourceEvent.stopPropagation(); // to prevent pan functionality from
//overriding node drag functionality.
// put any other 'dragstart' actions here
});
Here's #nrabinowitz' fiddle modified to use this cleaner zoom implementation, but illustrating how D3v3 breaks node drag: http://jsfiddle.net/QMKm3/718/
And here's the same fiddle modified to work with D3v3: http://jsfiddle.net/QMKm3/719/
I got my graph to work without the second "svg:g" append.
[...].attr("pointer-events", "all")
.attr("width", width2)
.attr("height", height2)
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw));
The rest is the same.
I got a solution for D3 force directed graph with zooming option.
var m = [40, 240, 40, 240],
width = 960,
height = 700,
root;
var svg = d3.select("body").append("svg")
.attr("class", "svg_container")
.attr("width", width)
.attr("height", height)
.style("overflow", "scroll")
.style("background-color", "#EEEEEE")
.append("svg:g")
.attr("class", "drawarea")
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
//applying zoom in&out for svg
d3.select("svg")
.call(d3.behavior.zoom()
.scaleExtent([0.5, 5])
.on("zoom", zoom));
//zooming
function zoom() { //zoom in&out function
var scale = d3.event.scale,
translation = d3.event.translate,
tbound = -height * scale,
bbound = height * scale,
lbound = (-width + m[1]) * scale,
rbound = (width - m[3]) * scale;
// limit translation to thresholds
translation = [
Math.max(Math.min(translation[0], rbound), lbound),
Math.max(Math.min(translation[1], bbound), tbound)
];
d3.select(".drawarea")
.attr("transform", "translate(" + translation + ")" +
" scale(" + scale + ")");
}
If you want to zoom and pan force layout without changing node-size, try below.
You can also drag nodes without trembling.
This code is based on original force layout example. As for nodes and links data, please refer to original sample data. http://bl.ocks.org/mbostock/4062045
Plz note the variables xScale and yScale, the functions dragstarted(), dragged(), and dragended().
Function tick() was changed as well.
You can see the result at http://steelblue.tistory.com/9
The language on the site is Korean. However you can easily find the result at the third example on the page.
var graph = {
"nodes": [
{ "name": "Myriel", "group": 1 },
{ "name": "Napoleon", "group": 1 },
// ......
{ "name": "Mme.Hucheloup", "group": 8 }
],
"links": [
{ "source": 1, "target": 0, "value": 1 },
{ "source": 2, "target": 0, "value": 8 },
// .......
{ "source": 76, "target": 58, "value": 1 }
]
};
var width = 640,
height = 400;
var color = d3.scale.category20();
var xScale = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([0, height])
.range([0, height]);
var zoomer = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([0.1, 8]).on("zoom", zoom);
function zoom() {
tick();
};
var drag = d3.behavior.drag()
.origin(function (d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d.fixed |= 2;
}
function dragged(d) {
var mouse = d3.mouse(svg.node());
d.x = xScale.invert(mouse[0]);
d.y = yScale.invert(mouse[1]);
d.px = d.x;
d.py = d.y;
force.resume();
}
function dragended(d) {
d.fixed &= ~6; }
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.call(zoomer);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function (d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function (d) { return color(d.group); })
.call(drag);
node.append("title")
.text(function (d) { return d.name; });
force.on("tick",tick);
function tick(){
link.attr("x1", function (d) { return xScale(d.source.x); })
.attr("y1", function (d) { return yScale(d.source.y); })
.attr("x2", function (d) { return xScale(d.target.x); })
.attr("y2", function (d) { return yScale(d.target.y); });
node.attr("transform", function (d) {
return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")";
});
};

Categories

Resources