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.
Related
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.
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.
I can successfully display some points on a openlayers basemap using d3.js however I want to actually display icons (at the moment maki png icons) instead of an svg point.
is it possible to load a png/jpg/svg image to a map using d3.js?
So far I can load the icon directly onto the svg but all the markers locate on the same spot so I think I'm having a problem converting the coordinates properly.
var feature = svg.selectAll("path")
.data(amenities.features)
.enter()
.append("svg:image")
.attr("xlink:href", "maki/renders/post-18#2x.png")
.attr("x", function(d, i) {return amenities.features[i].geometry.coordinates[0]})
.attr("y", function(d, i) {return amenities.features[i].geometry.coordinates[1]})
.attr("width", "20")
.attr("height", "20")
.attr("class", "amenity");
Previously I have been able to create an svg with image background inside it using a 'pattern' to show the image so that is also a possibility but I couldn't translate the code to use it with the geographic aspect of d3.
I know I'm writing the icons at the moment to the 'svg' div, so they don't transform correctly when I zoom in and out. I'm aiming to write the images to the 'g' div, as in have code like:
var feature = g.selectAll("path")
but when I use this line, the elements appear on the document but the icons don't actually render on the map.
Thanks in advance.
There a few issues here. First, I'm not sure you fully grasp how d3 selections works, as indicated by the fact that you are binding amenities.features to your selection and then accessing it for the x and y attributes via an index. See How Selections Work for more details on this. In addition, you need to translate the geographic coordinates of the features to screen coordinates by passing them through your geographic projection function. This should get you close:
// more projections: https://github.com/d3/d3-geo-projection/
var projection = d3.geoAlbers();
var amenities = svg.selectAll('.amenities')
.data(amenities.features);
amenities.enter().append('image');
amenities
.attr("class", "amenities")
.attr("xlink:href", "maki/renders/post-18#2x.png")
// The data is already bound so use it instead of the index. Als,
// you need to translate geo coordinates to screen coordinates by
// passing them through your projection function.
.attr("x", function(d,i) {return projection(d.geometry.coordinates)[0];})
.attr("y", function(d,i) {return projection(d.geometry.coordinates)[1];})
.attr("width", "20")
.attr("height", "20")
I don't think I appropriately used groups but I think the key is having the transform then translate thing in there.
So my example is http://bl.ocks.org/mpmckenna8/b87df1c44243aa1575cb.
But because I didn't use groups properly I don't know that the icons would handle zooming like you seem to want. In my example I just append the images to the circles I've created.
.attr("transform", function(d) { return "translate(" + projection(d.geometry.coordinates) + ")"; })
.attr('opacity',.3)
.attr('fill', '#fad959')
Here's the fiddle.
When I use the brush, the colored areas start off beyond the x axis defined.
For the line in the graph, I got a suggestion in this SO to add clip-path: url(#clip) to the line css. It works. After applying that, the line starts exactly from the 0 of x when using the brush.
But when I apply the same logic to the css of the .area.above and .area.below, it doesn't work.
The areas are clipped correctly but only one is actually displayed...from inspecting the elements in the browser developer tools, one of the areas is apparently overlaying the other.
Some one help me where I'm making the mistake?
Thanks in advance.
Here's a fiddle which I think does what you want: http://jsfiddle.net/henbox/jzPaq/
The problem was that you can only have one clip-path defined for an element. So adding the rectangular clipPath:
...append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
was over-writing the clip-below and clip-above ones (in <path class="area below"...> and <path class="area above"...> respectively.
The solution I used was based on this info: http://apike.ca/prog_svg_clip.html
For the 'below' I used the intersection of the rectangular (#clip) and 'below' clip paths, like this:
Give the path element an id (clipbelowshape):
focus.append("clipPath")
.attr("id", "clip-below")
.append("path")
.attr("id", "clipbelowshape")
.attr("d", area.y0(height));
Create the intersect of clip clipPath with clipbelowshape:
var clipbelowintersect = focus.append("clipPath")
.attr("id", "clipbelowintersect")
.attr("clip-path", "url(#clip)");
clipbelowintersect.append("use")
.attr("xlink:href", "#clipbelowshape");
Use the new intersect clip path
focus.append("path")
.attr("class", "area below")
.attr("clip-path", "url(#clipbelowintersect)")
.attr("d", area);
Do the same thing with the above path
I have donut chart, check the jsfiddle. There is text in side donut chart. Right now the text inside donut chart is shows foreground color's percentage. What I want is when I click on text of foreground percentage, I should get midground's percentage with flip transition, something like this flip action in css. I tried on click on this code, but I have no idea how to use
var text = svg.append("text")
.text('0%')
.attr("text-anchor", "middle")
.style("font-size",fontSize+'px')
.attr("dy",fontSize/3)
.attr("dx",2);
How can I do this in d3?
You can achieve this effect with chained transitions that scale in one dimension:
.on("click", function() {
d3.select(this)
.transition().duration(1000)
.attr("transform", "scale(0,1)")
.transition().duration(1000)
.attr("transform", "scale(1,1)")
.text("foo");
});
Complete example here.