D3, plotting to map, possible data formatting - javascript

I am trying to create a map and plot some points to it using d3, I found a few good examples to build off of but I believe i am stuck. My guess is I am not handling the plot points correctly according to how I have the data structured. I could use a bit of help - this is my first attempt. Here's what I have so far :
var m_width = document.getElementById("map").offsetWidth,
width = 938,
height = 500;
var projection = d3.geo.mercator()
.scale(150)
.translate([width / 2, height / 1.5]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#map").append("svg")
.attr("preserveAspectRatio", "xMidYMid")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("width", m_width)
.attr("height", m_width * height / width);
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
var g = svg.append("g");
d3.json("scripts/world-110m2.json", function(error, us) {
g.append("g")
.attr("id", "countries")
.selectAll("path")
.data(topojson.feature(us, us.objects.countries).features)
.enter()
.append("path")
.attr("id", function(d) { return d.id; })
.attr("d", path)
});
svg.selectAll(".pin")
.data(places)
.enter().append("circle", ".pin")
.attr("r", 5)
.attr("transform", function(d) {
return "translate(" + projection([
d.earthquakes.lon,
d.earthquakes.lat
]) + ")"
});
window.addEventListener('resize', function(event){
var w = document.getElementById("map").offsetWidth;
svg.attr("width", w);
svg.attr("height", w * height / width);
});
And the "places" data is structured like so
var places = {"count":"392","earthquakes":[{"src":"us","eqid":"2010sdbk","timedate":"2010-01-31 15:18:44","lat":"-18.7507","lon":"169.3940","magnitude":"5.1","depth":"231.50","region":"Vanuatu"}
Where all the place are inside an object array "earthquakes" inside places. (lon and lat specifically inside of that).
The world map shows up fine, I am just having trouble getting these plot points to work. Would appreciate any help greatly. Thanks for reading!!

You almost had it, but couple problems here:
1.) The data you pass to .data should be an array (of where to add your circles).
2.) In your places object, you lat/lon are strings and need to be converted to numbers.
Try:
var places = {
"count": "392",
"earthquakes": [{
"src": "us",
"eqid": "2010sdbk",
"timedate": "2010-01-31 15:18:44",
"lat": "-18.7507",
"lon": "169.3940",
"magnitude": "5.1",
"depth": "231.50",
"region": "Vanuatu"
}]
};
svg.selectAll(".pin")
.data(places.earthquakes) //<-- pass array
.enter()
.append("circle")
.attr("class","pin")
.attr("r", 5)
.attr("transform", function(d) {
return "translate(" + projection([
+d.lon, //<-- coerce to number
+d.lat
]) + ")";
});
Example here.

Related

D3 Javascript - Repeat a group of shapes with data bind

I want to repeat a group of shapes specifically
text rect text circles
Where in circles is again a repeat of circle
My data is
Jsondata =[
{ "name":"A", "WidthOfRect":50, "justAnotherText":"250", "numberOfCircles" :3 },
{ "name":"B", "WidthOfRect":150, "justAnotherText":"350","numberOfCircles" :2 },
{ "name":"C", "WidthOfRect":250, "justAnotherText":"450","numberOfCircles" :1 }]
Basically Out of this data i am trying to construct a customized bar chart.
The width of the rect is based upon the data widthofrect from the json, as well as number of circles is based upon numberofcircles property.
I looked out for a number of options to repeat group of shapes but couldn't find one.
First of all, you're right in your comment: do not use loops to append elements in a D3 code. Also, your supposition about the length of the data is correct.
Back to the question:
The text and rect part is pretty basic, D3 101, so let's skip that. The circles is the interesting part here.
My proposed solution involves using d3.range to create an array whose number of elements (or length) is specified by numberOfCircles. That involves two selections.
First, we create the groups (here, scale is, obviously, a scale):
var circlesGroups = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(20," + scale(d.name) + ")"
});
And then we create the circles. Pay attention to the d3.range:
var circles = circlesGroups.selectAll(null)
.data(function(d) {
return d3.range(d.numberOfCircles)
})
.enter()
.append("circle")
//etc...
Here is a demo, I'm changing the numberOfCircles in your data to paint more circles:
var width = 500,
height = 200;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var data = [{
"name": "A",
"WidthOfRect": 50,
"justAnotherText": "250",
"numberOfCircles": 13
},
{
"name": "B",
"WidthOfRect": 150,
"justAnotherText": "350",
"numberOfCircles": 22
},
{
"name": "C",
"WidthOfRect": 250,
"justAnotherText": "450",
"numberOfCircles": 17
}
];
var scale = d3.scalePoint()
.domain(data.map(function(d) {
return d.name
}))
.range([20, height - 20])
.padding(0.5);
var colorScale = d3.scaleOrdinal(d3.schemeCategory10);
var circlesGroups = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(20," + scale(d.name) + ")"
})
.style("fill", function(d) {
return colorScale(d.name)
})
var circles = circlesGroups.selectAll(null)
.data(function(d) {
return d3.range(d.numberOfCircles)
})
.enter()
.append("circle")
.attr("r", 5)
.attr("cx", function(d) {
return 10 + 12 * d
});
var axis = d3.axisLeft(scale)(svg.append("g").attr("transform", "translate(20,0)"));
<script src="https://d3js.org/d3.v5.min.js"></script>
PS: I'm using D3 v5.

