Rotating Globe Choropleth in D3 - javascript

I'm trying to make a chorolpleth with a rotating globe in d3. I can get the globe to render out fine but I can't get the countries to fill with the proper color scale.
Longer explanation. I basically started with Mike Bostock's code for the spinning globe found here:
http://bl.ocks.org/mbostock/6747043
I've got some economic data for about 85 countries that I'm reading in from an external csv. And I'm trying to get the colors to map to the countries per the values in the csv. There's another Bostock example of a choropleth here (static and just the US and referenced frequently in SO d3 questions):
http://bl.ocks.org/mbostock/4060606
What I end up with are solid white (#fff) countries on the face of the globe. Which is not what I'm trying to get.
I added the ISO 3166-1 numeric codes to my csv so that I could match them to the same ids inside the topojson data. So my csv looks like:
country id curracct
Germany 276 260.9
Sweden 752 7.24
Etc.
My first thought was just to create a variable that was a function, which went through the length of the 'countries' from the topojson data and found the countries where the id equaled the id from the csv countries, then assigned the scaled color to them. Then I set 'context.fillStyle' equal to that variable/function. That didn't work.
Then I just put 'context.fillStyle' directly inside of a function (which is the code as it's currently written below). That didn't work either.
Again, I'm trying to get the 85 or so countries with data in the csv to appear color-coded on the front side spinning globe according to the scale I've set up.
My guess is that there is something I don't understand about the variable 'context' and what it's handling. If this were .style("fill", [put my function here to map the colors]) syntax I would be okay. So, anyone got any thoughts?
I'm not a coder. Actually I guess I am as I am trying to write some code. Maybe I should just say I'm a self-taught and mostly terrible coder. Although through examples, the JS console, and other questions on SO, I can usually work out where the errors are. This time I've reached a wall. Any help is appreciated. Thanks.
var width = 560,
height = 560,
speed = -1e-2,
start = Date.now();
var sphere = {type: "Sphere"};
var color = d3.scale.quantize()
.range(["#ffffd9", "#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"]);
var projection = d3.geo.orthographic()
.scale(width / 2.1)
.clipAngle(90)
.translate([width / 2, height / 2]);
var graticule = d3.geo.graticule();
var canvas = d3.select("body")
.append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
var path = d3.geo.path()
.projection(projection)
.context(context);
queue()
.defer(d3.json, "/d3/world-110m.json")
.defer(d3.csv, "trade.csv")
.await(globeTrade);
function globeTrade(error, topo, data) {
var land = topojson.feature(topo, topo.objects.land),
countries = topojson.feature(topo, topo.objects.countries),
borders = topojson.mesh(topo, topo.objects.countries, function(a, b) { return a !== b; }),
grid = graticule();
color.domain([0, d3.max(data, function(d){return d.curracct})]);
d3.timer(function() {
var λ = speed * (Date.now() - start),
φ = -15;
context.clearRect(0, 10, width, height);
context.beginPath();
path(sphere);
context.lineWidth = 2.5;
context.strokeStyle = "#000";
context.stroke();
context.fillStyle = "#fff";
context.fill();
context.save();
context.translate(width / 2, 0);
context.scale(-1, 1);
context.translate(-width / 2, 0);
projection.rotate([λ + 180, -φ]);
context.beginPath();
path(land);
context.fillStyle = "#ddd" //changed to a nuetral gray
context.fill();
context.beginPath();
path(grid);
context.lineWidth = .5;
context.strokeStyle = "rgba(119,119,119,.5)";
context.stroke();
context.beginPath();
path(borders);
context.lineWidth = .25;
context.strokeStyle="#fff";
context.stroke();
context.restore();
projection.rotate([λ, φ]);
context.beginPath();
path(grid);
context.lineWidth = .5;
context.strokeStyle = "rgba(119,119,119,.5)";
context.stroke();
// This is where I am failing
context.beginPath();
path(countries);
function render (d){
for (var j = 0; j < countries.features.length; j++) {
if (d.id == countries.features[j].id) {
context.fillStyle = color(d.curracct)
}
else {
context.fillStyle = "#737368"; //left Bostock's color for now
}
}
}
context.fill();
context.lineWidth = .1;
context.strokeStyle = "#000";
context.stroke();
});
data.forEach(function(d, i) {
d.curracct = +d.curracct;
d.id = +d.id;
});
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>

Related

Need help making modifications to a geodesic sphere using d3.js

I'm tinkering with a slowly-rotating geodesic sphere, and I was hoping you guys could help me make some tweaks:
https://codepen.io/anon/pen/QaYQBd
(The code is based on this: https://bl.ocks.org/mbostock/3057239)
var width = 1000,
height = 500;
var velocity = [.002, .002],
t0 = Date.now();
var projection = d3.geo.orthographic()
.scale(height / 2 - 10);
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
context.strokeStyle = "#fa0";
context.lineWidth = 0.5;
geodesic(3);
d3.timer(function() {
var time = Date.now() - t0;
projection.rotate([time * velocity[0], time * velocity[1]]);
redraw();
});
function redraw() {
context.clearRect(0, 0, width, height);
faces.forEach(function(d) {
d.polygon[0] = projection(d[0]);
d.polygon[1] = projection(d[1]);
d.polygon[2] = projection(d[2]);
});
context.beginPath();
faces.forEach(function(d) {
drawTriangle(d.polygon);
});
context.stroke();
}
function drawTriangle(triangle) {
context.moveTo(triangle[0][0], triangle[0][1]);
context.lineTo(triangle[1][0], triangle[1][1]);
context.lineTo(triangle[2][0], triangle[2][1]);
context.closePath();
}
function geodesic(n) {
faces = d3.geodesic.polygons(n).map(function(d) {
d = d.coordinates[0];
d.polygon = d3.geom.polygon(d.map(projection));
return d;
});
redraw();
}
I've been able to modify the code to get the design of the sphere closer to what I want. I'm not sure how to progress any further.
My goals:
Add dots to each vertex like this: https://bl.ocks.org/mbostock/3055104
Give the sphere a 3d perspective instead of being flat (Check out this design, which has a slider that lets you adjust the perspective: http://dmccooey.com/polyhedra/GeodesicIcosahedron3.html)
Have the stroke widths and dot sizes change relative to the perspective (this will make the whole thing have a more 3d solid look to it, instead of having the same stroke widths for every line segment)
Move the sphere to an arbitrary position within the canvas and allow the canvas size to change depending on the width of the browser window
Randomize the rotation and starting position of the sphere when the page reloads
If anyone can help me achieve any of these goals, I'd be ecstatic!

Trying to draw arc in canvas - weird behavior

I am trying to make a circle out of arcs (something similar to a donut chart is what i am trying to achieve visually) and I succeeded. But, the edges look like a 4 year old drew them!
This is how i'm drawing my arcs:
var arc = new Kinetic.Shape({
drawFunc: function(canvas) {
var context = canvas.getContext('2d');
var x = Math.round(canvas.width / 2);
var y = Math.round(canvas.height / 2);
var radius = 210;
var startAngle = 1.44 * Math.PI;
var endAngle = 1.83 * Math.PI;
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = 175;
canvas.fillStroke(this);
},
stroke: '#121b21',
strokeWidth: 175
});
I created an example fiddle.
I am new to canvas so i figured its probably me...
Can someone please let me know if i am doing something wrong here?
Thank you!
This is the thick-stroke-arc bug present in WebKit browsers. It's probably due to some rounding problem in the arc drawing code. You'll find it looks fine in other browsers most likely. For Webkit, you can use another method to draw these wedges meanwhile.
Possible workarounds:
Use the method you have now, but put a white circle in the middle once you're done with the wedges to blot out the interior irregularities
Use Kinetic.Wedge instead of context.arc.

How can i draw a Square in HTML5 Canvas at run time?

I am working on a HTML5 Project.There is a drawing graphics API to draw Rectangle (fillRectStrokeRect).But how can i draw a SQUARE. I have tried the following way to draw it
CODE
getMouse(e);
x2=mx; y2=my;
var width=endX-startX;
var height=endY-startY;
annCanvasContext.beginPath();
annCanvasContext.lineWidth=borderWidth;
var centerX=width/2;
var centerY=width/2;
var radius=width/2;
annCanvasContext.arc(centerX+5, centerY+5, radius, 0, 2 * Math.PI, false);
annCanvasContext.stroke();
Use fillRect or strokeRect with the width and height being equal.
var x = 0, y = 0,
side = 10;
ctx.fillRect(x, y, side, side);
Demo
As you say in the comments, if you want to fit the largest square in a circle, it's more Math related than about code. I'll trying explaining it to you, but you'll probably find better, more visual explanations elsewhere on the Internet.
Draw the diameter of the circle in a way that it divides your square into two equal parts. Now one part is a right angled triangle, which has two of its sides equal. We know the diameter. Using the Pythogorean theorem, you get this equation:
side^2 + side^2 = diameter^2.
Let's find the side now.
2(side^2) = diameter^2
side^2 = (diameter^2)/2
side = Math.sqrt( (diameter^2)/2 )
Now, to turn this into code.
var ctx = document.getElementById('canvas').getContext('2d'),
radius = 20;
ctx.canvas.addEventListener('click', function (e){
ctx.fillStyle = 'black';
ctx.arc(e.pageX, e.pageY, radius, 0, Math.PI*2, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = 'red';
var diameter = radius * 2;
var side = Math.sqrt( (diameter * diameter)/2 );
ctx.fillRect(e.pageX - side/2, e.pageY - side/2, side, side);
ctx.closePath();
}, false);
This would draw a square inside a circle wherever you click on the canvas.
Demo

Draw rectangle on HTML 5 / Javascript Teechart

I need to draw vertical and horizontal rectangle / bands (like in Gantt charts) on the HTML 5 / JavaScript Tee charts.
I could draw a rectangle using the Bar series as below but I want the rectangle to float on a particular XY cor-ordinate rather standing from the x axis.
var series1 = new Tee.Bar();
series1.data.values = [30000];
//alert(" openValues[y] "+ openValues[y] + " closeValues[y] "+ closeValues[y] );
series1.data.x = [ (openValues[y] + closeValues[y]) /2 ];
series1.format.shadow.visible = false;
series1.format.lineCap = "round";
series1.format.stroke.fill = "#D3D3D3";
series1.format.stroke.size = .5;
series1.colorEach = "auto";
series1.format.join = "round";
series1.format.cap = "square";
series1.format.fill="#F8F8FF";
series1.barSize=100000;
//series1.format.transparent =1;
series1.color = "silver";
series1.marks.visible = false;
//series1.hover.shadow =false;
series1.hover.stroke.size =.01;
Chart1.addSeries(series1);
Is it possible to just draw a rectangle using HTML 5 / Javascript Teecharts. I want to to draw rectangles like in gantt chart with specific x1,y1,x2 and y2 values.
I tried using the Format Object in Teecharts but it didn't help
Chart1.panel.format.rectPath(20,20,30,40);
To draw simple rectangle using html5...
window.onload = function() {
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.beginPath();
context.rect(188, 50, 200, 100);
context.fillStyle = '#8ED6FF';
context.fill();
context.lineWidth = 5;
context.strokeStyle = 'black';
context.stroke();
};
here is example link for gantt chart using html5
http://www.eecg.toronto.edu/~brousse1/Libraries/RGraph/docs/gantt.html
The next TeeChart Javascript v1.1 will incude the Gantt Series. Here it is the demo:
http://www.steema.us/files/jscript/demos/series/gantt/gantt.htm
We'll be glad to hear any comment about it.
Steema Support Central

Draw an arrow on HTML5 Canvas between two objects

I'm working on concept maps application, which has a set of nodes and links. I have connected the links to nodes using the center of the node as reference. Since I have nodes with different size and shapes, it is not advisable to draw arrow-head for the link by specifying height or width of the shape. My approach is to draw a link, starting from one node, pixel by pixel till the next node is reached(here the nodes are of different color from that of the background), then by accessing the pixel value, I want to be able to decide the point of intersection of link and the node, which is actually the co-ordinate for drawing the arrow-head.
It would be great, if I could get some help with this.
Sample Code:
http://jsfiddle.net/9tUQP/4/
Here the green squares are nodes and the line starting from left square and entering into the right square is the link. I want the arrow-head to be drawn at the point of intersection of link and the right square.
I've created an example that does this. I use Bresenham's Line Algorithm to walk the line of whole canvas pixels and check the alpha at each point; whenever it crosses a 'threshold' point I record that as a candidate. I then use the first and last such points to draw an arrow (with properly-rotated arrowhead).
Here's the example: http://phrogz.net/tmp/canvas_shape_edge_arrows.html
Refresh the example to see a new random test case. It 'fails' if you have another 'shape' already overlapping one of the end points. One way to solve this would be to draw your shapes first to a blank canvas and then copy the result (drawImage) to the final canvas.
For Stack Overflow posterity (in case my site is down) here's the relevant code:
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>HTML5 Canvas Shape Edge Detection (for Arrow)</title>
<style type="text/css">
body { background:#eee; margin:2em 4em; text-align:center; }
canvas { background:#fff; border:1px solid #666 }
</style>
</head><body>
<canvas width="800" height="600"></canvas>
<script type="text/javascript">
var ctx = document.querySelector('canvas').getContext('2d');
for (var i=0;i<20;++i) randomCircle(ctx,'#999');
var start = randomDiamond(ctx,'#060');
var end = randomDiamond(ctx,'#600');
ctx.lineWidth = 2;
ctx.fillStyle = ctx.strokeStyle = '#099';
arrow(ctx,start,end,10);
function arrow(ctx,p1,p2,size){
ctx.save();
var points = edges(ctx,p1,p2);
if (points.length < 2) return
p1 = points[0], p2=points[points.length-1];
// Rotate the context to point along the path
var dx = p2.x-p1.x, dy=p2.y-p1.y, len=Math.sqrt(dx*dx+dy*dy);
ctx.translate(p2.x,p2.y);
ctx.rotate(Math.atan2(dy,dx));
// line
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(-len,0);
ctx.closePath();
ctx.stroke();
// arrowhead
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(-size,-size);
ctx.lineTo(-size, size);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// Find all transparent/opaque transitions between two points
// Uses http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
function edges(ctx,p1,p2,cutoff){
if (!cutoff) cutoff = 220; // alpha threshold
var dx = Math.abs(p2.x - p1.x), dy = Math.abs(p2.y - p1.y),
sx = p2.x > p1.x ? 1 : -1, sy = p2.y > p1.y ? 1 : -1;
var x0 = Math.min(p1.x,p2.x), y0=Math.min(p1.y,p2.y);
var pixels = ctx.getImageData(x0,y0,dx+1,dy+1).data;
var hits=[], over=null;
for (x=p1.x,y=p1.y,e=dx-dy; x!=p2.x||y!=p2.y;){
var alpha = pixels[((y-y0)*(dx+1)+x-x0)*4 + 3];
if (over!=null && (over ? alpha<cutoff : alpha>=cutoff)){
hits.push({x:x,y:y});
}
var e2 = 2*e;
if (e2 > -dy){ e-=dy; x+=sx }
if (e2 < dx){ e+=dx; y+=sy }
over = alpha>=cutoff;
}
return hits;
}
function randomDiamond(ctx,color){
var x = Math.round(Math.random()*(ctx.canvas.width - 100) + 50),
y = Math.round(Math.random()*(ctx.canvas.height - 100) + 50);
ctx.save();
ctx.fillStyle = color;
ctx.translate(x,y);
ctx.rotate(Math.random() * Math.PI);
var scale = Math.random()*0.8 + 0.4;
ctx.scale(scale,scale);
ctx.lineWidth = 5/scale;
ctx.fillRect(-50,-50,100,100);
ctx.strokeRect(-50,-50,100,100);
ctx.restore();
return {x:x,y:y};
}
function randomCircle(ctx,color){
ctx.save();
ctx.beginPath();
ctx.arc(
Math.round(Math.random()*(ctx.canvas.width - 100) + 50),
Math.round(Math.random()*(ctx.canvas.height - 100) + 50),
Math.random()*20 + 10,
0, Math.PI * 2, false
);
ctx.fillStyle = color;
ctx.fill();
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
</script>
</body></html>

Categories

Resources