D3 map, 'd' attribute - javascript

(sorry for my english bad level)
Hi I'm using D3 for the first time with mithril js. The map is ok but I have a problem with colors of provinces and it comes from the 'd' attribute to get the id of provinces.The attribute is undefined and I don't understand what is 'd' exactly. is mithril the problem? is there an other way to get 'd' attribute?
controller.map = function(el){
var width = 1160;
var height = 960;
var scale = 10000;
var offset = [width / 2, height / 2];
var center = [0, 50.64];
var rotate = [-4.668, 0];
var parallels = [51.74, 49.34];
var projection = d3.geo.albers()
.center(center)
.rotate(rotate)
.parallels(parallels)
.scale(scale)
.translate(offset)
;
var path = d3.geo.path()
.projection(projection)
;
var svg = d3.select(el).append("svg")
.attr("width",width)
.attr("height",height)
;
d3.json("belprov.json",function(error,be){
if (error) return console.error(error);
var bounds = path.bounds(topojson.feature(be, be.objects.subunits));
var hscale = scale*width / (bounds[1][0] - bounds[0][0]);
var vscale = scale*height / (bounds[1][1] - bounds[0][1]);
scale = (hscale < vscale) ? hscale : vscale;
offset = [width - (bounds[0][0] + bounds[1][0])/2,
height - (bounds[0][1] + bounds[1][1])/2];
var centroid = d3.geo.centroid(topojson.feature(be, be.objects.subunits));
center = [0, centroid[1]];
rotate = [-centroid[0],0];
projection = d3.geo.albers()
.center(center)
.rotate(rotate)
.parallels(parallels)
.scale(scale)
.translate(offset);
svg.selectAll(".province")
.data(topojson.feature(be, be.objects.provinces).features)
.enter().append("path")
.attr("class", function(d) { return "province " + d.id })
.attr("d", path)
;
})
};

The "d" attribute in a path object defines the successive coordinates of the points through which the path has to go (it also gives indication about whether the path should use bezier curves, straight lines, etc.). See some documentation here.
Be careful: in d3, d is often used as a parameter for anonymous functions representing the data currently binded to the current element. So the two are completely different things.
Here, your line
.attr("d", path)
should probably look more like
.attr("d", function(d){return d.path})
i.e., take the field path within the data elements.

You can do something like this to color diffrent paths:
//make a color scale
var color20 = d3.scale.category20();
//your code as you doing
//on making paths do
svg.selectAll(".province")
.data(topojson.feature(be, be.objects.provinces).features)
.enter().append("path")
.attr("class", function(d) { return "province " + d.id })
.style("fill", function(d){return color(d.id);})//do this to color path based on id.
.attr("d", path)

Related

how to create three-level donut chart in d3.js