Wrong point of origin reference

I'm facing problem with wrong point of origin when multiple elements are added to an SVG.
JSFiddle: https://jsfiddle.net/sbc6ejeu/2/
I've added an SVG and associated path and couple of circles to it. They seem to correspond to the correct origin. However when I move the slider, I expect the circle of id=movingCicle (as mentioned in the code) to move along the green curve (line). I'm unable to start the initial
position of the circle to the same origin as other svg elements.
Also I observe that the range of the red circle is not same as the other elements or the SVG to which it is appended. For the 2nd and 3rd drop down options, the red cicle moves out of the graph when the slider is increased. I feel I'm missing out on something.
Appretiate any help on this.Thanks!
function initialize() {
// Add circle data
jsonCircles = [{
"xAxis": 50,
"yAxis": 154
}, {
"xAxis": 150,
"yAxis": 154
}];
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
width = 600 - margin.left - margin.right;
height = 270 - margin.top - margin.bottom;
// Set the ranges
x = d3.scale.linear().range([0, width]);
y = d3.scale.linear().range([height, 0]);
// Define the axes
xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(10);
yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(7);
valueLine = d3.svg.line()
.x(function(d, i) {
return x(i);
})
.y(function(d) {
return y(d);
});
// Adds the svg canvas
svg = d3.select("#graph")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "svg1")
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
}
function updateCirclePosition(i) {
d3.select("#movingCircle").remove();
svg.append("circle")
.attr("cx", +i)
.attr("cy", yValues[i])
.attr("r", 5)
.attr("id", "movingCircle")
.style("fill", "red");
}
function addSvgElements() {
// Add the valueline path.
var path = svg.append("path")
.attr("class", "line")
.attr("id", "lineId")
.attr("d", valueLine(yValues));
}
Inside the function updateCirclePosition, the variable i contains the value of the budget, and yValues[i] is the corresponding revenue.
The corresponding coordinates in the chart can be found using x and y functions, therefore x(i) and y(yValues[i]) should be used to set the correct cx and cy:
svg.append("circle")
.attr("cx", x(i))
.attr("cy", y(yValues[i]))
updated fiddle: https://jsfiddle.net/sbc6ejeu/5/

d3.js create the pie chart but square shaped

Just started using d3.js and javascript. I have this weird chart requirement. Want to create the chart exactly like pie chart but, in square shaped. Just like below.
So, I thought, may be I create the pie chart and add the square between the pie chart and erase the part outside square. But, it is not working out yet.
Secondly, I thought, I can do this with CSS. I did this. But, I am not happy with this solution. It is too hacky. Can someone help me with good solution.
This is my jsfiddle link.
//// Done this to create the square.
var svgContainer = d3.select("#square").append("svg")
.attr("width", 200)
.attr("height", 200);
var rectangle = svgContainer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 200)
.attr("fill", '#ec4c4a')
.attr("height", 200);
// Done this to create the pie chart. Found this example some where.
var element_id = 'pie'
var elementSelector = '#pie';
svgWidth = 390;
svgHeight = 320;
svgInnerRadius = 0;
svgOuterRadius = 145;
heightOffset = 0;
scoreFontSize = '49px';
$(elementSelector).replaceWith('<svg id="'+ element_id +'" class="scoreBar" width="'+ svgWidth +'" height="'+ (svgHeight - heightOffset) +'"></svg>');
$(elementSelector).css({'width': svgWidth + 'px', 'height': (svgHeight-heightOffset) + 'px'});
var anglePercentage = d3.scale.linear().domain([0, 100]).range([0, 2 * Math.PI]);
var fullAnglePercentage = 100;
var color = d3.scale.ordinal().range(["#ACACAC", "#EAEAEA", "#123123", "#DDEEAA", "#BACBAC"]);
data = [[50, 90, 1],
[50, 30, 2],
[30, 10, 3],
[10, -1, 4],
[-1, -10, 5]]
var vis = d3.select(elementSelector);
var arc = d3.svg.arc()
.innerRadius(svgInnerRadius)
.outerRadius(svgOuterRadius)
.startAngle(function(d){return anglePercentage(d[0]);})
.endAngle(function(d){return anglePercentage(d[1]);});
vis.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", arc)
.style("fill", function(d){return color(d[2]);})
.attr("transform", "translate(" + svgWidth / 2 + ", " + svgHeight / 2 + ")");
Thanks in advance.
You can achieve this using clip path. What is a clip path?
To SVG add defs of clippath
var svg1 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
//making a clip square as per your requirement.
svg1.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", -120)
.attr("y", -100)
.attr("width", radius)
.attr("height", radius);
Make your normal d3 pie chart like:
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);
});
To the main group add the clip like this:
var svg = svg1.append("g").attr("clip-path", "url(#clip)")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
Full working code here.

