I'm working on a map based on a mbostock example (full example is at https://bl.ocks.org/mbostock/899711). It superimposes several d3.js circles at various points on a google map. I'd like to add a second set of circles slightly offcentered from the original points, so that each point has a red and a blue circle.
I'm not sure about how the circles themselves are being drawn, though. There is a transform function that is applied:
function transform(d) {
d = new google.maps.LatLng(d.value[1], d.value[0]);
d = projection.fromLatLngToDivPixel(d);
return d3.select(this)
.style("left", (d.x - padding) + "px")
.style("top", (d.y - padding) + "px");
}
and applied to each point using each. If I attempt to draw both circles by simply adding another set of circles to the point where the circles are appended:
marker.append("circle")
.attr("r", 4.5)
.attr("cx", padding)
.attr("cy", padding)
.append("circle")
.attr("r", 4.5)
.attr("cx", padding + 30)
.attr("cy", padding + 30);
only one set of circles ends up being drawn. I'm guessing this has to do with the transform function only being applied to the last set of circles that is defined? How can I add another set of circles to the map?
Your code appends circles to circles. This results in invalid SVG, circles can't be children of circles.
Don't chain the .append.
marker.append("circle")
.attr("r", 4.5)
.attr("cx", padding)
.attr("cy", padding);
marker.append("circle")
.attr("r", 4.5)
.attr("cx", padding + 30)
.attr("cy", padding + 30);
Related
Suppose we wanted to make a list-like visual. Setting the y logic for the circles can be as simple as:
var data = [0,1,2,3,4,5,6,7,8,9];
var yScale = d3.scaleLinear()
.range([height,0])
.domain([0,9]);
svg.selectAll(null)
.data(data)
.enter()
.append('circle')
.attr('cy', function(d) { return yScale(d) })
.attr('cx', 100)
.attr('r', 10)
.style('fill', "#a6a6a6");
However, suppose we wanted to go for some style points and arrange the circles not in a blocky / tabular arrangement but rather arrange them about a circle or arc. I had this result in mind (only concerned with the outer circles):
While I think d3 does have trigonometric functions, I have never seen them used in pixel coordinates. I'd imagine the pseudo-code to be something like:
var semiCircleScale = d3.?????
.range([250 degrees, 110 degrees])
.domain([0,9]);
svg.selectAll(null)
.data(data)
.enter()
.append('circle')
.attr('cy', function(d) { return semiCircleScale(d) })
.attr('cx', 100)
.attr('r', 10)
.style('fill', "#a6a6a6");
Question
Is anyone familiar with using circle / arc scales for use with x,y logic for appending shapes? Or is there an easier/less-math-intensive way?
So the idea is to create 2 different path of arc and then calculate the circumference and place the circles along with.
d3.svg.arc()
.append("path")
.attr("d", arc1)
Here is a fiddle link with minimum code to establish the idea
https://jsfiddle.net/Dibyanshu/g03p6sxj/
I am trying to convert a bubble chart from d3v3 to v4. Running into x,y,d missing variables?
In this version -- a rect is applied to the svg - and then a circle is cut -- so its like an inverse bubble chart.
I am keen to work out a set radius for the chart as a maxium -- if it should act like a score between 0 and 100? What kind of math to apply that a max radius has been reached to signify that the value is very big?
I also tried to have the svg mask adapt - if the browser or its container changed size -- ideally would want it to response during the change - rather than resizeEnd
//version 3
https://jsfiddle.net/8ag1vf6e/1/
//current version 4
https://jsfiddle.net/d56g9r0y/
// filters go in defs element
var defs = innversebubble.append("defs");
var mask = defs.append("mask")
.attr("id", "myMask");
mask.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.style("fill", "white")
.style("opacity", 1);
var invisiblebubble = mask.append("circle")
.data(data);
//create a fixed bubble first
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", function(d) {
return d.value - 20;
});
//now mask the fixed circle
var masker = defs.append(function() {
return mask.node().cloneNode(true)
})
.attr("id", "myMaskForPointer")
.select("rect")
.style("fill", "white")
.style("opacity", 1);
invisiblebubble
.attr("r", 10);
//apply the rest of the chart elements
var rect = innversebubble
.attr("class", "series")
.append("g")
.attr("transform", "translate(0,0)")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.attr("mask", "url(#myMask)")
.style("fill", backcolor)
.style("opacity", backopacity);
//animate this circle
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.transition()
.duration(1800)
.attr("r", 10)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
latest jsfiddle - 15th June -- needs fixing
https://jsfiddle.net/xmrtahns/
"I am keen to work out a set radius for the chart as a maxium -- if it should act like a score between 0 and 100? What kind of math to apply that a max radius has been reached to signify that the value is very big?
I also tried to have the svg mask adapt - if the browser or its container changed size -- ideally would want it to response during the change - rather than resizeEnd"
I've fixed the conversion and the data source - but still need issues to resolve.
var backcolor = $this.data("color");
var backopacity = $this.data("opacity");
var width = $this.data("width");
var height = $this.data("height");
var data = [{
"label": $this.data("label-name"),
"centralLabel": $this.data("central-label"),
"xPer": $this.data("displace-left"),
"yPer": $this.data("displace-top"),
"value": $this.data("bubble-value")
}];
http://jsfiddle.net/hLymw8et/2/
--I am keen to work out a set radius for the chart as a maximum -- if it should act like a score between 0 and 100?
--What kind of math to apply that a max radius has been reached to signify that the value is very big?
--I also tried to have the svg mask adapt - if the browser or its container changed size -- ideally would want it to response during the change - rather than resizeEnd –
I am working on a D3 Graph. My graph has circles with no fill color which look like this:
var circles = svg.selectAll("circle")
.data(x.ticks(6))
.enter().append("circle")
.attr("r", function (d) { return radius(d); })
.style("fill", "none")
.style("stroke", "black")
.style("stroke-dasharray", "3,3")
.style("stroke-width", "1px")
I have added a mouse event to that circle so that whenever someone hovers over it, the circle would get larger width:
.on('mouseenter', function (a, i) {
d3.select(this)
.style("stroke-dasharray", "0")
.style("stroke-width", "3px")
})
However, the width of the circle is too small to be easily touchable without extra effort. What would be a good and efficient solution to make the hit slop bigger so that the mouseevent would trigger with ease?
The best option is control the minimum circle radius(the radius where it is easily touchable).That means, if a circle is not easily touchable when radius < 5px then keep minimum radius as 5px.
http://jsfiddle.net/pPMqQ/146/
I'm developing a bubble chart application that is a derivative of a force chart to provide a little bit of movement.
Here is some of the code. As I create the svg - I also set up the viewBox - which I think could be used to adjust the size of the chart - but I've not been able to adjust the ratios properly to get the correct scaling.
I've also added here the code that adds the nodes - as the node size is calculated - its using a scale variable to affect the size of the orbs.
var svg = d3.select(selector)
.append("svg")
.attr("class", "bubblechart")
.attr("width", parseInt(w + margin.left + margin.right,10))
.attr("height", parseInt(h + margin.top + margin.bottom,10))
.attr('viewBox', "0 0 "+parseInt(w + margin.left + margin.right,10)+" "+parseInt(h + margin.top + margin.bottom,10))
.attr('perserveAspectRatio', "xMinYMid")
.append("g")
.attr("transform", "translate(0,0)");
methods.force = d3.layout.force()
.charge(1000)
.gravity(100)
.size([methods.width, methods.height])
var bubbleholder = svg.append("g")
.attr("class", "bubbleholder")
var bubbles = bubbleholder.append("g")
.attr("class", "bubbles")
var labelbubble = bubbleholder.append("g")
.attr("class", "labelbubble")
// Enter
nodes.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", 1)
.style("fill", function (d) { return methods.fill(d.label); })
.call(methods.force.drag);
// Update
nodes
.transition()
.delay(300)
.duration(1000)
.attr("r", function (d) { return d.radius*scale; })
// Exit
nodes.exit()
.transition()
.duration(250)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", 1)
.remove();
I have the remaining issues
scaling is an issue
I've set the width/heights via data attributes - currently I have a scaling variable set to adjust the size of the orbs depending on the width of the chart. I would like to find a more scientific way of ensuring the chart is resized accordingly and also that the elements always remain central (don't become obscured).
ensuring the smaller elements are on top of the big elements
I've also noticed that small objects may randomly fall underneath larger orbs, is there a way to organize the rendering of the orbs dependant on size, so bigger elements always sit at the bottom layer.
I've solved the second problem by sorting the data via value.
http://jsfiddle.net/pPMqQ/149/
data = data.sort(function(a, b) {return b.value - a.value})
Currently I have a scale variable depending on the width of the chart - var scale = methods.width*.005;
but its not very scientific per say
If the chart is 150 width
http://jsfiddle.net/pPMqQ/150/
the chart renders - but the bubbles no longer fit in the space.
the chart at 250 px
http://jsfiddle.net/pPMqQ/151/
I have a map where circles (origin of people) appear when clicking on a legend.
Additionally, it is possible to zoom in, and then, circles (and country path) are transformed (using d3.behavior.zoom).
Though, if I first zoom in, and then click on the legend, circles do not appear at the right places. How can I solve this problem and append them at the right coordinates (within the zoomed map).
Any ideas? I'm sure the solution is not that difficult, but I'm stucked.
See (http://wahrendorf.de/circlemapping/world_question.html) for an example.
Thanks,
Morten
You need to take into account d3.event.translate and d3.event.scale when you draw the circles. The easiest way to do this is to factor out your zoom function so that it may be called by the circle drawing function.
var translate = [0,0];
var scale = 1;
var zoom_function = function() {
canvas.selectAll("path")
.attr("transform","translate("+translate.join(",")+")scale("+scale+")");
canvas.selectAll("circle.origin")
.attr("transform","translate("+translate.join(",")+")scale("+scale+")")
.attr("r", function(d) { return radius/scale; });
};
var zoom = d3.behavior.zoom().scaleExtent([1,6])
.on("zoom",function() {
translate = d3.event.translate;
scale = d3.event.scale;
zoom_function();
});
// ... The rest of the code ...
canvas.append("text")
.text("show circles")
.attr("x", 30 ) .attr("y", 480 )
.attr("dy", ".35em")
.on("click", function(d) {
/// load data with long/lat of circles
d3.csv("./World_files/places_q.csv", function(error, origin) {
canvas.selectAll("circle.origin").remove();
canvas.selectAll("circle.origin")
.data(origin)
.enter()
.append("circle")
.attr("cx", function(d) {return projection([d.originlong, d.originlat])[0];})
.attr("cy", function(d) {return projection([d.originlong, d.originlat])[1];})
.attr("r", 2)
.style("fill", "red")
.style("opacity", 0.5)
.attr("class", "origin");
// Call the zoom function here to fix the placement of the circles.
zoom_function();
});
});
You will need to track the last known d3.event.translate and d3.event.scale values since they will be undefined when you are drawing the circles.