I'm using trying to create a multi-level donut chart in d3 version5
This image is drawn by d3 version3. it is working fine in version3. I decided to upgrade d3 to the latest version. now, donut chart is not drawn by d3(also no errors in the console)
D3 version 3 > version 5
Here is the sample dataset I used:
Hint: first value in the array is used storage and second is free storage
{
average: [30.012, 69.988],
minimum: [10, 90],
maximum: [40, 60]
}
Note: Above data is just a sample this is not exact data.
Here is the code I tried:
var width = 300;
var height = 300;
var radius = Math.floor((width / 6) - 2);
var classFn = function(a, b) {
return a === 0 ? classes[b] : 'default';
};
var pie = d3.layout.pie().sort(null);
var arc = d3.svg.arc();
var svg = d3.select(selector).append("svg");
svg.attr("width", width);
svg.attr("height", height);
svg = svg.append("g");
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path");
path = path.data(function(d) {
return pie(d);
});
path.enter().append("path");
path.attr("class", function(d, i, j) {
return classFn(i, j);
})
path.attr("d", function(d, i, j) {
return arc.innerRadius((j === 0 ? 0 : 2) + radius * j).outerRadius(radius * (j + 1))(d);
});
Note: This code is working fine in d3 version3.
2. Update:
I've updated the answer with a better solution. I didn't do this at first, because I didn't grasp you structure. I've updated it to being more D3 idiomatic. Plus it does away with the hack I made in my first update :)
var dataset = {
average: [0, 100],
minimum: [0, 100],
maximum: [0, 100]
}
var width = 300;
var height = 300;
var radius = Math.floor((width / 6) - 2);
var pie = d3.pie().sort(null);
var arc = d3.arc();
var svg = d3.select('body').append("svg");
svg.attr("width", width);
svg.attr("height", height);
svg = svg.append("g");
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
gs.each(function (d, j) {
d3.select(this).selectAll('path')
.data(pie(d)).enter()
.append('path')
.attr("class", function(d, i) {
// return classFn(i);
})
.attr('d', function (d) {
return arc
.innerRadius((j === 0 ? 0 : 2) + radius * j)
.outerRadius(radius * (j + 1))(d);
})
})
The updated code uses the index (here j) that is available when appending the g elements, which corresponds to you original j index. This makes it possible to calculate the radii in the original way.
To achieve this, the arc appending code is wrapped into a .each function that iterates over the g elements, making j available to us.
The class application should work as well, but I've commented it out, as the classFn function doesn't work, since the classes variable is not present.
1. Update:
Besides the original answer, when calculating the arc radii you rely on a j value that is different from D3 v3 and v5. I summise that j is used the index of the d3.values array, so I've cooked up a way to reverse look-up that index based on the input values.
First create a map for reverse mapping data values into their corresponding index:
var dataValueJoinChar = 'ยค'
var datasetValuesToIndex = d3.values(dataset).reduce((acc, curr, i) => {
acc[`0${dataValueJoinChar}${curr[0]}`] = i
acc[`1${dataValueJoinChar}${curr[1]}`] = i
return acc
}, {})
Then change the last part of your code to:
path = path.data(function(d) {
return pie(d);
}).enter().append("path");
path.attr("class", function(d, i, j) {
return classFn(i, j);
})
path.attr("d", function(d, i, j) {
var orgIndex = datasetValuesToIndex[`${i}${dataValueJoinChar}${d.data}`]
return arc
.innerRadius((orgIndex === 0 ? 0 : 2) + radius * orgIndex)
.outerRadius(radius * (orgIndex + 1))(d);
});
It might not be too pretty, but it's a simple adaption of your code that works.
------- Original answer --------
In D3 v5 pie and arc are found at d3.pie and d3.arc respectively. Therefore, try changing:
var pie = d3.layout.pie().sort(null);
var arc = d3.svg.arc();
To this instead:
var pie = d3.pie().sort(null);
var arc = d3.arc();
Pie API reference: https://github.com/d3/d3-shape/blob/v1.3.4/README.md#pie
Arc API reference: https://github.com/d3/d3-shape/blob/v1.3.4/README.md#arc
If you use a bundler to bundle sub-modules, both are part of the d3-shape module. If not they are both available in the full D3 library.
Hope this helps!

Colorbrewer in D3 & Scales