JS Gauge with growing arc

Does anyone know of a JavaScript charting library that is capable of drawing a a gauge like this:
I've already looked at Highcharts, Kendo UI and FusionCharts, but I couldn't find any samples with a non-constant width of the arc...but that could also be because I don't even know what to search for exactly.
I found this post which seems to go in the right direction, but I'd rather not have to draw SVG myself if there's an out of the box solution.
In case anyone else ever needs something like that, I ended up building it myself using D3. Full animated sample is available at http://jsfiddle.net/0288wscf/11/
var domain = [1, 100];
var angleScale = d3.scale.linear().domain(domain).range([minAngle, maxAngle]);
var radiusScale = d3.scale.linear().domain(domain).range([radius - minWidth, radius - maxWidth]);
var colorScale = d3.scale.linear().domain(domain).range([minColor, maxColor]);
var svg = d3.select("body").append("svg")
.attr("width", 2 * radius)
.attr("height", 2 * radius);
var gauge = svg.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")")
var arc = d3.svg.arc()
.innerRadius(radiusScale)
.outerRadius(radius)
.startAngle(angleScale)
.endAngle(angleScale);
function update(n) {
var ticks = gauge.selectAll(".tick").data(d3.range(1, n), function(d) { return d; });
ticks.enter()
.append("path")
.attr("class", "tick")
.attr("stroke", colorScale)
.attr("d", arc)
.attr("stroke-width", tickThickness)
.attr("opacity", 0)
.transition()
.delay(enterDuration)
.attr("opacity", 1);
ticks.exit()
.transition()
.delay(exitDuration)
.remove();
}

Is there a way to zoom into a D3 force layout graph?

