d3: drawing multiple rectangles using areas - javascript

I need to draw draw many filled rectangles inside a graph and would like to achieve this using svg paths.
(I'm not using the rect tag because perfomance will suffer over time.)
My current approach uses the d3.svg.area to generate the path for each area, but the rectangles are not being drawn properly.
As far as I understand the rendered path attribute, it seems that path is missing a moveTo per rectangle.
The following is my simplified code of the problem.
var data = [
{x0:0,x1:50,y0:0,y1:10},
{x10:0,x1:60,y0:20,y1:30},
];
var width = 500;
var barHeight = 20;
var areaFunc = d3.svg.area()
//.interpolate('step')
.x0(function(d){return d.x0;})
.x1(function(d){return d.x1;})
.y0(function(d){return d.y0;})
.y1(function(d){return d.y1;});
var chart = d3.select('#chart')
.attr('width', width)
.attr('height', barHeight * data.length);
chart.append('path')
//.data(data)
.attr('d', areaFunc(data))
.attr('class', 'absences area')
.attr('style', 'fill:blue;stroke:black;stroke-width:1');
JSFiddle: http://jsfiddle.net/3kLdkgz8/

If you want several rectangles, you need to define your data like that
var data = [
[{x0:10,x1:60,y0:0,y1:0},{x0:10,x1:60,y0:20,y1:20}], // rect 1
[{x0:100,x1:600,y0:20,y1:20},{x0:100,x1:600,y0:200,y1:200}]// rect 2
];
And call it like that
chart.selectAll('path')
.data(data)
.enter()
.append('path')
.attr('d', areaFunc)
.attr('class', 'absences area')
.attr('style', 'fill:blue;stroke:black;stroke-width:1');
See http://jsfiddle.net/zh1vqfos/2/

Related

hide circles on Orthographic drag

I've created a globe which has circles and a drag. The problem is that the circles appear on the far side of the globe. I would like those circles to be hidden.
My bl.ock can be found here:
http://bl.ocks.org/anonymous/dc2d4fc810550586d40d4b1ce9088422/40c6e199a5be4e152c0bd94a13ea94eba41f004b
For example, I would like my globe to function like this one: https://bl.ocks.org/larsvers/f8efeabf480244d59001310f70815b4e
I've seen solutions such as this one: How to move points in an orthogonal map? but it doesn't quite work for me. The points simply disappear, as d[0] and d[1] seem to be undefined.
I've also tried using methods such as this: http://blockbuilder.org/tlfrd/df1f1f705c7940a6a7c0dca47041fec8 but that also doesn't seem to work. The problem here seems to be that he is using the json as his data, while my circles data are independent of the json.
Only similar example I've found is the one: https://bl.ocks.org/curran/115407b42ef85b0758595d05c825b346 from Curran but I don't really understand his code. His method is quite different than mine.
Here is my JavaScript code:
(function(){
var h = 600;
var w = 900;
var i = 0;
var map = void 0;
var world = void 0;
var US = void 0;
var margin = {
top: 10,
bottom: 40,
left: 0,
right: 30
};
var circleScale = d3.scaleSqrt()
.domain([0, 4445])
.range([0.5, 10])
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;
var dragging = function(d){
var c = projection.rotate();
projection.rotate([c[0] + d3.event.dx/6, c[1] - d3.event.dy/6])
map.selectAll('path').attr('d', path);
map.selectAll(".circles").attr("cx", function(d){
var coords = projection([d.Longitude_imp, d.Latitude_imp])
return coords[0];
})
.attr("cy", function(d){
var coords = projection([d.Longitude_imp, d.Latitude_imp])
return coords[1];
})
}
var drag = d3.drag()
.on("drag", dragging)
var projection = d3.geoOrthographic().clipAngle(90);
var path = d3.geoPath().projection(projection);
var svg = d3.select("body")
.append("svg")
.attr("id", "chart")
.attr("width", w)
.attr("height", h)
d3.json("world.json", function(json){
d3.csv("arms_transfer_2012_2016_top - arms_transfer_2012_2016_top.csv", function(error, data){
var countries = topojson.feature(json, json.objects.countries).features
var US = countries[168]
map = svg.append('g').attr('class', 'boundary');
world = map.selectAll('path').data(countries);
US = map.selectAll('.US').data([US]);
Circles = map.selectAll(".circles").data(data)
console.log(countries[168])
world.enter()
.append("path")
.attr("class", "boundary")
.attr("d", path)
US.enter()
.append("path")
.attr("class", "US")
.attr("d", path)
.style("fill", "lightyellow")
.style("stroke", "orange")
Circles.enter()
.append("circle")
.attr("class", "circles")
.attr("r", function(d){
return circleScale(d.Millions)
})
.attr("cx", function(d){
var coords = projection([d.Longitude_imp, d.Latitude_imp])
return coords[0];
})
.attr("cy", function(d){
var coords = projection([d.Longitude_imp, d.Latitude_imp])
return coords[1];
})
.style("fill", "#cd0d0e")
svg.append("rect")
.attr("class", "overlay")
.attr("width", w)
.attr("height", h)
.call(drag)
})
})
})();
There are a few different methods to achieve this, but one of the easier methods would be to calculate the angular distance between the projection centroid (as determined by the rotation) and the circle center on the drag event:
map.selectAll("circle")
.style("display", function(d) {
var circle = [d.Longitude_imp, d.Latitude_imp];
var rotate = projection.rotate(); // antipode of actual rotational center.
var center = [-rotate[0], -rotate[1]]
var distance = d3.geoDistance(circle,center);
return (distance > Math.PI/2 ) ? 'none' : 'inline';
})
Take the center of each point and get the rotational center with projection.rotate() - note that the rotation values are inverse of the centering point. A rotation of [10,-20] centers the map at [-10,20], you move the map under you. With these two points we can use d3.geoDistance() which calculates the distance between two points in radians, hence the use of Math.PI/2 - which gives us points outside of 90 degrees, for these we hide, for the rest we show.
This can be incorporated a little nicer into your code, but I keep it separate here to show what is happening clearer.
Here's an example block - drag to trigger, I haven't applied the logic to the initial load.
An alternative approach, as noted by Gerardo Furtado, would be to use a path to display the circles - using path.pointRadius to set the size of the circle for each point. Instead of appending a circle, you could append path with the following format:
Circles.enter()
.append("path")
.attr("class", "circles")
.attr("d",createGeojsonPoint)
The, on update/drag:
map.selectAll('.circles').attr('d',createGeojsonPoint);
This method uses the clip angle of the orthographic to hide features when they are more than 90 degrees from the center of the projection (as determined by rotation). Your createGeojsonPoint function needs to set the radius and return a valid geojson object:
var createGeojsonPoint = function(d) {
console.log(d);
path.pointRadius(circleScale(d.Millions)); // set point radius
return path({"type":"Point","coordinates":[d.Longitude_imp,d.Latitude_imp]}) // create geojson point, return path data
}
All together, with the necessary modifications, your code might look like this.

d3.js projection scale doesn't increase a size of a country

I'm trying to build something with d3.js and GeoJSON for the first time. I have managed to get a country - Estonia to be displayed but it is so small you can barely see it. I tried to play with projection geoMercator().scale but it doesn't work - no increase in size.
Please see a picture attached below(under bar chart):
Here is my js:
var projection = d3.geoMercator()
.translate([w/2, h/2])
.scale([100]);
var path = d3.geoPath()
.projection(projection)
var w3 = 2000;
var h3 = 1500;
var svg = d3.select("body")
.append("svg")
.attr("width", w3)
.attr("height", h3)
d3.json("estonia.json", function (json){
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("fill", "#2294AA");
})
What am I doing wrong?
I took your code and saw that when I increased the scale, Estonia disappeared. Had to center the projection like this:
var projection = d3.geoMercator()
.center([24.312863, 57.793424])
.scale([500])
.translate([w/2, h/2])
I took the coordinates [24.312863, 57.793424] from the .json file.

How to add interactive features (line and label) to a D3 line chart?

I am new to D3.js and currently developing a line chart for one of my projects. I referred the documentation and example here and created my own version here.
Now, I am planning to add two interactive features here:
On mouseover in the chart draw a vertical line to the nearest data point.
Show a label with X and Y attributes next to this vertical line.
To make this features more clear, please refer this example.
Here is what I tried which came from a suggestion:
svg.append("g") // creating new 'group' element
.selectAll('rect') // adding a rectangle for the label
.selectAll('line'); // adding a line
However, the line ans rectangle doesnt show up. I have been Googling a lot but in no vain. What am I missing?
jsFiddle
//create groups for line and label (and invisible selection area)
var infos = svg.append('g')
.selectAll('rect')
.data(data)
.enter()
.append('g')
//move to datapoint location
.attr('transform',function(d,i){d.x = x(d.date) ; d.y = 0; return "translate(" + d.x + "," + d.y + ")";});
//create and select line "rectangles" (easier than doing actual lines)
infos.append("rect")
.attr('class','line')
.attr('height', height)
.attr('width', 1)
.attr('opacity',0);
//create and select line "rectangles" (easier than doing actual lines)
infos.append("rect")
.attr('class','area')
.attr('height', height)
//should probably do something to make sure they don't overlap, such as measure distance between neighbours and use that as width
.attr('width', width/data.length/2)
.attr('opacity',0)
//move so that the data point is in the middle
.attr('x',-width/data.length/4)
.on('mouseover', function(){
g_elem = this.parentNode;
d3.select(g_elem).selectAll(".line").attr("opacity",1);
})
.on('mouseout', function(){
g_elem = this.parentNode;
d3.select(g_elem).selectAll(".line").attr("opacity",0);
});
http://jsfiddle.net/SKb8W/7/
For labels, here is a useful example: http://jsfiddle.net/WLYUY/5/ (from D3.js: Position tooltips using element position, not mouse position?)
And for using mouse coordinates you can do something like:
var coordinates = [0, 0];
coordinates = d3.mouse(this);
var x = coordinates[0];
var y = coordinates[1];
(from Mouse position in D3)

How do make my plot points smooth in d3.js using projection?

I'm able to plot some weather data onto a map using the following code. However the points are rectangles and i'd like to get them smoother.
,
I'd like to plot them smoother like something similar to
I believe I need to look into interpolating, spatial analysis, and/or Choropleth maps. I think they are different algorithms in doing this. I feel like i need to fill in more points in between the existing ones? And with that is it possible to make gradient like points? Is this doable in D3? Or should i consider using three.js or WebGL stuff?
var width = 960,
height = 960;
var map = {};
var projection = d3.geo.mercator()
.scale((width + 1) / 2 / Math.PI)
.translate([width / 2, height / 2])
.precision(.1);
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);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("world-50m.json", function(error, world) {
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
map.plot_points = [];
map.max = 30;
map.min = -1;
var opacity = d3.scale.linear()
.domain([map.min, map.max])
.range([0,1]);
var rainbow = ["#CE0C82", "#800CCE", "#1F0CCE", "#0C5BCE", "#0C99CE", "#2ECE0C", "#BAE806", "#FEFF00", "#FFCD00", "#FF9A00", "#FF6000", "#FF0000"];
zs.forEach(function(zv,zi){
zv.forEach(function(zzv, zzi){
if(zzv != 999)
{
map.plot_points.push({lat: ys[zi], long:xs[zzi],value:zzv});
}
})
});
console.log(map);
var points = svg.selectAll("rects.points")
.data(map.plot_points)
.enter()
.append("rect")
.attr("class", "points")
.style("fill", function(d) {
var scale = d3.scale.linear().domain([map.min, map.max]).range([1, rainbow.length]);
return rainbow[Math.round(scale(d.value))];
}).attr("width", 8)
.attr("height", 8)
.style("fill-opacity", 1)
.attr("transform", function(d) {
return "translate(" + projection([d.long, d.lat]) + ")";
})
It sounds like the problem in your case is the data. What you would need to do is take the original data and interpolate it to a smoother form. For this, you can use a GIS program such as QGIS. How exactly to do that depends on what format your original data is in.
Once you have the smoother data, you can plot it again in D3. My guess is that the end result would be somewhat similar to what I've done here, where contour lines are drawn to much the same effect as what you're aiming for.
Maybe you could take a look into heatmap js.
http://www.patrick-wied.at/static/heatmapjs/
Although is point based it may give you a hint.
It uses canvas instead of svg.
Jason Davies wrote an implementation of the conrec algorithm that does exactly what you need:
https://github.com/jasondavies/conrec.js
It's got a working example inside
I believe that the white stripes are happening because of the projection you are using;
In fact the height of each rectangle should adjust accordingly going north and south from the Equator, because the Mercator projection alters the distance going north and south.
To have a fixed height of the rectangles you could try with this projection instead:
http://bl.ocks.org/mbostock/3757119
which preserves the dimension going north and south

D3js how do you draw a circle exactly at the middle of a doughnut chart

I wanted to create a doughnut chart with d3.js and I want to put a navigation button just exactly on the middle of it, maybe when clicked shrinks the whole chart into a smaller version (expand probably). But that is not how complex my question is, but if you can find an example that probably exactly what I wanted to have, its better.
The question now is just position a circle, I don't know if there is a better alternative for it, but this is how it goes.
var height=800,
width=800;
var data = [10,50,80];
var color = d3.scale.ordinal()
.range(["red", "blue", "yellow"]);
// the graph
var radius=300;
var canvas = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var group = canvas.append("g")
.attr("transform", "translate(300,300)");
var arc = d3.svg.arc()
.innerRadius(100)
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function(d){
return d;
});
var arcs = group.selectAll(".arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc");
arcs.append("path")
.attr("d", arc)
.attr("fill", function(d){
return color(d.data);
});
canvas.append("g")
.attr("class", "collapse")
.append("circle")
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 100);
I just a did a dirty append of circle, because I don't want to go on, if there is a better way to do it, that is why I asked. How would one make it to the center, and if you are to generous, how to add events on it, probably using on, and then scale it down to about 100px and make it the button itself, so when toggled again, it collapses
EDIT
Guess I'm a bit asking to much, how about:
Center the button without hard coding 300px to it? and on your opinion , would an svg circle can do fine as a button?

Categories

Resources