I can't work out why my chart doesn't update when I change selection.
Here's my code so far on Plunker
The code that seemingly doesn't work is:
dropDown.on("change", function() {
d3.selectAll("circle")
.data(orgData[this.value])
.attr("y", function(d) {
return height - price_scale(d.value);
})
.attr("height", function(d) {
return price_scale(d.value);
});
});
The code comes from the answer to a previous query that I made:
How to use d3 filter and update function to toggle between data selections
That code worked because the update function tweaked the parameters of a circle svg object. Is there a parameter I've not factored in with rect objects?
circle elements don't have height or y attributes, they have cx, cy and r attributes.
Alternatively maybe you meant to select rect elements which do have height and y attributes.
Related
I'm trying to move an element in D3, in order to correspond to a circle underneath. Basically, when the user zooms on the page, the circles shrink (which allows them to remain visually appealing and separated).
I want to build a function that fires with the zoom event, that keeps the images centered within the circles. The circles are centered on their center points. However, as the images shrink, they appear to move to the left because their anchors are in the top-left corner.
I need a solution that might involve adding their sacrificed width and height to their relative "x" and "y" attributes. How would I implement a function like this? Or is there a better way?
The blockbuilder is here: http://blockbuilder.org/KingOfCramers/125cc79bce7dea48b21786b37302d258
Here is the relevant bit of code (the icon variable is the starting width of the image):
function zoom() {
var iconMove = icon/d3.event.transform.k;
g.attr("transform", d3.event.transform)
d3.selectAll(".storyImages")
.attr("width", `${iconMove}px`)
.attr("height", `${iconMove}px`)
.attr("x", // Keep this centered)
.attr("y", // Keep this centered)
d3.selectAll("circle")
.attr("r", function(){
return cirSize/d3.event.transform.k
})
}
Thanks for any help you can provide!
If you can position them to start, you can update them the same way on zoom, just with the new width/height of each item. You initially append each item with these attributes:
.attr("x", (d) => projection([d.lat,d.lon])[0] - icon/2)
.attr("y", (d) => projection([d.lon,d.lat])[1] - icon/2)
.attr("width", `${icon}px`)
.attr("height", `${icon}px`)
Which offsets the icon from the x,y values returned by the projection by half the icon's width and height - centering it on the projected point. Note: Your x value is set with d.lat, d.lon rather than d.lon, d.lat, also, your csv has lng, rather than lon as a header, so d.lng should be used).
To keep the icon centered on the point, just update the icon using the new icon width/height (which in your case is located in iconMove) and the new projected point:
.attr("x", (d) => projection([d.lng,d.lat])[0] - iconMove/2)
.attr("y", (d) => projection([d.lng,d.lat])[1] - iconMove/2)
.attr("width", iconMove)
.attr("height", iconMove);
Here's an updated block (I wasn't able to figure out how to save a new block builder block).
I've made this (SOLVED) chart and I can't figure out how to update clipPath.
I've commented out
y.domain(d3.extent(data, function(d) { return d[city]; }));
in the change function because it didn't update the clipPath alongside the yAxis, I've tried to assign a class to the clipPath and update the y and height attribute in the change function but nothing seem to work.
Would greatly appreciate if anyone knows what I'm doing wrong or why it's not possible to update clipPath.
You set the new domain, but you also need to update those things based on the domain (both clip-paths and the area's y0):
y.domain(d3.extent(data, function(d) { return d[city]; })); // fix clipPath
svg.select('#clip-below>rect')
.attr("y", y(1))
.attr("height", height - y(1));
svg.select('#clip-above>rect')
.attr("height", y(1));
area.y0(y(1));
Here's an updated bl.ocks.
I have the following adapted d3.js visual and I'm unable to work out why the transition does not fire. The ars should rotate around to different sizes when the radio button is clicked.
It seems that clicking the radio button is changing the titles of the arc (if I hover the cursor over each)
Is this section of code to blame?
// check if svg group already exists
var svg = d3.select("#sunGroup");
if (svg.empty()) {
var svg = d3.select("#sunBurst")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr({
'transform': "translate(" + width / 2 + "," + height * .52 + ")",
id: "sunGroup"
});
}
Here is the full (not) working example:
https://plnkr.co/edit/hpvREU?p=preview
This is the transition I'm trying to hold onto: plnkr.co/edit/NnQUAp?p=preview
What I trying to do is move the logic at line 128 (starting d3.selectAll("input").on("change", function change() {...) out of this function
An easy fix to your problem is to remove all children of the SVG whenever you switch data types:
d3.select("#sunBurst").selectAll('*').remove()
That way, you are binding the new data to new elements. You can also remove the svg.empty() and d3.select('#sunGroup') code. This simple fix lets you switch between pie charts, and is in the spirit of the code you currently have. Here's the users pie chart.
However, this may not be the best way to do what you're trying to achieve. As a reference, see Mike Bostock's General Update Pattern series (link is to first in the series) for how to update your SVG.
I have a map of the US, with cities plotted. I'm trying to use d3-tip to provide tooltips to my cities on hover. The problem I'm running into is that d3-tip isn't accounting for the projection, and I'm not sure how to apply it.
The projection I'm using:
var projection = d3.geo.mercator()
.scale(150)
.translate([width / 2, height / 1.5]);
I apply it to my circle elements by the following:
.attr("cx", function(d) {
return projection([d.Longitude, d.Latitude])[0];
})
.attr("cy", function(d) {
return projection([d.Longitude, d.Latitude])[1];
})
Problem is d3-tip doesn't seem to take x and y attributes, it grabs them from the node issuing the event and doesn't seem to account for it having the projection applied; which doesn't make sense to me.
I usually do tooltips with d3 by appending a div with absolute positioning and hiding it in the body.
Then on mousing over an element, I move the div to the location of the mouse pointer and unhide it. I then hide the div once the mouseout event occurs.
This allows me to control X and Y as well as the styling and text of the tooltip.
Here's a great example: https://gist.github.com/biovisualize/1016860
Here is jsfiddle of a Sankey diagram:
I am trying to modify colors of the links so that the color of each link is actually gradient from its source node color to its target node color. (it is assumed that opacity will remain 0.2 or 0.5 depending whether a mouse hovers or not over the link; so links will remain a little "paler" than nodes)
I took a look at this nice and instructive example, which draws this gradient filled loop:
However, I simply couldn't integrate that solution to mine, it looks too complex for the given task.
Also, note that links in original Sankey diagram move while node is being dragged, and must display gradient even in those transitory states. A slight problem is also transparency of links and nodes, and order of drawing. I would appreciate ideas, hints.
#VividD: Just saw your comment, but I was about done anyway. Feel free to ignore this until you've figured it out on the own, but I wanted to make sure I knew how to do it, too. Plus, it's a really common question, so good to have for reference.
How to get a gradient positioned along a line
With the caveat for anyone reading this later, that it will only work because the paths are almost straight lines, so a linear gradient will look half-decent -- setting a path stroke to a gradient does not make the gradient curve with the path!
In initialization, create a <defs> (definitions) element in the SVG and save the selection to a variable:
var defs = svg.append("defs");
Define a function that will create a unique id for your gradient from a link data object. It's also a good idea to give a name to the function for determining node colour:
function getGradID(d){return "linkGrad-" + d.source.name + d.target.name;}
function nodeColor(d) { return d.color = color(d.name.replace(/ .*/, ""));}
Create a selection of <linearGradient> objects within <defs> and join it to your link data, then set the stop offsets and line coordinates according to the source and target data objects.
For your example, it probably will look fine if you just make all the gradients horizontal. Since that's conveniently the default I thought all we would have to do is tell the gradient to fit to the size of the path it is painting:
var grads = defs.selectAll("linearGradient")
.data(graph.links, getLinkID);
grads.enter().append("linearGradient")
.attr("id", getGradID)
.attr("gradientUnits", "objectBoundingBox"); //stretch to fit
grads.html("") //erase any existing <stop> elements on update
.append("stop")
.attr("offset", "0%")
.attr("stop-color", function(d){
return nodeColor( (d.source.x <= d.target.x)? d.source: d.target)
});
grads.append("stop")
.attr("offset", "100%")
.attr("stop-color", function(d){
return nodeColor( (d.source.x > d.target.x)? d.source: d.target)
});
Unfortunately, when the path is a completely straight line, its bounding box doesn't exist (no matter how wide the stroke width), and the net result is the gradient doesn't get painted.
So I had to switch to the more general pattern, in which the gradient is positioned and angled along the line between source and target:
grads.enter().append("linearGradient")
.attr("id", getGradID)
.attr("gradientUnits", "userSpaceOnUse");
grads.attr("x1", function(d){return d.source.x;})
.attr("y1", function(d){return d.source.y;})
.attr("x2", function(d){return d.target.x;})
.attr("y2", function(d){return d.target.y;});
/* and the stops set as before */
Of course, now that the gradient is defined based on the coordinate system instead of based on the length of the path, you have to update those coordinates whenever a node moves, so I had to wrap those positioning statements in a function that I could call in the dragmove() function.
Finally, when creating your link paths, set their fill to be a CSS url() function referencing the corresponding unique gradient id derived from the data (using the pre-defined utility function):
link.style("stroke", function(d){
return "url(#" + getGradID(d) + ")";
})
And Voila!