D3 has a force directed layout here. Is there a way to add zooming to this graph? Currently, I was able to capture the mouse wheel event but am not really sure how to write the redraw function itself. Any suggestions?
var vis = d3.select("#graph")
.append("svg:svg")
.call(d3.behavior.zoom().on("zoom", redraw)) // <-- redraw function
.attr("width", w)
.attr("height", h);
Update 6/4/14
See also Mike Bostock's answer here for changes in D3 v.3 and the related example. I think this probably supersedes the answer below.
Update 2/18/2014
I think #ahaarnos's answer is preferable if you want the entire SVG to pan and zoom. The nested g elements in my answer below are really only necessary if you have non-zooming elements in the same SVG (not the case in the original question). If you do apply the behavior to a g element, then a background rect or similar element is required to ensure that the g receives pointer events.
Original Answer
I got this working based on the zoom-pan-transform example - you can see my jsFiddle here: http://jsfiddle.net/nrabinowitz/QMKm3/
It was a bit more complex than I had hoped - you have to nest several g elements to get it to work, set the SVG's pointer-events attribute to all, and then append a background rectangle to receive the pointer events (otherwise it only works when the pointer is over a node or link). The redraw function is comparatively simple, just setting a transform on the innermost g:
var vis = d3.select("#chart")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('svg:g');
vis.append('svg:rect')
.attr('width', w)
.attr('height', h)
.attr('fill', 'white');
function redraw() {
console.log("here", d3.event.translate, d3.event.scale);
vis.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
This effectively scales the entire SVG, so it scales stroke width as well, like zooming in on an image.
There is another example that illustrates a similar technique.
Why the nested <g>'s?
This code below worked well for me (only one <g>, with no random large white <rect>:
var svg = d3.select("body")
.append("svg")
.attr({
"width": "100%",
"height": "100%"
})
.attr("viewBox", "0 0 " + width + " " + height )
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("pointer-events", "all")
.call(d3.behavior.zoom().on("zoom", redraw));
var vis = svg
.append('svg:g');
function redraw() {
vis.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
Where all the elements in your svg are then appended to the vis element.
The provided answers work in D3 v2 but not in v3. I've synthesized the responses into a clean solution and resolved the v3 issue using the answer provided here: Why does d3.js v3 break my force graph when implementing zooming when v2 doesn't?
First the main code. This is a cleaned up version of #ahaarnos' answer:
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().on("zoom", redraw))
.append('g');
function redraw() {
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
Now you have pan and zoom, but you won't be able to drag nodes because the pan functionality will override the drag functionality. So we need to do this:
var drag = force.stop().drag()
.on("dragstart", function(d) {
d3.event.sourceEvent.stopPropagation(); // to prevent pan functionality from
//overriding node drag functionality.
// put any other 'dragstart' actions here
});
Here's #nrabinowitz' fiddle modified to use this cleaner zoom implementation, but illustrating how D3v3 breaks node drag: http://jsfiddle.net/QMKm3/718/
And here's the same fiddle modified to work with D3v3: http://jsfiddle.net/QMKm3/719/
I got my graph to work without the second "svg:g" append.
[...].attr("pointer-events", "all")
.attr("width", width2)
.attr("height", height2)
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw));
The rest is the same.
I got a solution for D3 force directed graph with zooming option.
var m = [40, 240, 40, 240],
width = 960,
height = 700,
root;
var svg = d3.select("body").append("svg")
.attr("class", "svg_container")
.attr("width", width)
.attr("height", height)
.style("overflow", "scroll")
.style("background-color", "#EEEEEE")
.append("svg:g")
.attr("class", "drawarea")
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
//applying zoom in&out for svg
d3.select("svg")
.call(d3.behavior.zoom()
.scaleExtent([0.5, 5])
.on("zoom", zoom));
//zooming
function zoom() { //zoom in&out function
var scale = d3.event.scale,
translation = d3.event.translate,
tbound = -height * scale,
bbound = height * scale,
lbound = (-width + m[1]) * scale,
rbound = (width - m[3]) * scale;
// limit translation to thresholds
translation = [
Math.max(Math.min(translation[0], rbound), lbound),
Math.max(Math.min(translation[1], bbound), tbound)
];
d3.select(".drawarea")
.attr("transform", "translate(" + translation + ")" +
" scale(" + scale + ")");
}
If you want to zoom and pan force layout without changing node-size, try below.
You can also drag nodes without trembling.
This code is based on original force layout example. As for nodes and links data, please refer to original sample data. http://bl.ocks.org/mbostock/4062045
Plz note the variables xScale and yScale, the functions dragstarted(), dragged(), and dragended().
Function tick() was changed as well.
You can see the result at http://steelblue.tistory.com/9
The language on the site is Korean. However you can easily find the result at the third example on the page.
var graph = {
"nodes": [
{ "name": "Myriel", "group": 1 },
{ "name": "Napoleon", "group": 1 },
// ......
{ "name": "Mme.Hucheloup", "group": 8 }
],
"links": [
{ "source": 1, "target": 0, "value": 1 },
{ "source": 2, "target": 0, "value": 8 },
// .......
{ "source": 76, "target": 58, "value": 1 }
]
};
var width = 640,
height = 400;
var color = d3.scale.category20();
var xScale = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([0, height])
.range([0, height]);
var zoomer = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([0.1, 8]).on("zoom", zoom);
function zoom() {
tick();
};
var drag = d3.behavior.drag()
.origin(function (d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d.fixed |= 2;
}
function dragged(d) {
var mouse = d3.mouse(svg.node());
d.x = xScale.invert(mouse[0]);
d.y = yScale.invert(mouse[1]);
d.px = d.x;
d.py = d.y;
force.resume();
}
function dragended(d) {
d.fixed &= ~6; }
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.call(zoomer);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function (d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function (d) { return color(d.group); })
.call(drag);
node.append("title")
.text(function (d) { return d.name; });
force.on("tick",tick);
function tick(){
link.attr("x1", function (d) { return xScale(d.source.x); })
.attr("y1", function (d) { return yScale(d.source.y); })
.attr("x2", function (d) { return xScale(d.target.x); })
.attr("y2", function (d) { return yScale(d.target.y); });
node.attr("transform", function (d) {
return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")";
});
};

Categories

Resources