Been away from d3.js for a few months... and I've inherited a simple US map with features that someone else started.
The features are represented by simple dots of varying sizes.
I want to add emanating concentric circles to each dot, similar to the classic Onion example by Mike Bostock: http://bl.ocks.org/mbostock/4503672 (maybe not so ominous looking though)
I've got a block set up here: http://bl.ocks.org/mbostock/4503672
(Not sure why the states aren't rendering correctly in the block, but it probably doesn't matter for this.)
In Mike's example there is only one dot, so I'm have trouble understanding how to translate what he did to what I've got (many dots).
Here's my script:
/**
* Page initialization
*/
$(function() {
renderMap('#map-container');
});
function renderMap(container) {
var width = 960,
height = 500,
active;
var projection = d3.geo.albersUsa()
.scale(960)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var radius = d3.scale.sqrt()
.domain([0, 1e7])
.range([0, 10]);
var path2 = d3.geo.path()
.projection(projection);
// Remove svg, if already exist
d3.select(container).select('svg').remove();
var svg = d3.select(container).append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", width)
.attr("height", height);
//.on("click", reset);
var g = svg.append("g");
queue()
.defer(d3.json, "/mbostock/raw/4090846/us.json")
.defer(d3.json, "dots.json")
.await(function (error, us, centroid) {
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr("class", "state");
//.on('click', click);
g.append('path')
.attr("id", "state-borders")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("d", path)
.attr("class", "mesh");
var dots = g.append("g")
.attr("id", "dots")
.selectAll("path")
.data(centroid.data)
.enter().append("path")
.attr("class", "dot")
.attr("d", path2.pointRadius(function(d) { return radius(d.properties.pool); }));
}
);
}
and the key part of Mike's example for making the rings is:
setInterval(function() {
svg.append("circle")
.attr("class", "ring")
.attr("transform", "translate(" + projection([100, -8]) + ")")
.attr("r", 6)
.style("stroke-width", 3)
.style("stroke", "red")
.transition()
.ease("linear")
.duration(6000)
.style("stroke-opacity", 1e-6)
.style("stroke-width", 1)
.style("stroke", "brown")
.attr("r", 160)
.remove();
}, 750);
how do I get the rings positioned on the dots?
Review the differences between the two methods to learn a little bit more about how functional/declarative programming abstracts away the pain of iterative programming.
Approach with D3 idioms:
fiddle: http://jsfiddle.net/blakedietz/E66eT/1/
update: D3 Way
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
body {
background: #192887;
}
.graticule {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.land {
fill: #007421;
}
.dot {
fill: #c7141a;
}
.ring {
fill: none;
stroke: #c7141a;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
var width = 960,
height = 500;
var projection = d3.geo.mercator()
.center([113, -3])
.scale(1275)
.translate([width / 2, height / 2])
.clipExtent([[0, 0], [width, height]])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule()
.step([5, 5]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
var data = [{x:-8,y:100},{x:-10,y:110},{x: -12,y:120}];
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class","dot")
.attr("transform",translateCircle)
.attr("r",8);
function translateCircle(datum, index)
{
return "translate(" + projection([datum.y, datum.x]) + ")";
};
setInterval(function(){
svg
.selectAll("ring")
.data(data)
.enter()
.append("circle")
.attr("class", "ring")
.attr("transform", translateCircle)
.attr("r", 6)
.style("stroke-width", 3)
.style("stroke", "red")
.transition()
.ease("linear")
.duration(6000)
.style("stroke-opacity", 1e-6)
.style("stroke-width", 1)
.style("stroke", "brown")
.attr("r", 160)
.remove();
}, 750)
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>
</html>
So I didn't create a fully d3 idiomatic approach for this solution, but it will work. If you can get this to work implicitly within a svg.selectAll("circle"/"unique selection"...), etc, that would be even more awesome. I'll work on that in the mean time. Until then there's this more explicitly iterative approach.
With Mike's example you're only appending a single element to the D.O.M. in the setInterval call. In order to expedite the binding process, I've created a projection method which operates on a set of coordinates: the translateCircle will operate on a datum within a collection of coordinates allowing access to the internal attributes of each collection element.
Within each setInterval call the forEach method iterates over the collection of coordinates and then calls the same internals within the setInterval method that was called by Mike originally.
Not So D3
<style>
body {
background: #192887;
}
.graticule {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.land {
fill: #007421;
}
.dot {
fill: #c7141a;
}
.ring {
fill: none;
stroke: #c7141a;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
var width = 960,
height = 500;
var projection = d3.geo.mercator()
.center([113, -3])
.scale(1275)
.translate([width / 2, height / 2])
.clipExtent([[0, 0], [width, height]])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule()
.step([5, 5]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
var data = [{x:-8,y:100},{x:-10,y:110},{x: -12,y:120}];
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class","dot")
.attr("transform",translateCircle)
.attr("r",8);
function translateCircle(datum, index)
{
return "translate(" + projection([datum.y, datum.x]) + ")";
};
setInterval(function(){
data.forEach(function(datum)
{
svg
.append("circle")
.attr("class", "ring")
.attr("transform", translateCircle(datum))
.attr("r", 6)
.style("stroke-width", 3)
.style("stroke", "red")
.transition()
.ease("linear")
.duration(6000)
.style("stroke-opacity", 1e-6)
.style("stroke-width", 1)
.style("stroke", "brown")
.attr("r", 160)
.remove();
})
}, 750)
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>
</html>
Related
I'm aware that starting with v4 d3-zoom swallows up certain events for some reasons I don't fully understand. I've read some discussions about this, and I know that if I stopPropagation() on mousedown, then the zoom behavior won't get a chance to consume the event, and mouseup will consequently fire. The problem with that is that then the zoom doesnt work.
I haven't found a workaround for the case of needing to handle the mouseup event AND still have the zoom work. I'm particularly interested in the dragging case only. When the user does mousedown and starts to drag the canvas, I want to change the cursor to a clenched-hand, and when the user stops dragging and lets go of the mouse I want to change the cursor back.
How is this possible to do with the new d3-zoom behavior without resorting to a timeout? 'click' event is also not an option since that doesn't fire if there's a mousemove event in between.
I am concluding from your question that you are not able to track mouseup event after dragging the canvas. If that is the case then we can use events provided by "zoom" functionality - zoomstart and zoomend.
We can simply add that to zoom behavior and track when the user starts zoom and ends zoom. And in this way we can easily change cursor property.
Please find below code snippet and working code as well. Please let me know if i am missing anything.
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
},
width = 460 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var zoom = d3.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed)
.on("start", function() {
document.getElementsByTagName("svg")[0].style.cursor = "grab";
})
.on("end", function() {
document.getElementsByTagName("svg")[0].style.cursor = "default";
})
console.log(zoom.scaleExtent()[0], zoom.scaleExtent()[1]);
var drag = d3.drag()
.subject(function(d) {
return d;
})
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
var slider = d3.select("body").append("p").append("input")
.datum({})
.attr("type", "range")
.attr("value", zoom.scaleExtent()[0])
.attr("min", zoom.scaleExtent()[0])
.attr("max", zoom.scaleExtent()[1])
.attr("step", (zoom.scaleExtent()[1] - zoom.scaleExtent()[0]) / 100)
.on("input", slided);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom);
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var container = svg.append("g");
container.append("g")
.attr("class", "x axis")
.selectAll("line")
.data(d3.range(0, width, 10))
.enter().append("line")
.attr("x1", function(d) {
return d;
})
.attr("y1", 0)
.attr("x2", function(d) {
return d;
})
.attr("y2", height);
container.append("g")
.attr("class", "y axis")
.selectAll("line")
.data(d3.range(0, height, 10))
.enter().append("line")
.attr("x1", 0)
.attr("y1", function(d) {
return d;
})
.attr("x2", width)
.attr("y2", function(d) {
return d;
});
dots = [{
x: 100,
y: 100,
}]
dot = container.append("g")
.attr("class", "dot")
.selectAll("circle")
.data(dots)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.call(drag);
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
function zoomed(event) {
const currentTransform = d3.event.transform;
container.attr("transform", currentTransform);
slider.property("value", currentTransform.k);
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
function slided(d) {
zoom.scaleTo(svg, d3.select(this).property("value"));
}
.dot circle {
fill: lightsteelblue;
stroke: steelblue;
stroke-width: 1.5px;
}
.dot circle.dragging {
fill: red;
stroke: brown;
}
.axis line {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
</style>
<title></title>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
</body>
</html>
var zoom = d3.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed)
.on("start", function() {
document.getElementsByTagName("svg")[0].style.cursor = "grab";
})
.on("end", function() {
document.getElementsByTagName("svg")[0].style.cursor = "default";
})
For one of my projects, I want to create a real time scatter plot using D3. The sample rate needs to be around 30 updates per seconds. I managed to create a graph that updates itself but I am facing two issues :
1) I cannot figure out how to remove data using the exit() and remove() function
2) The page freezes up pretty quickly
I am also wondering if it was the most efficient way to create a real-time scatterplot. I have adapted code from https://bost.ocks.org/mike/path/
I have looked throughout blogs and tutorials to no avail. Any help would be deeply appreciated. Thanks !
EDIT : As suggested by rioV8, I ported the script to d3.v5. A few things changed but the problem is the same. If you can give me suggestion on how to make this real-time scatterplot works, that would be amazing. I realized that there is no example of such graph on internet, only line plot. Thanks !
Here is my code.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v5.js"></script>
<script>
var n = 40,
random = d3.randomNormal(0, .2)
var data = [{x:10,y:Math.random()},{x:15,y:Math.random()},{x:20,y:Math.random()},{x:25,y:Math.random()}];
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 20, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleLinear()
.domain([0, n - 1])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, 1])
.range([height, 0]);
g.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + y(0) + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y));
var points = svg.selectAll("dot")
.data(data)
points.enter().append("circle")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.transition()
.duration(500)
.ease(d3.easeLinear)
.on("start", tick);
points.exit().remove();
function tick() {
// Push a new data point onto the back.
data = [{x:10,y:Math.random()},{x:15,y:Math.random()},{x:20,y:Math.random()},{x:25,y:Math.random()}];
// Redraw the line.
console.log("bob")
points.data(data).enter().append("circle")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.transition()
.duration(500)
.ease(d3.easeLinear)
.on("start", tick);
}
</script>
Im making a graph that fills the circle by the percentage of the number of a certain product by the total of products avaible im almost close to what i need only problem is i cant figure it out how to change the left-over part of the donut arc.
this is the code
http://jsfiddle.net/LBzx7/345/
I can change the color the circle of the % of the product on this line
.attr("fill", "#F1F1F1");
, but what is left is the same color of the page background, i need to be able to change the color of that. Any ideas?
Here's a code snippet with the requirement fulfilled.
var dataset = {
hddrives: [90,10],
};
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#2DA7E2", "red"]);
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 70);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
//Draw the Circle
svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 65)
.attr("fill", "#F1F1F1");
var path = svg.selectAll("path")
.data(pie(dataset.hddrives))
.enter().append("path")
.attr("class", "arc")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc);
svg.append("text")
.attr("dy", "0em")
.style("text-anchor", "middle")
.attr("class", "inside")
.text(function(d) { return '56%'; });
svg.append("text")
.attr("dy", "1.5em")
.style("text-anchor", "middle")
.attr("class", "data")
.text(function(d) { return '53GB / 123GB'; });
.inside {
font-family: 'Roboto Condensed', sans-serif;
font-size:30px;
}
.data {
font-size:12px;
color:grey;
}
.arc {
stroke: #fff;
}
.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.7/d3.min.js"></script>
Code changes:
Changed the color scale's range to .range(["#2DA7E2", "red"]);
With this, .attr("fill", function(d, i) { return color(i); }) will find appropriate color based on i. (as it was just one color before, the color was being repeated).
Got rid the opacity of the arcs i.e. removed the following line
(as this was causing the "left-over" part to have an opacity of 0)
.style("opacity", function(d, i) { return i == dataset.hddrives.length - 1 ? 0 : 1; })
Hope this helps. :)
how to add a label in the center of path programmatically without using the BBOX method because it does not work with banana shapes
d3.json("mapgeo.json", function(json) {
//Bind data and create one path per GeoJSON feature
paths = g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr('name', function(d) {
return d.properties.name.toLowerCase();
})
.attr("d", path)
.attr("id", function(d, i) { return 'polygon'+i;})
.style("fill", "steelblue");
for(var i=0;i<paths[0].length;i++){
var pp = paths[0][i].__data__.properties;
svg
.append('text')
.attr("x", 145)
.attr("dy", 105)
.append("textPath")
.attr("xlink:href","#polygon"+i)
.text(paths[0][i].__data__.properties.temperature+' C°');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="400" height="300">
<g>
<path name="cf40" d="M590.3383838385344,295.20151514932513 C 756 327,756 327, 878.5818181820214,279.5361111164093L822.186363636516,527.0494949556887L728.1939393933862,555.2472222223878Z" id="polygon2" style="fill: steelblue;" transform="translate(-500,-260)"></path>
</g>
<text x="145" dy="105"><textPath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#polygon2">CF40</textPath></text>
</svg>
(I confess that I quite didn't understand what you want to achieve with your code, so, I'm going to address specifically your question's title: "how to add a label in the center of a path").
D3 have a handy function for locating the center of the path, called path.centroid:
Returns the projected planar centroid (typically in pixels) for the specified GeoJSON object. This is handy for, say, labeling state or county boundaries, or displaying a symbol map.
You can use it to position your labels:
.attr("x", function(d) {
return path.centroid(d)[0];
})
.attr("y", function(d) {
return path.centroid(d)[1];
})
Here is a demo with a USA map (just found the code online). I'm locating the center of each path using centroid and labelling it with "foo":
var width = 500,
height = 400;
var projection = d3.geoAlbersUsa()
.scale(700)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("https://dl.dropboxusercontent.com/u/232969/cnn/us.json", function(error, us) {
svg.selectAll(".state")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr('class', 'state');
svg.selectAll(".stateText")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("text")
.attr("x", function(d) {
return path.centroid(d)[0];
})
.attr("y", function(d) {
return path.centroid(d)[1];
})
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.text("foo")
});
.state {
fill: none;
stroke: black;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
So I've just started my D3 journey, and wanted to ask about how one would create a small 1px border around the chart.
I created the variables "border" and "bordercolor" and then I added .attr("border",border) to the var svg = d3.select("body") portion of my code. It doesn't crash, but I get no border either.
I guess the question is how do i add this border, and if someone could explain why what i did is wrong.
<script type="text/javascript">
//Width and height
var w = 800;
var h = 400;
var padding = 20;
var border=1;
var bordercolor='black';
var dataset = [
[5, 20], [480, 90], [250, 50], [100, 33], [330, 95],[-50,-100],[50,-45],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88],[-480, 90], [3,-90]
];
// create scale functions
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain( [-100, d3.max(dataset, function(d) { return d[1]; })] )
.range([2,5]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("border",border)
;
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 3);
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[0] + "," + d[1];
})
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "red");
</script>
Use the style attribute to place an outline around the svg:
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("style", "outline: thin solid red;") //This will do the job
.attr("width", w)
.attr("height", h);
The svg var is just a container. You need to add a path or element to the container and then give it the stroke color and width you want for your border. There is more than one way to do this. In this gist I did it by adding a rect with the following values:
var borderPath = svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", h)
.attr("width", w)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);
IMHO it's better to keep separated shape from style instructions:
.append("rect")
.attr("x", 5)
.attr("y", 5)
.attr("height", 40)
.attr("width", 50)
.attr("class","foo")
...
CSS:
svg rect.foo {
fill: white;
stroke-width: 0.5;
stroke: grey;
}
Simply use css:
svg {
border:1px solid black;
}
If X and Y Axis are used, other option is to use tickSizeOuter()
Example:
var yAxis = d3.axisLeft(y).ticks(5).tickSizeOuter(-width);
var xAxis = d3.axisBottom(x).ticks(5).tickSizeOuter(-height);