I am trying to implement a scatter plot with zooming capability with D3. I have generated the plot and the visualization,it can zoom in and out only when the mouse cursor is over the data points. In other words, when the mouse is on anywhere other than the dots, I am unable to zoom.
I have been searching and trying a number of examples from the Internet, but I had no luck. Please check the code residing in this link: https://github.com/e-kaya/scatter.git What do you think I have done wrong?
Thanks in advance.
You are calling the zoom function on the .dots.
svg.select(".dots").call(zoom);
Try binding the zoom function to the <svg>.
d3.select('svg').call(zoom);
depending on your requirement you may have to tweak the zoomed function as well.
function zoomed() {
d3.select('svg).attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
This will zoom your svg container.
Related
I am using this working code as a starter. I am trying to modify according to my data and need.
The only thing I ain't getting is why this zoom in zoom out not working. I tried this solution but got no luck.
I ain't able to find where the problem is within the code but I can see the comment where it stated that the functionality is deactivated. How can I make it activated? Sorry if this is a silly question I am still a newbie in js. There are similar questions too but the thing is jsfiddle are not working anymore so I can't see the whole code to understand the logic.
// Zoom functionnality is desactivated (user can use browser Ctrl + mouse wheel shortcut)
function zoomAndDrag() {
//var scale = d3.event.scale,
var scale = 1,
translation = d3.event.translate,
tbound = -height * scale,
bbound = height * scale,
lbound = (-width + margin.right) * scale,
rbound = (width - margin.left) * 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 + ')');
}
Here is the working demo with the code:
https://blockbuilder.org/ninjakx/2c4e726a531b40f9f09d7df41217d1de
D3 uses the Mouse Wheel event for zoom in/out functionality.
Your sample disables the mouse wheel event in this line:
d3.select('#tree-container').select('svg').on(mouseWheelName, null);
Also, the demo uses version 3 of D3, I strongly recommend to use the latest version of the library (V5).
I am using 'dagre d3' for displaying dependency graph. I also use slider(which I use to display step by step evolution of graph when user slides the slider) so each time Slider is moved, graph is redrawn. For the first time, 'zoom' feature will be set like below:
var zoom = d3.behavior.zoom().on("zoom", function () {
inner.attr("transform", "translate(" + d3.event.translate + ")" +
"scale(" + d3.event.scale + ")");
});
svg.call(zoom);
I want to retain the zoom level if the user has zoomed in or zoomed out the graph and then moved the slider. So make it work, I did the following.
this method will be called whenever user slides the slider.
function redrawGraphToStage(stageToRedraw) {
// Not sure how to get previous values. below one works but feel its bit hacky.
if(inner[0][0].transform.animVal.length){
tempInnerTranslate = [inner[0][0].transform.animVal[0].matrix.e, inner[0][0].transform.animVal[0].matrix.f];
tempInnerScale = inner[0][0].transform.animVal[1].matrix.a;
}
// do modifications to graph. and re-render.
if(tempInnerScale || tempInnerTranslate){
// this will set up the zoom level to before.
zoom.translate(tempInnerTranslate).scaleExtent(tempInnerScale, tempInnerScale).event(svg);
}
Even though it setup the zoom level to previous values, I wont be able to zoom after this. Any suggestions would be great.
Try with,
var zoom = d3.behavior.zoom().on("zoom", zoomed);
svg.call(zoom);
function zoomed() {
inner.attr("transform",
"translate(" + zoom.translate() + ")" +
"scale(" + zoom.scale() + ")"
);
}
function redrawGraphToStage(stageToRedraw) {
// Not sure how to get previous values. below one works but feel its bit hacky.
if(inner[0][0].transform.animVal.length){
tempInnerTranslate = [inner[0][0].transform.animVal[0].matrix.e, inner[0][0].transform.animVal[0].matrix.f];
tempInnerScale = inner[0][0].transform.animVal[1].matrix.a;
}
// do modifications to graph. and re-render.
if(tempInnerScale || tempInnerTranslate){
// this will set up the zoom level to before.
zoom.translate(tempInnerTranslate).scale(tempInnerScale, tempInnerScale);
zoomed();
}
I'm not sure what you're using the slider for, but I made you a demo where you can control the zoom scale with a slider bar. Significant change: scaleIntent replaced with scale.
JSFiddle working demo
I'm currently trying to combine two of my graphs into one view. My second graph, the bar chart will update the data if you use the slider. When you slide it forward, it works fine and dandy but when you slide it backwards it doesn't redraw the chart. When I was working on the bar chart separately it was fully working, it just seems that when I combine it with the line graph that it's only partially working.
I'm trying to find the cause of the problem and I think it's something wrong with this block of code. If I comment it out the bar graph redraws properly.
var chart1 = d3.select("body").append("svg")
.attr("width", chart1_width + chart1_margin.left + chart1_margin.right)
.attr("height", chart1_height + chart1_margin.top + chart1_margin.bottom)
.append("g")
.attr("transform", "translate(" + chart1_margin.left + "," + chart1_margin.top + ")");
Link to my fiddle: http://jsfiddle.net/flyingburrito/a8fym1ma/3/
Thanks!
I moved the bar chart on top and that made it work fine. An easy solution would be to keep it like that.
A better solution would be to analyse why that happens. With D3 when things happen like this it will often be in your selects. That is your problem here
d3.select("#sexYear").on("input", function () {
debugger;
d3.select("svg").selectAll("rect").remove();
update(this.value);
});
You are selecting 'svg' which selects the first svg which is the line chart, not the bar chart. This is because 'd3.select' selects the first of whatever is being called. What I did was add a class called 'barchart' to the barchart and then I select the barchart as such
var chart2 = d3.select("body").append("svg")
.attr('class','barchart')
and
d3.select("#sexYear").on("input", function () {
debugger;
d3.select(".barchart").selectAll("rect").remove();
update(this.value);
fiddle
In this live code link,
http://nvd3.org/livecode/index.html#codemirrorNav
if you add chart.yRange([0, 300]) for inverting the y-axis, the x-axis moves up
and sticks to the top(near the legend).
Any possible fix?
PS: The problem is with most of the charts on that page but 'Cumulative line chart' is closest to my use case.
The position of the x axis is hardcoded to y.range()[0] in the NVD3 source, but you can adjust this after the chart has been drawn. In your particular case, add the following code after .call(chart):
d3.select('.nv-x.nv-axis')
.attr('transform', 'translate(0,' + chart.yAxis.range()[1] + ')');
I have a problem that I haven't been able to solve for a number of weeks. I'm working on a modified version of this example: http://bl.ocks.org/mbostock/1667367 I've defined the brush initially so it has a brush extent between 0.5 and 0.8.
var brush = d3.svg.brush()
.x(x2)
.extent([0.5, 0.8])
.on("brush", brushed);
The brush selection shows up (on the context graph) in the correct location, but the initial view of the focus area is still set to the extent of the entire data set (and not to the clipping area of the brush). I've read that defining the brush doesn't automatically force a redraw of the area, but I can't seem to figure out how to make the view of the focus area automatically scale to the brush extents. Can someone please provide some input on this?
Update 1
I currently have a function called Brushed which does the following:
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select("path").attr("d", Light_area);
focus.select(".x.axis").call(xAxis);
Light_line_overlay.select("path").attr("d", Light_area);
rules.select(".y.grid").call(make_x_axis_light()
.tickSize(-height, 0, 0)
.tickFormat("")
);
var xx0=brush.x()(brush.extent()[0]);
var xx1=brush.x()(brush.extent()[1]);
brushfill.attr("x", xx0);
brushfill.attr("width", xx1-xx0);
}
It's slightly different from the example... because I've been modifying it to do different things from the base example. However, the first comment suggests that I should just call this brushed function after declaring the brush (see first post). However, calling this function doesn't do anything (or at least, it doesn't update the focus area to the extents of the brush). Do you have any suggestions?
I apologize for answering this two years late but I just ran in to the same situation and this was the only resource I found on the topic. I was able to figure it out, so hopefully this will help anybody else who stumbles upon it.
The code in the original question was almost all the way there, it just didn't have the right scaling on the extent initialization.
The data I'm using is an array of objects with a ts key (which is epoch milliseconds) that I use for my x values.
// These are needed for the brush construction to know how to scale
x2.domain(x.domain());
y2.domain(y.domain());
// Pick out the ~50% and ~80% marks from the data
var N = data.length;
var cx0 = new Date(data[Math.floor(N*0.50)].ts);
var cx1 = new Date(data[Math.floor(N*0.80)].ts);
// Construct with that extent, which will leave the
// context box started in the correct position.
var brush = d3.svg.brush()
.x(x2)
.extent([cx0, cx1])
.on("brush", brushed)
;
// This is just the original brushed example
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select(".area").attr("d", line);
focus.select(".x.axis").call(xAxis);
}
...
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")")
;
// Now that focus is defined we can manually update it
brushed();
I actually kept the call to brushed at the very end of the setup, just to keep things pretty, but the point here was just to illustrate that once focus is defined you can call brushed to do whatever updates you want there.
Ultimately it seems your main issue was getting the right type for the extent. Using [0.5, 0.8] worked on initialization, but if you check whenever brushed is called from actually sliding the focus around with the mouse, brush.extent() will be [Date(), Date()]. And that makes sense, since we're passing that extent to x.domain. So this sets up all the scaling before initializing the brush, so that the initialization extent can be a Date, then everything else is gravy.
You need to perform actions similar to the ones of the brushed function whenever your brush extent is changed programmatically. Resize the x.domain, refresh the view.
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select("path").attr("d", area);
focus.select(".x.axis").call(xAxis);
}
If that doesn't solve your problem, consider providing some code example.