I am a novice at D3 and I was trying to implement zoom/pan functionality on my scatter plot. Unfortunately, after following a tutorial, whenever I zoom on the visualization, the axes ticks disappear but the visualization doesn't readjust. Could someone explain what I'm doing wrong?
//Adding zoom and pan interaction to visualization
var zoom = d3.zoom()
.scaleExtent([.5, 20])
.extent([0, 0], [width, height])
.on("zoom", zoomed);
// This add an invisible rect on top of the chart area. This rect can recover pointer events: necessary to understand when the user zoom
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(zoom);
function zoomed(){
// recover the new scale
var newX = d3.event.transform.rescaleX(xScale);
var newY = d3.event.transform.rescaleY(yScale);
// update axes with these new boundaries
xAxis.call(d3.axisBottom(newX))
yAxis.call(d3.axisLeft(newY))
//update positions
dotGraph.selectAll("circle")
.attr("cx", d => newX(d.installs))
.attr("cy", d => newY(d.reviews));
}
The full code is located here: https://jsfiddle.net/wkLr7hob/
Thanks.
If you comment out the extent portion of your code you'll see the zoom working as expected¹, minus the enforcement of an extent.
You're not providing a valid extent to zoom.extent(), you pass two arrays:
.extent([0, 0], [width, height])
Instead of one [[0,0],[width,height]],
But this still doesn't fix the extent of the zoom behavior, I believe you are looking for translateExtent which limits the panning extent of the chart - however, if we use [[0,0],[width,height]], zooming out to 0.5 is not possible: the extent shown would be greater than the translate extent. So we might as well set the minimum scale to 1.
Doing that we get this.
¹ Also, in the zoom function, you're selecting all circles in dotGraph, but dotGraph is a selection of circles already - so we can drop the selectAll("circle") method in the zoom function. Otherwise the circles won't update.
Related
My implementation for Brush & Zoom functionality in my d3 line chart is not working as expected,
I followed this link - https://bl.ocks.org/EfratVil/92f894ac0ba265192411e73f633a3e2f,
Problems what I am facing is -
chart is not showing all the values, I have 4 data but it only shows 3 data
onClick of dot I am showing the rect which is not moving with the brush functionality
minor thing but chart always goes out of the box
My code sandbox - https://codesandbox.io/s/proud-firefly-xy1py
Can someone point out what I am doing wrong? thanks.
Please suggest me what I am doing wrong, thanks.
Your first point is going behind your clip area. For example, if you right click on the first visible circle and inspect element you will see all 4 circle elements are present in the dom. The first circle element is behind the axis.
This means you have to move your plot to the right. Unfortunately, the way you have coded the chart you have not appended a g element for the main chart and then appended the circles and path to that g element. As a result this has to be done in multiple places.
First we adjust your clip path as:
svg
.append("defs")
.append("SVG:clipPath")
.attr("id", "clip")
.append("SVG:rect")
.attr("width", containerWidth)
.attr("height", height)
.attr("x", 40)
.attr("y", 0);
next we adjust your circles
scatter
.selectAll(".foo")
.data(data)
.enter()
.append("circle")
.attr("class", "foo")
.attr("transform", "translate(40,0)")
and then your line
scatter
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line)
.attr("transform", "translate(40,0)");
You will have to account for this 40 px translate for your other elements as well. Although I am having a hard time destructuring your svg. I think this should give you the idea though. Check the axis matches the time points as well.
Check the code sand box
Update
To make the rectangles move with the brush, you will have to add code to your brushed const function to recalculate the x, y, width and height using the updated scales.
Update2
After going through the codesandbox presented in the comments I was able to add the code to update the rectangles to the brushed const as below to make the rects also move with the brushing:
// update rectangles
scatter
.selectAll(".rect-elements")
.attr("x", d => {
console.log(d);
return xScale(d.startTime) - 12.5;
})
.attr("y", 0)
.attr("width", 24)
.attr("height", height + 5);
Full working Code Sandbox.
What's the best way of adding horizontal gridlines to a d3 gantt chart like in this example? I was originally thinking of making an axis and making the tick marks the length of the chart (as in this example), but this puts them directly in the middle of the chart rectangles.
Is it possible to make the axis ticks into "lanes" around the rectangles, or would it just be easier to plot lines (as done here)?
A solution comes from this issue post on GitHub. Basically just translate the axis group by half the width of the band:
var yScale = d3.scaleBand()
.domain(lanes)
.rangeRound([0, chartHeight], 0.1);
var yGridAxis = d3.axisLeft()
.scale(yScale)
.tickFormat('')
.tickSize(-chartWidth);
chart.append('g')
.attr('class', 'grid')
.attr('transform', function(d) {
return 'translate(0,' + (-yScale.bandwidth()/2) + ')';
})
.call(yGridAxis);
tried to create a scatterplot which can be zoomed but only the axis is getting zoomed and not the data. Not able to figure out whats wrong anybody any help with this one?.
github link of project : scatterplot with zoom
I see two separate issues with the way the zoom is working:
You are not selecting the <circle>s (points in the scatter plot) correctly when zooming. Consequently when you zoom only the axes are changing (as you described). An easy way to fix this is to give each <circle> a class (e.g. class="dot"), and then use that to select them.
First add the class="dot" to each of your circles (line ~140):
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
Then update the function zoom to select them correctly (line ~195):
svg.selectAll(".dot") // <---- select all circles with class "dot"
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.close); });
Right now the zoom only occurs when you try to zoom in on an axis or individual point. If you want a user to be able to zoom in no matter where their mouse is over your scatter plot, you can add a background <rect> that will make sure the zoom event is detected for the SVG.
svg.append("rect")
.style("fill", "#fff")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
Making these two changes fixes the zoom.
I'm using a linear scale chart with zoom/pan functionality to display a large dataset (500+ points). Here's the code I use to construct the x-scale:
x = d3.scale.linear()
.domain([0, data.length-1])
.range([0, w]);
This way all data is squeezed into the chart making it impossible to view the details like in the top part of the image:
I'd like to display the data similar to the bottom chart (and let the user scroll to see more of the data using the pan functionality).
One way to do this is to manipulate the domain of the X scale such that it maps the zoom boundaries to be the min/max domain values that map to the 0-width values of the range. You can then use a clip path to clip/hide that parts of the plot that are drawn outside of the X scale range.
It might make more sense with a jsFiddle example: http://jsfiddle.net/reblace/8TmM9/
In this example, there are 10 squares that are always being drawn. You can inspect the dom to see what I'm talking about. But, there is a clip path that is only wide enough for you to see 4 of them at a time. The X scale maps the input values of 0-9 to the output coordinates. The range is set as 0 to the width required to draw all 10 of the squares, and the domain is set as [0, 9].
var xScale = d3.scale.linear()
.domain(d3.extent(data))
.range([0, width]);
...
var svg = d3.select('#chart').append('svg')
.attr('width', clipWidth)
.attr('height', clipHeight);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", clipWidth)
.attr("height", clipHeight);
...
var g = svg.append("g");
g.selectAll("rect")
.data(data).enter().append('rect')
.attr("class", "area").attr("clip-path", "url(#clip)")
.attr('x', xScale)
.attr('width', rectWidth)
.attr('height', rectHeight)
.style('fill', d3.scale.category20());
Initially, this will draw the first four rectangles in the visible pane. By manipulating the domain so that it is instead [1,10] or [2,11] or even [-1, 8], we can effectively shift the drawn elements left and right so that a different span of the plot is drawn in the visible area.
// Pan Left
xScale.domain([xScale.domain()[0] - 1, xScale.domain()[1] - 1]);
// Pan Right
xScale.domain([xScale.domain()[0] + 1, xScale.domain()[1] + 1]);
This technique is identical whether you are doing it with squares or plots.
Mike Bostock has an example that does this with plots in the manner you are attempting here as well: Focus+Context http://bl.ocks.org/mbostock/1667367
I am using dragdealer JS with D3.js. What i am doing is that when You drag the slider made by dragdealer JS the elements made by D3.js will move like a picture slider.
Here is the code which which i wrote : code.
Now there are two problems with this code:
1) This code is working in FireFox but not in Chrome & IE 10?
2) How to configure the slider so that on one slide, only one tile will move into the view and only one will move out?
The number of tiles or rectangles are not fixed. There can be any number of tiles depending on the user.
Code:
var width = 4000,
height = 200,
margin = 2,
nRect = 20,
rectWidth = (width - (nRect - 1) * margin) / nRect,
svg = d3.select('#chart').append('svg')
.attr('width', width);
var data = d3.range(nRect),
posScale = d3.scale.linear()
.domain(d3.extent(data))
.range([0, width - rectWidth]);
console.log(rectWidth)
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', posScale)
.attr('width', rectWidth)
.attr('height', height);
function redraw(x)
{
svg.transition()
.ease("linear")
.attr("transform", "translate(" + -(x*rectWidth) + ")" );
console.log(-(x*rectWidth));
}
var step = nRect/2;
new Dragdealer('magnifier',
{
steps: step,
snap: true,
animationCallback: function(x, y)
{ console.log(x*10)
redraw(x*step);
}
});
i am trying to devise a way so that the value of steps will change according to the number of tiles.
Please help me.
You had a few problems that I've fixed here: http://jsfiddle.net/SqKZv/1/
In Chrome your svg element needed the height property set
In Chrome/IE, it doesn't appear that you can apply the transform attribute to your SVG element, I'm actually surprised this works in FireFox. I wrapped all of your rect elements in a g element and transformed that.
D3 does dragging very well by itself, so you don't need Dragdealer to do this. In addition to d3.behavior.drag, you can check out d3.svg.brush, specifically these examples of snapping to get what you want:
Brush Snapping http://bl.ocks.org/mbostock/6232537
Brush Snapping II http://bl.ocks.org/mbostock/6232620
You may also want to try out the new D3 feature called brush: https://github.com/mbostock/d3/wiki/SVG-Controls
Here is an example I made using brush to implement a similar feature as you mentioned.
https://github.com/CSE512-14W/a3-chaoyu-aniket