I have a geoJSON looking like so
{"type":"FeatureCollection",
"crs":{"type":"name",
"properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}},
"features":[{"type":"Feature",
"properties":{"scalerank":10,"natscale":1,"labelrank":8,"featurecla":"Admin-1 capital","name":"Colonia del Sacramento","namepar":null,"namealt":null,"diffascii":0,"nameascii":"Colonia del Sacramento","adm0cap":0,"capalt":0,"capin":null,"worldcity":0,"megacity":0,"sov0name":"Uruguay","sov_a3":"URY","adm0name":"Uruguay","adm0_a3":"URY","adm1name":"Colonia","iso_a2":"UY","note":null,"latitude":-34.479999,"longitude":-57.840002,"changed":4,"namediff":1,"diffnote":"Added missing admin-1 capital. Population from GeoNames.","pop_max":21714,"pop_min":21714,"pop_other":0,"rank_max":7,"rank_min":7,"geonameid":3443013,"meganame":null,"ls_name":null,"ls_match":0,"checkme":0},
"geometry":{"type":"Point","coordinates":[-57.8400024734013,-34.4799990054175]
}}]
}
I want to set to use colorbrewer to chose colors, depending on the value pop_max takes. Then I want to display this point data on a leaflet map through overlaying a svg ontop of leaflet. I can easily display the points and chose the color like so:
var feature = g.selectAll("path")
.data(collection.features)
.enter()
.append("path")
.style("fill", function(d) {
if(d.properties.pop_max) < 1000 {
return("red")
} else if {....
};
});
However, inconvenient.
So i tried:
var colorScale = d3.scale.quantize()
.range(colorbrewer.Greens[7])
.domain(0,30000000);
var feature = g.selectAll("path")
.data(collection.features)
.enter()
.append("path")
.style("fill", function(d) {
colorScale(d.properties.pop_max);
});
That does not display any points at all... Note that I estimated my domain. 0 is not necessarily the lowest number nor 30000000 the highest.
Any ideas?
First you'll need to find the max and min pop_max, something like this should work:
var extent = d3.extent(geojson.features, function(d) { return d.properties.pop_max; });
Second, since you want colors to represent 7 ranges of values you should be using d3.scale.threshold:
var N = 7,
step = (extent[1] - extent[0]) / (N + 1),
domain = d3.range(extent[0], extent[1] + step, step);
var colorScale = d3.scale.threshold()
.domain(domain)
.range(colorbrewer.Greens[N]);
EDITS
Looks like quantile can do this easier:
d3.scale.quantile()
.domain([extent[0], extent[1]])
.range(colorbrewer.Greens[N]);

Unable to get zoom by dragging a rectangle over a d3 series chart to work properly

I am trying to get zoom to work by dragging a rectangle over my series plot to identify the interval of zooming. Here is my plunkr
http://plnkr.co/edit/isaHzvCO6fTNlXpE18Yt?p=preview
You can see the issue by drawing a rectangle with the mouse over the chart - The new chart overshoots the boundary of the X and Y axes. I thought my group under the svg would take care of the bounds of the series (path) but I am clearly mistaken. After staring at it for a long time, I could not figure it out. Please ignore the angular aspect of the plunkr. I think the issue is somewhere in the
//Build series group
var series = svgGroup.selectAll(".series")
.data(data)
.enter().append("g")
.attr("class", "series");
//Build each series using the line function
series.append("path")
.attr("class", "line")
.attr("d", function (d) {
return line(d.series);
})
.attr("id", function (d) {
//While generating the id for each series, map series name to the path element.
//This is useful later on for dealing with legend clicks to enable/disable plots
legendMap[d.name] = this;
//Build series id
return buildPathId(d.name);
})
.style("stroke", function (d) {
//Use series name to get the color for plotting
return colorFcn(d.name);
})
.style("stroke-width", "1px")
.style("fill", "none");
Any help with this is appreciated.
Thank you very much.
I think the method renderChartWithinSpecifiedInterval(minX, maxX, minY, maxY, pixelCoordinates) maybe has some problem there.
It seems the parameter like max_x passed in line 130 are a very big value like time seconds
var svg = renderChartWithinSpecifiedInterval(min_X, max_X, min_Y, max_Y, false);
max_X,min_X are value like 1415171404335
min_Y = 0, max_Y = 100
But in dragDrop call in line 192
function gEnd(d,i){
svg.selectAll(".zoom-rect").remove();
var svgGp = svg.select("g");
var groupTransform = d3.transform(svgGp.attr("transform"));
var xOffset = groupTransform.translate[0];
var yOffset = groupTransform.translate[1];
var xBegin = Math.min(xStart,xDyn) - xOffset;
var xEnd = Math.max(xStart,xDyn) - xOffset;
var yBegin = Math.min(yStart,yDyn) - yOffset;
var yEnd = Math.max(yStart,yDyn) - yOffset;
renderChartWithinSpecifiedInterval(xBegin, xEnd, yBegin, yEnd, true);
//It seems here the parameters values are all pixels
like xBegin = 100, xEnd = 200
}
hope it helps!

how to create a dynamic append using d3?

I'm using d3 and I'd like to append a group with basic shapes attached to it, like the following:
startEvent (a circle)
task (a recangle)
endEvent (two circles)
since I'm new to d3 I'd like to know how to append each group dynamically depending on the 'shape type' and avoid to append each shape one by one using a foreach.
this is the code:
var shapes ={
startEvent:function(id,x,y,params){
var radius = 18,
cy = Math.floor(Number(y) + radius),
cx = Math.floor(Number(x) + radius),
g = d3.select('g');
var circle = g.append('circle')
.attr('cx', cx)
.attr('cy', cy)
.attr('r', radius)
.attr('id', id);
if(params.label!==undefined){
var txt = g.append('text')
.attr('y',y).text(params.label);
txt.attr('x',Number(x));
txt.attr('y',Number(y));
}
return g;
},
endEvent:function(id,x,y, params){
// something similar to startEvent, but with two circles instead of one
},
task:function(id,x,y, params){
// something similar but with a rectangle
}
};
passing the data and rendering the elements:
svg.selectAll('g')
.data(data)
.enter()
.append(function(d){
params={label: d.meta.name};
return shapes[d.type](d.id,d.x,d.y,params);
});
but I'm getting
Error: Failed to execute 'appendChild' on 'Node': The new child
element is null.
I guess that's because I'm returning the selector, any ideas?
based on this and this answers I got to the following point, it seems like you need to create an instance manually under the d3 namespace, once you got that you can use a d3 selector over it and return the node() of the element which return the actual DOM code.
this is the code:
var shapes ={
startEvent:function(id,x,y,params){
var radius = 18,
cy = Math.floor(Number(y) + radius),
cx = Math.floor(Number(x) + radius),
e = document.createElementNS(d3.ns.prefix.svg,'g'),
g = d3.select(e).attr('id', id).
attr('class','node');
var circle = g.append('circle')
.attr('cx', cx)
.attr('cy', cy)
.attr('r', radius)
.attr('class','circle');
if(params.label!==undefined){
var txt = g.append('text')
.attr('y',y).text(params.label);
txt.attr('x',Number(x));
txt.attr('y',Number(y));
}
return g;
},
endEvent:function(id,x,y, params){
// something similar to startEvent, but with two circles instead of one
},
task:function(id,x,y, params){
// something similar but with a rectangle
}
};
and then return the node
svg.selectAll('g')
.data(data)
.enter()
.append(function(d){
params={label: d.meta.name};
var v = shapes[d.type](d.id,d.x,d.y,params);
return v.node();
});

d3.js Two Dimensional Array Bar Chart

I am trying to make a bar chart containing 3 'groups' of data in d3.js. I have been able to implement the example from "Let's Make a Bar Chart" sample http://bost.ocks.org/mike/bar/2/, but am wondering how you would implement that with a 2 dimensional data set instead.
I'm having a bit of a tough time wrapping my head around what I feel should be a fairly straight forward process (select first index of 2d array -> iterate through that array and display the values of each element in bars -> select second index of array -> iterate through that array and display the value of each element in bars etc...), so examples would be hugely appreciated.
The code thus far is:
var data = [4, 8, 15, 16, 23, 42];*/
var width = Math.max(500, innerWidth), height = Math.max(500, innerHeight), barHeight = 80;
var x = d3.scale.linear().domain([0, d3.max(data)]).range([0, width]);
var chart = d3.select(".chart").attr("width", width).attr("height", barHeight * data.length * 3);
var bar = chart.selectAll("g").data(data).enter().append("g").attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect").attr("width", x).attr("height", barHeight - 1);
bar.append("text").attr("x", function(d) { return x(d) - 3; }).attr("y", barHeight / 2).attr("dy", ".35em").text(function(d) { return d; });
however I would like to change the "data" variable to be:
var data = [[40,45,5],
[50,49,2],
[60,62,4],
[40,41,6],
[42,40,3],
[65,67,10],
[70,67,17],
[66,65,2],
[45,44,3],
[39,38,7],
[38,38,8],
[45,40,4],
[43,35,3],
[50,65,8],
[51,50,6]];

Categories

Resources