I'm wrestling with a problem of a brush not being removed correctly on a bar chart. You can see the Bl.ock here and see what's not working correctly.
In short, the brush highlights the bars that have been selected by the brush, as well as snaps to the edge of the rect to make selecting spans of time easier (there's a secondary bug here where the brush snapping isn't quite mapping correctly to the dates -- you'll see this if you try to draw the brush up to the edge of the barchart). Somewhere along the way (maybe with the rect snapping?) the background click-to-remove-brush feature stopped working (it now selects a single year span, although doesn't highlight the rect correctly). To make it easier for users, I wanted to add a button that a user can click to remove the brush when they're done (the resetBrush() function below).
My understanding was the brush selection can be cleared with brush.extent(), but when you clear the extent you then have to redraw the brush. I thought I was doing that correctly, but alas, I'm running into some problem somewhere that I can't seem to track down. Any pointers on where I'm tripping up would be greatly appreciated!
Code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: sans-serif;
color: #000;
text-rendering: optimizeLegibility;
}
.barchart {
z-index: 30;
display: block;
visibility: visible;
position: relative;
padding-top: 15px;
margin-top: 15px;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.resize path {
fill: #666;
fill-opacity: .8;
stroke: #000;
stroke-width: 1.5px;
}
.brush .extent {
stroke: #fff;
stroke-opacity: .6;
stroke-width: 2px;
fill-opacity: .1;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
brushYearStart = 1848;
brushYearEnd = 1905;
// Scales
var x = d3.scale.ordinal().rangeRoundBands([0, width - 60], .1);
var y = d3.scale.linear().range([height, 0]);
// Prepare the barchart canvas
var barchart = d3.select("body").append("svg")
.attr("class", "barchart")
.attr("width", "100%")
.attr("height", height + margin.top + margin.bottom)
.attr("y", height - height - 100)
.append("g");
var z = d3.scale.ordinal().range(["steelblue", "indianred"]);
var brushYears = barchart.append("g")
brushYears.append("text")
.attr("id", "brushYears")
.classed("yearText", true)
.text(brushYearStart + " - " + brushYearEnd)
.attr("x", 35)
.attr("y", 12);
d3.csv("years_count.csv", function (error, post) {
// Coercion since CSV is untyped
post.forEach(function (d) {
d["frequency"] = +d["frequency"];
d["frequency_discontinued"] = +d["frequency_discontinued"];
d["year"] = d3.time.format("%Y").parse(d["year"]).getFullYear();
});
var freqs = d3.layout.stack()(["frequency", "frequency_discontinued"].map(function (type) {
return post.map(function (d) {
return {
x: d["year"],
y: +d[type]
};
});
}));
x.domain(freqs[0].map(function (d) {
return d.x;
}));
y.domain([0, d3.max(freqs[freqs.length - 1], function (d) {
return d.y0 + d.y;
})]);
// Axis variables for the bar chart
x_axis = d3.svg.axis().scale(x).tickValues([1850, 1855, 1860, 1865, 1870, 1875, 1880, 1885, 1890, 1895, 1900]).orient("bottom");
y_axis = d3.svg.axis().scale(y).orient("right");
// x axis
barchart.append("g")
.attr("class", "x axis")
.style("fill", "#000")
.attr("transform", "translate(0," + height + ")")
.call(x_axis);
// y axis
barchart.append("g")
.attr("class", "y axis")
.style("fill", "#000")
.attr("transform", "translate(" + (width - 85) + ",0)")
.call(y_axis);
// Add a group for each cause.
var freq = barchart.selectAll("g.freq")
.data(freqs)
.enter().append("g")
.attr("class", "freq")
.style("fill", function (d, i) {
return z(i);
})
.style("stroke", "#CCE5E5");
// Add a rect for each date.
rect = freq.selectAll("rect")
.data(Object)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y0) + y(d.y) - height;
})
.attr("height", function (d) {
return height - y(d.y);
})
.attr("width", x.rangeBand())
.attr("id", function (d) {
return d["year"];
});
// Draw the brush
brush = d3.svg.brush()
.x(x)
.on("brush", brushmove)
.on("brushend", brushend);
var arc = d3.svg.arc()
.outerRadius(height / 15)
.startAngle(0)
.endAngle(function(d, i) { return i ? -Math.PI : Math.PI; });
brushg = barchart.append("g")
.attr("class", "brush")
.call(brush);
brushg.selectAll(".resize").append("path")
.attr("transform", "translate(0," + height / 2 + ")")
.attr("d", arc);
brushg.selectAll("rect")
.attr("height", height);
});
// ****************************************
// Brush functions
// ****************************************
function brushmove() {
y.domain(x.range()).range(x.domain()).clamp(true);
b = brush.extent();
brushYearStart = Math.ceil(y(b[0]));
brushYearEnd = Math.ceil(y(b[1]));
// Snap to rect edge
d3.select("g.brush").call(brush.extent([y.invert(brushYearStart), y.invert(brushYearEnd)]));
// Fade all years in the histogram not within the brush
d3.selectAll("rect.bar").style("opacity", function (d, i) {
return d.x >= brushYearStart && d.x < brushYearEnd ? "1" : ".4"
});
}
function brushend() {
// Additional calculations happen here...
// filterPoints();
// colorPoints();
// styleOpacity();
// Update start and end years in upper right-hand corner of the map
d3.select("#brushYears").text(brushYearStart + " - " + brushYearEnd);
}
function resetBrush() {
d3.selectAll(".brush").remove();
d3.selectAll("brushg.resize").remove();
brush.clear();
brushg.call(brush);
}
</script>
<div id="resetMap">
<button
id="returnBrush"
class="btn btn-default"
onclick="resetBrush()"/>Remove Brush
</div>
</body>
</html>
If you execute d3.selectAll(".brush").remove(); you remove <g class="brush"></g> and his childs.
This d3.selectAll("brushg.resize").remove(); is a bug. Must to be brushg.selectAll(".resize").remove(); but is the same case that d3.selectAll(".brush").remove();.
You have to do this:
For reset the brush.extent() and fire the brush event.
function resetBrush() {
brush
.clear()
.event(d3.select(".brush"));
}
For reset #brushYears to the initial state
function brushend() {
var localBrushYearStart = (brush.empty()) ? brushYearStart : Math.ceil(y(b[0])),
localBrushYearEnd = (brush.empty()) ? brushYearEnd : Math.ceil(y(b[1]));
// Update start and end years in upper right-hand corner of the map
d3.select("#brushYears").text(localBrushYearStart + " - " + localBrushYearEnd);
}
For reset to initial values on brush event
function brushmove() {
y.domain(x.range()).range(x.domain()).clamp(true);
b = brush.extent();
3.1. To set the localBrushYearStart and localBrushYearEnd variables to initial state on brush.empty() or set to Math.ceil(brush.extent()))
var localBrushYearStart = (brush.empty()) ? brushYearStart : Math.ceil(y(b[0])),
localBrushYearEnd = (brush.empty()) ? brushYearEnd : Math.ceil(y(b[1]));
3.2. To execute brush.extent() on selection, or brush.clear() on brush.empty()
// Snap to rect edge
d3.select("g.brush").call((brush.empty()) ? brush.clear() : brush.extent([y.invert(localBrushYearStart), y.invert(localBrushYearEnd)]));
3.3. To set opacity=1 years on brush.empty() or selection, and opacity=.4 on not selected years
// Fade all years in the histogram not within the brush
d3.selectAll("rect.bar").style("opacity", function(d, i) {
return d.x >= localBrushYearStart && d.x < localBrushYearEnd || brush.empty() ? "1" : ".4";
});
}
Check the corrections on my BL.OCKS
Just do this
function resetBrush() {
d3.select("g.brush").call(brush.extent([0, 0]))
d3.selectAll("rect.bar").style("opacity", "0.4");
//reset year labels at top
}
Related
I have been trying to drag in d3 packed nodes using this code as a basis https://codepen.io/MrHen/pen/GZQOPW. Unfortunately I can't find a method to use so that when a node with children is dragged around it's (visible) children move with the parent. I use this function to drag circles around:
var drag = d3.behavior.drag()
.on("drag", function(d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
draw();
})
I the example above for instance I want to be able to drag the nodes on layer 1 (light blue) and when I do this their children change their position so they stay (visually) in the borders of their parents.
Thank you!
First remove the pointer-events: none on the root and middle nodes. Then set yourself up a little recursion to walk a node's descendants and update their position:
function recurOnChildren(d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
if (!d.children) return;
d.children.forEach(c => {
recurOnChildren(c);
});
}
And call from your drag handler:
var drag = d3.behavior.drag()
.on("drag", function(d, i) {
recurOnChildren(d);
draw();
})
Running code:
var margin = 20,
diameter = 960;
var color = d3.scale.linear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.layout.pack()
.padding(2)
.size([diameter - margin, diameter - margin])
.value(function(d) {
return d.size;
})
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
d3.json("https://gist.githubusercontent.com/mbostock/7607535/raw/695f8ed6298c06a946406c707200a1f6b21875d8/flare.json", function(error, root) {
if (error) throw error;
var focus = root,
nodes = pack.nodes(root),
view;
function recurOnChildren(d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
if (!d.children) return;
d.children.forEach(c => {
recurOnChildren(c);
});
}
var drag = d3.behavior.drag()
.on("drag", function(d, i) {
recurOnChildren(d);
draw();
})
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) {
return d.parent ? d.children ? "node node--middle" : "node node--leaf" : "node node--root";
})
.style("fill", function(d) {
return d.children ? color(d.depth) : null;
});
var text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) {
return d.parent === root ? 1 : 0;
})
.style("display", function(d) {
return d.parent === root ? "inline" : "none";
})
.text(function(d) {
return d.name;
});
var node = svg.selectAll("circle,text");
svg.selectAll(".node").call(drag);
d3.select("body")
.style("background", color(-1))
draw();
function draw() {
var k = diameter / (root.r * 2 + margin);
node.attr("transform", function(d) {
return "translate(" + (d.x - root.x) * k + "," + (d.y - root.y) * k + ")";
});
circle.attr("r", function(d) {
return d.r * k;
});
}
});
.node {
cursor: pointer;
}
.node:hover {
stroke: #000;
stroke-width: 1.5px;
}
.node--leaf {
fill: white;
}
.label {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
text-anchor: middle;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}
.label {
pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
Following is my code. I have been trying to plot humidity and dew point in basis of months in the axis. But I am getting an error of data undefined and also the month in the axis comes in number.
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<title> Data Visualization - Binding Dataset to Shapes Using D3 </title>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.legend {
font-size: 16px;
font-weight: bold;
text-anchor: middle;
}
</style>
<body>
<script>
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 70, left: 50},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%b").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
var priceline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.dew); });
// Adds the svg canvas
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.top + ")");
// Get the data
d3.json("weatherdata.json", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.history.date.mon);
d.dew = +d.history.dailysummary[0].meandewptm;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.dew; }));
// Nest the entries by symbol
var dataNest = d3.nest()
.key(function(d) {return d.dew;})
.entries(data);
var color = d3.scale.category10(); // set the colour scale
legendSpace = width/dataNest.length; // spacing for legend
// Loop through each symbol / key
dataNest.forEach(function(d,i) {
svg.append("path")
.attr("class", "line")
.style("stroke", function() { // Add the colours dynamically
return d.color = color(d.key); })
.attr("d", priceline(d.values));
// Add the Legend
svg.append("text")
.attr("x", (legendSpace/2)+i*legendSpace) // spacing
.attr("y", height + (margin.bottom/2)+ 5)
.attr("class", "legend") // style the legend
.style("fill", function() { // dynamic colours
return d.color = color(d.key); })
.text(d.key);
});
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
</script>
</body>
</html>
I am not sure how will i solve it. Can anyone please help. I am sharing the data file with the current issue I am facing.
I have attached the model data the way it should look like:
Thanks in advance.
For your question on Date format for the X axis , sorry i put an answer because i can't comment (need more reputation).
I think you need to do something like this
chart.xAxis
.tickFormat(function(d) {
return d3.time.format('%b')(format(d));
});
That will display only the month.
I'm making a multi-line chart and using a brush to select time periods. It's broadly based on Mike Bostock's example at http://bl.ocks.org/mbostock/1667367
My chart is at http://lowercasen.com/dev/d3/general/piezobrush.html
My problem is in selecting the multiple lines in my 'focus' area to apply the brush to. I've nested the data based on a key, so the data is within a function. Because the function that calls my brush is outside that function, it can't access the data and I'm getting a TypeError: undefined is not an object (evaluating 'data.length')
Here's the code that nests the data:
dataNest.forEach(function(d, i) {
focus.append("path")
.attr("class", "line")
.attr("id", d.key.replace(/\s+/g, '')) //the replace stuff is getting rid of spaces
.attr("d", levelFocus(d.values));
context.append("path")
.attr("class", "line")
.attr("id", d.key.replace(/\s+/g, '')) //the replace stuff is getting rid of spaces
.attr("d", levelContext(d.values));
and at the bottom I have the function for the brush:
function brushed() {
xFocus.domain(brush.empty() ? xContext.domain() : brush.extent());
focus.selectAll(".line").attr("d", levelFocus(d.values));
focus.select(".x.axis").call(xAxisFocus);
}
It works fine for the x axis (if I comment out the line where I'm trying to select the lines) but I don't know how to select the lines correctly.
Apologies for any garbled syntax or confusing language, my coding skills are basic at best.
Any help is greatly appreciated, I've searched for hours for a solution.
Here's the full code as requested by Lars
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Multiline with brush</title>
<script src="http://d3js.org/d3.v3.js"></script>
<script src="d3/tooltip.js"></script>
<link href="styles/evidentlySoCharts.css" rel="stylesheet">
<meta name="viewport" content="initial-scale=1">
<style>
svg {
font: 10px sans-serif;
}
path {
stroke-width: 1;
fill: none;
}
#Stream1, #Nebo1D {
stroke: #009390;
}
#Stream1Legend, #Nebo1DLegend {
fill: #009390;
}
#Stream2, #Nebo2D {
stroke: #8dc63f;
}
#Stream2Legend, #Nebo2DLegend {
fill: #8dc63f;
}
#Stream3, #Nebo1S {
stroke: #132d46;
}
#Stream3Legend, #Nebo1SLegend {
fill: #132d46;
}
#Stream4, #Nebo2S {
stroke: #aaa813;
}
#Stream4Legend, #Nebo2SLegend {
fill: #aaa813;
}
#Stream5, #Nebo3 {
stroke: #619dd4;
}
#Stream5Legend, #Nebo3Legend {
fill: #619dd4;
}
.pn1d, .pn2d {
fill: none;
clip-path: url(#clip);
}
.pn1d {
stroke: #009390;
}
.pn2d {
stroke: #1b4164;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script>
var marginFocus = {top: 10, right: 10, bottom: 250, left: 40},
marginContext = {top: 430, right: 10, bottom: 170, left: 40},
width = 960 - marginFocus.left - marginFocus.right,
heightFocus = 650 - marginFocus.top - marginFocus.bottom,
heightContext = 650 - marginContext.top - marginContext.bottom;
legendOffset = 550;
var parseDate = d3.time.format("%d/%m/%y %H:%M").parse;
var xFocus = d3.time.scale().range([0, width]),
xContext = d3.time.scale().range([0, width]),
yFocus = d3.scale.linear().range([heightFocus, 0]),
yContext = d3.scale.linear().range([heightContext, 0]);
var xAxisFocus = d3.svg.axis().scale(xFocus).orient("bottom"),
xAxisContext = d3.svg.axis().scale(xContext).orient("bottom"),
yAxisFocus = d3.svg.axis().scale(yFocus).orient("left");
var levelFocus = d3.svg.line()
.interpolate("linear")
.x(function(d) { return xFocus(d.date); })
.y(function(d) { return yFocus(d.level); });
var levelContext = d3.svg.line()
.interpolate("linear")
.x(function(d) { return xContext(d.date); })
.y(function(d) { return yContext(d.level); });
var svg = d3.select("body").append("svg")
.attr("width", width + marginFocus.left + marginFocus.right)
.attr("height", heightFocus + marginFocus.top + marginFocus.bottom);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", heightFocus);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + marginFocus.left + "," + marginFocus.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + marginContext.left + "," + marginContext.top + ")");
d3.csv("data/PiezoNeboNestSimple.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.level = +d.level;
});
xFocus.domain(d3.extent(data.map(function(d) { return d.date; })));
yFocus.domain([d3.min(data.map(function(d) { return d.level; })) -2,0]);
xContext.domain(xFocus.domain());
yContext.domain(yFocus.domain());
// Nest the entries by piezo
var dataNest = d3.nest()
.key(function(d) {return d.piezo;})
.entries(data);
legendSpace = width/dataNest.length; // spacing for legend // ******
var brush = d3.svg.brush()
.x(xContext)
.on("brush", brushed);
focus.selectAll("g").data(dataNest)
.enter()
.append("g")
.attr("class", "line")
.attr("id", function(d) { return d.key.replace(/\s+/g, '') }) //the replace stuff is getting rid of spaces
.append("path")
.attr("d", function(d) { return levelFocus(d.values); });
context.selectAll("g").data(dataNest)
.enter()
.append("g")
.attr("class", "line")
.attr("id", function(d) { return d.key.replace(/\s+/g, '') }) //the replace stuff is getting rid of spaces
.append("path")
.attr("d", function(d) { return levelContext(d.values); });
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + heightFocus + ")")
.call(xAxisFocus);
focus.append("g")
.attr("class", "y axis")
.call(yAxisFocus);
context.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + heightContext + ")")
.call(xAxisContext);
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", heightContext + 7);
function brushed() {
xFocus.domain(brush.empty() ? xContext.domain() : brush.extent());
focus.selectAll(".line").attr("d", levelFocus(dataNest.values));
focus.select(".x.axis").call(xAxisFocus);
}
});
</script>
</body>
</html>
It mostly boils down to two things as far as I can see. First, the elements you're selecting to be updated are the g and not the path elements and second, you need to reference the data bound to the elements in order to set d. Both are easily fixed and the brushed function looks something like this.
function brushed() {
xFocus.domain(brush.empty() ? xContext.domain() : brush.extent());
focus.selectAll("path").attr("d", function(d) { return levelFocus(d.values); });
focus.select(".x.axis").call(xAxisFocus);
}
Complete demo here. Note that some bits are still missing, in particular the clip path to restrict the lines to the chart area. This can be copied and pasted directly from the example you've referenced though.
I am attempting to create a map of the 10 major NASA facilities in D3. I have successfully generated the base United States map and appended NASA logos at each one of the center locations based on a .csv with latitude and longitude. However, I cannot figure out any elegant way to draw lines / links / arcs / connections between the points on the map.
In the code below, I have drawn a line between GSFC and KSC (using the 'var = places', 'var = route', and 'svg.append("path")') but it is on an SVG layer, so it is on top of the logos (which looks awful) and does not scale (or go away would be fine, too) when clicking to zoom in on a state. I would like to be able to draw links between the centers based on the latitude and longitude data from the .csv.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.background {
fill: none;
pointer-events: all;
}
#states {
fill: #aaaaaa;
}
#states .active {
fill: #ff0000;
fill-opacity: .5;
}
#state-borders {
fill: none;
stroke: #ffffff;
stroke-width: 1.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
path.link {
fill: none;
stroke: #666666;
stroke-width: 1.5px;
}
.stroke {
fill: none;
stroke: #000;
stroke-width: 3px;
}
.fill {
fill: #fff;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
.route {
fill: none;
stroke: blue;
stroke-width: 3px;
}
</style>
<body>
<h2>
<span>NASA Centers</span>
</h2>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 1000,
height = 600,
centered;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
var places = {
GSFC: [-76.852587, 38.991621],
KSC: [-80.650813, 28.524963]
};
var route = {
type: "LineString",
coordinates: [
places.GSFC,
places.KSC
]
};
var point = svg.append("g")
.attr("class", "points")
.selectAll("g")
.data(d3.entries(places))
.enter().append("g")
.attr("transform", function(d) { return "translate(" + projection(d.value) + ")"; });
point.append("text")
.attr("y", 5)
.attr("dx", "1em")
.text(function(d) { return d.key; });
d3.json("us.json", function(error, us) {
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.on("click", clicked);
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("id", "state-borders")
.attr("d", path);
d3.csv("nasacenters.csv", function(error, data) {
g.selectAll("image").data([0])
.data(data)
.enter()
.append("image")
.attr("xlink:href", "nasalogo.png")
.attr("width", "30")
.attr("height", "30")
.attr("x", function(d) {
return projection([d.lon, d.lat])[0]-15;
})
.attr("y", function(d) {
return projection([d.lon, d.lat])[1]-15;
})
svg.append("path")
.datum(route)
.attr("class", "route")
.attr("d", path)
.style("opacity", 0.5);
});
});
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
</script>
</body>
</html>
The .csv file is in the following format:
code,center,lat,lon
GSFC,Goddard Space Flight Center,38.991621,-76.852587
KSC,Kennedy Space Center,28.524963,-80.650813
JPL,Jet Propulsion Laboratory,34.200463,-118.176008
DFRC,Dryden Flight Research Center,34.613714,-118.076790
GRC,Glenn Research Center,41.415891,-81.861774
MSFC,Marshall Space Flight Center,34.646554,-86.674368
ARC,Ames Research Center,37.409574,-122.064292
LaRC,Langley Research Center,37.092123,-76.376230
JSC,Johnson Space Center,29.551508,-95.092256
SSC,Stennis Space Center,30.363692,-89.600036
I modified your example slightly based on the problems you described: http://bl.ocks.org/erikhazzard/6201948
It looks like there are three issues:
Paths draw on top of icon. To fix this, you can change the order of when you add items to the group, or add sub groups to your main g group, ensuring the order that you add the groups matches the order you want things to appear.
The paths between points doesn't zoom when you zoom the map. To fix this, make sure to add everything to the group that you're modifying the clicked() function. In this case, your g group is being zoomed on, so if you add the paths to the g group instead of the svg directly the paths will zoom as well. In the example provided, text does also not zoom in - that's because it's added directly to the SVG and not the g group that is being transformed.
Paths aren't created automatically from the data. To fix this, you can generate an array containing LineString objects from the data. For example,
for(var i=0, len=data.length-1; i<len; i++){
// (note: loop until length - 1 since we're getting the next
// item with i+1)
links.push({
type: "LineString",
coordinates: [
[ data[i].lon, data[i].lat ],
[ data[i+1].lon, data[i+1].lat ]
]
});
}
Then, do the standard data join pattern and pass in the links list to the data. When you pass in path as the d attribute, it will generate a great arc based on the coordinates for each item:
// Standard enter / update
var pathArcs = arcGroup.selectAll(".arc")
.data(links);
//enter
pathArcs.enter()
.append("path").attr({
'class': 'arc'
}).style({
fill: 'none',
});
//update
pathArcs.attr({
//d is the points attribute for this path, we'll draw
// an arc between the points using the arc function
d: path
})
.style({
stroke: '#0000ff',
'stroke-width': '2px'
})
In my example ( http://bl.ocks.org/enoex/6201948 ) I added a transition on the great arc paths to illustrate how the path is drawn based on the order of coordinate pairs passed into the links object.
Hope that helps!
I have extended the pie-chart example at:
with pies that vary in radius depending on a percentage. I would like to add gridlines (circles) every 20 percent, but I can't figure out how.
here is the updated csv:
age,population,percent
<5,2704659,67
5-13,4499890,38
14-17,2159981,91
18-24,3853788,49
25-44,14106543,71
45-64,8819342,88
=65,612463,64
and here is the updated code with pie-parts of different radius:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
background: #333;
}
.arc path {
stroke: #fff;
stroke-width: 2px;
}
.arc grid {
stroke: #fff;
stroke-width: 1;
stroke-dasharray: 5,5;
}
.arc text {
fill:#fff;
font-size:12px;
font-weight:bold;
}
.arc line {
stroke: #fff;
}
</style>
<body>
<script src="d3.js"></script>
<script>
var width = 960,
height = 500,
radius = Math.min(width, height) / 2 - 10;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(function(d) { return 50 + (radius - 50) * d.data.percent / 100; })
.innerRadius(20);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
var grid = d3.svg.area.radial()
.radius(150);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.population = +d.population;
d.percent = d.percent;
});
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
});
</script>
First set the number of ticks:
var numTicks = 5; // Each tick is 20%
Then create the data to create the gridlines:
var sdat = [];
for (i=0; i<=numTicks; i++) {
sdat[i] = (radius/numTicks) * i;
}
And then you can use a function to create the radial gridlines, and you can call it from within the d3.csv block:
addCircleAxes = function() {
var circleAxes, i;
svg.selectAll('.circle-ticks').remove();
circleAxes = svg.selectAll('.circle-ticks')
.data(sdat)
.enter().append('svg:g')
.attr("class", "circle-ticks");
// radial tick lines
circleAxes.append("svg:circle")
.attr("r", String)
.attr("class", "circle")
.style("stroke", "#CCC")
.style("opacity", 0.5)
.style("fill", "none");
// Labels for each circle
circleAxes.append("svg:text")
.attr("text-anchor", "center")
.attr("dy", function(d) { return d - 5 })
.style("fill", "#fff")
.text(function(d,i) { return i * (100/numTicks) });
};
An example is here: http://bl.ocks.org/3994129
(Borrowed from: http://kreese.net/blog/2012/08/26/d3-js-creating-a-polar-area-diagram-radial-bar-chart/)