Related
So I've been trying to create a donut chart in d3.js and am having trouble adding labels to the chart. My chart data is in an array, but I think because of the "pie" variable, only the "value" from the data is being passed through and not the "text". Have tried multiple ways to try and bring the "text" in but with no luck. Hopefully a fresh set of eyes can see where my mistake is!
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 750 - margin.left - margin.right,
height = 520 - margin.top - margin.bottom;
var r = height/3;
var aColor = [
'#0652DD',
'#C4E538',
'#F79F1F',
'#5758BB',
'#D980FA',
"#EA2027"
]
var piedata = [
{text:"Facebook", "value":76},
{text:"Website", "value":13},
{text:"HardwareZone", "value":4},
{text:"YouTube", "value":5},
{text:"Instagram", "value":1},
{text:"Twitter","value":1},
];
var vis = d3.select('#chart2')
.append("svg:svg")
.data([piedata])
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var pie = d3.layout.pie().sort(null).value(function(d){return d.value;});
// Declare an arc generator function
var arc = d3.svg.arc().innerRadius(r *0.5).outerRadius(r*0.8);
var outerArc = d3.svg.arc()
.innerRadius(r*0.95)
.outerRadius(r*0.95);
// Select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice").attr("transform", "translate(" + width/2 + "," + height/2 + ")");
arcs.append("g:path")
.attr("fill", function(d, i){return aColor[i];})
.attr("d", function (d) {return arc(d);})
.attr("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.7)
;
// Add the polylines between chart and labels:
arcs.append("g:polyline")
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", "1px")
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) + 5 // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d) + 5; // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = r * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
});
//Add text labels
arcs.append("g:label")
.attr('transform', function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
pos[0] = r * 0.99 * (midangle < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
return (midangle < Math.PI ? 'start' : 'end')
})
.text(function(d) { return d.text; }); //this is where the problem is!
Here is how you can add labels:
arcs.append('text')
.text(d => d.data.text)
.attr('dy', 4)
.attr('text-anchor', d => (d.startAngle + d.endAngle) / 2 > Math.PI ? 'end' : 'start')
.attr('x', d => outerArc.centroid(d)[0])
.attr('y', d => outerArc.centroid(d)[1]);
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 750 - margin.left - margin.right,
height = 520 - margin.top - margin.bottom;
var r = height/3;
var aColor = [
'#0652DD',
'#C4E538',
'#F79F1F',
'#5758BB',
'#D980FA',
"#EA2027"
]
var piedata = [
{text:"Facebook", "value":76},
{text:"Website", "value":13},
{text:"HardwareZone", "value":4},
{text:"YouTube", "value":5},
{text:"Instagram", "value":1},
{text:"Twitter","value":1},
];
var vis = d3.select('#chart2')
.append("svg:svg")
.data([piedata])
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var pie = d3.layout.pie().sort(null).value(function(d){return d.value;});
// Declare an arc generator function
var arc = d3.svg.arc().innerRadius(r *0.5).outerRadius(r*0.8);
var outerArc = d3.svg.arc()
.innerRadius(r*0.95)
.outerRadius(r*1.1);
// Select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice").attr("transform", "translate(" + width/2 + "," + height/2 + ")");
arcs.append("g:path")
.attr("fill", function(d, i){return aColor[i];})
.attr("d", function (d) {return arc(d);})
.attr("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.7)
;
// Add the polylines between chart and labels:
arcs.append("g:polyline")
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", "1px")
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) + 5 // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d) + 5; // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = r * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
});
//Add text labels
arcs.append('text')
.text(d => d.data.text)
.attr('dy', 4)
.each(d => console.log(d))
.attr('text-anchor', d => (d.startAngle + d.endAngle) / 2 > Math.PI ? 'end' : 'start')
.attr('x', d => outerArc.centroid(d)[0])
.attr('y', d => outerArc.centroid(d)[1]);
text {
font-size: 16px;
font-family: "Ubuntu";
fill: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div id="chart2" />
I am trying to create a corrologram using a set of data using D3.js.
I used R to create the correlation matrix but for visualization I want to use D3js and create a chart which shows the correlation matrix as in the picture. Can anyone guide me on this please.
Interesting problem so I took a whack at it. Using the mtcars dataset and given an R calculated correlation matrix, output in a CSV format using:
write.csv(cor(mtcars), file="data.csv")
Which creates:
"","mpg","cyl","disp","hp","drat","wt","qsec","vs","am","gear","carb"
"mpg",1,-0.852161959426613,-0.847551379262479,-0.776168371826586,0.681171907806749,-0.867659376517228,0.418684033921778,0.664038919127593,0.599832429454648,0.480284757338842,-0.550925073902459
"cyl",-0.852161959426613,1,0.902032872146999,0.83244745272182,-0.69993811382877,0.782495794463241,-0.591242073768869,-0.810811796083005,-0.522607046900675,-0.492686599389471,0.526988293749643
You can replicate your plot with d3:
d3.csv("data.csv", function(error, rows) {
// read in the CSV file and put the data in a d3 format or an array of objects
var data = [];
rows.forEach(function(d) {
var x = d[""]; // x represent the column name
delete d[""];
for (prop in d) {
var y = prop, // y is this row name
value = d[prop]; // correlation value
data.push({
x: x,
y: y,
value: +value
});
}
});
// standard d3 plot setup
var margin = {
top: 25,
right: 80,
bottom: 25,
left: 25
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
domain = d3.set(data.map(function(d) { // our domain is just the column names
return d.x
})).values(),
num = Math.sqrt(data.length), // how many rows and columns
color = d3.scale.linear() // our color scale from red to white to blue
.domain([-1, 0, 1])
.range(["#B22222", "#fff", "#000080"]);
// set-up x and y scale
var x = d3.scale
.ordinal()
.rangePoints([0, width])
.domain(domain),
y = d3.scale
.ordinal()
.rangePoints([0, height])
.domain(domain),
xSpace = x.range()[1] - x.range()[0], // this is the space of each grid space
ySpace = y.range()[1] - y.range()[0];
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 + ")");
// bind our data for each grid space
var cor = svg.selectAll(".cor")
.data(data)
.enter()
.append("g")
.attr("class", "cor")
.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ")";
});
// outer rectangle on each grid space
cor.append("rect")
.attr("width", xSpace)
.attr("height", ySpace)
.attr("x", -xSpace / 2)
.attr("y", -ySpace / 2)
// filter out below the diagonal
cor.filter(function(d){
var ypos = domain.indexOf(d.y);
var xpos = domain.indexOf(d.x);
for (var i = (ypos + 1); i < num; i++){
if (i === xpos) return false;
}
return true;
})
// append a text
.append("text")
.attr("y", 5)
.text(function(d) {
if (d.x === d.y) {
return d.x;
} else {
return d.value.toFixed(2);
}
})
// color it
.style("fill", function(d){
if (d.value === 1) {
return "#000";
} else {
return color(d.value);
}
});
// filter above the diagonal
cor.filter(function(d){
var ypos = domain.indexOf(d.y);
var xpos = domain.indexOf(d.x);
for (var i = (ypos + 1); i < num; i++){
if (i === xpos) return true;
}
return false;
})
// add a circle
.append("circle")
.attr("r", function(d){
return (width / (num * 2)) * (Math.abs(d.value) + 0.1);
})
.style("fill", function(d){
if (d.value === 1) {
return "#000";
} else {
return color(d.value);
}
});
// build the "yAxis" color scale
// its a series of rects colored correctly
// to produce a smooth gradient
var aS = d3.scale
.linear()
.range([-margin.top + 5, height + margin.bottom - 5])
.domain([1, -1]);
var yA = d3.svg.axis()
.orient("right")
.scale(aS)
.tickPadding(7);
var aG = svg.append("g")
.attr("class", "y axis")
.call(yA)
.attr("transform", "translate(" + (width + margin.right / 2) + " ,0)")
var iR = d3.range(-1, 1.01, 0.01);
var h = height / iR.length + 3;
iR.forEach(function(d){
aG.append('rect')
.style('fill',color(d))
.style('stroke-width', 0)
.style('stoke', 'none')
.attr('height', h)
.attr('width', 10)
.attr('x', 0)
.attr('y', aS(d))
});
});
Here's the result:
Full working code.
We can use d3 v4 here's the updated code with d3 changes' log.
I use http://d3pie.org/#docs-settings
But there is no such parameter as the distance from the center to the internal labels.
Can someone tried to do it?
I want to move the internal labels closer to the outer edge of the circle.
Thank you so much.
now so:
need:
You can position the labels by defining a new arc as suggested in https://stackoverflow.com/a/8270668/2314737 and then applying the centroid function.
I defined a new arc newarc with an inner radius equal to 2/3 of the outer radius.
var newarc = d3.svg.arc()
.innerRadius(2 * radius / 3)
.outerRadius(radius);
Here's the JS code:
var width = 300;
var height = 300;
var svg = d3.select("body").append("svg");
svg.attr("width", width)
.attr("height", height);
var dataset = [11, 13, 18, 25, 31];
var radius = width / 2;
var innerRadius = 0;
var arc = d3.svg.arc()
.innerRadius(0)
.outerRadius(radius);
var pie = d3.layout.pie();
var arcs = svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + radius + ", " + radius + ")");
//Draw arc paths
var color = d3.scale.category10();
arcs.append("path")
.attr("fill", function (d, i) {
console.log(d);
return color(i);
})
.attr("stroke", "white")
.attr("d", arc);
var newarc = d3.svg.arc()
.innerRadius(2 * radius / 3)
.outerRadius(radius);
// Place labels
arcs.append("text")
.attr("transform", function (d) {
return "translate(" + newarc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.attr("fill", "white")
.text(function (d) {
return d.value + "%";
});
Here is a working demo: http://jsfiddle.net/user2314737/kvz8uev8/2/
I decided to enroll in another way.
I added my property in the object and function of positioning inner labels in D3pie file d3pie.js
This function is located on the line - 996 d3pie.js
positionLabelGroups: function(pie, section) {
d3.selectAll("." + pie.cssPrefix + "labelGroup-" + section)
.style("opacity", 0)
.attr("transform", function(d, i) {
var x, y;
if (section === "outer") {
x = pie.outerLabelGroupData[i].x;
y = pie.outerLabelGroupData[i].y;
} else {
var pieCenterCopy = extend(true, {}, pie.pieCenter);
// now recompute the "center" based on the current _innerRadius
if (pie.innerRadius > 0) {
var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
var newCoords = math.translate(pie.pieCenter.x, pie.pieCenter.y, pie.innerRadius, angle);
pieCenterCopy.x = newCoords.x;
pieCenterCopy.y = newCoords.y;
//console.log('i ='+i , 'angle='+angle, 'pieCenterCopy.x='+pieCenterCopy.x, 'pieCenterCopy.y='+pieCenterCopy.y);
}
var dims = helpers.getDimensions(pie.cssPrefix + "labelGroup" + i + "-inner");
var xOffset = dims.w / 2;
var yOffset = dims.h / 4; // confusing! Why 4? should be 2, but it doesn't look right
// ADD VARAIBLE HERE !!! =)
var divisor = pie.options.labels.inner.pieDistanceOfEnd;
x = pieCenterCopy.x + (pie.lineCoordGroups[i][0].x - pieCenterCopy.x) / divisor;
y = pieCenterCopy.y + (pie.lineCoordGroups[i][0].y - pieCenterCopy.y) / divisor;
x = x - xOffset;
y = y + yOffset;
}
return "translate(" + x + "," + y + ")";
});
},
I add var divisor = pie.options.labels.inner.pieDistanceOfEnd;
Then I spotted this property devoltnyh the configuration file bhp and passed for plotting parameters.
inner: {
format: "percentage",
hideWhenLessThanPercentage: null,
pieDistanceOfEnd : 1.8
},
Meaning pieDistanceOfEnd: 1 hang tag on the outer radius of the chart
value pieDistanceOfEnd: 1.25 turn them slightly inward ....
You can play these parameters and to achieve the desired option.
In d3pie.js look for the function positionLabelGroups. In this function both labels (outer and inner) are positioned.
To modify the distance from the center you can play with the x,y here:
x = pieCenterCopy.x + (pie.lineCoordGroups[i][0].x - pieCenterCopy.x) / 1.8;
y = pieCenterCopy.y + (pie.lineCoordGroups[i][0].y - pieCenterCopy.y) / 1.8;
What I did was decreasing the 1.8 to 1.2 and obtained what youre looking for. Dont know what the other vars do, but you can study the code to figure it out
here is my simple code for d3 scalable map:
winWidth = $(window).width();
winHeight = $(window).height();
projection = d3.geo.mercator()
.translate([0, 0])
.scale(winWidth / 2 / Math.PI);
zoom = d3.behavior.zoom()
.scaleExtent([1, 50])
.on("zoom", manageMap);
path = d3.geo.path()
.projection(projection);
svg = d3.select("#map").append("svg")
.attr("viewBox", "0 0 " + winWidth + " " + winHeight)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + winWidth / 2 + "," + winHeight / 2 + ")")
.call(zoom);
g = svg.append("g");
d3.json("world-50m.json", function(error, world) {
g.append("path")
.datum(topojson.feature(world, world.objects.countries))
.attr("class", "land")
.attr("d", path);
g.append("path")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
function manageMap() {
var t = d3.event.translate,
s = d3.event.scale;
t[0] = Math.min(winWidth / 2 * (s - 1), Math.max(winWidth / 2 * (1 - s), t[0]));
t[1] = Math.min(winHeight / 2 * (s - 1) + 230 * s, Math.max(winHeight / 2 * (1 - s) - 230 * s, t[1]));
zoom.translate(t);
g.style("stroke-width", 1/s).attr("transform", "translate(" + t + ")scale(" + s + ")");
svg.select("g").selectAll("circle")
.attr("cx", function(d) { return projection([d.lng, d.lat])[0]; })
.attr("cy", function(d) { return projection([d.lng, d.lat])[1]; })
.attr("r", 11/s);
}
Is there any simple way to have current visible area coordinates, when map is scaled and translated? I'm already try to project translation of map, but just got some strange numbers.
This will do it. I've also put it up on http://bl.ocks.org/sfinktah/1d38c8a268d893d769ed
Even if you have found your solution, this may be useful for future googlers.
function getScreenBounds() {
return [getPoint(0,0), getPoint()];
}
function getPoint(x,y) {
if (x == null) x = winWidth;
if (y == null) y = winHeight;
var container = g.node();
var svg = container.ownerSVGElement || container;
var point = svg.createSVGPoint();
point.x = x, point.y = y;
point = point.matrixTransform(container.getScreenCTM().inverse());
return formatLocation(projection.invert([point.x, point.y]), zoom.scale());
}
function formatLocation(p, k) {
var format = d3.format("." + Math.floor(Math.log(k) / 2 - 2) + "f");
return (p[1] < 0 ? format(-p[1]) + "°S" : format(p[1]) + "°N") + " "
+ (p[0] < 0 ? format(-p[0]) + "°W" : format(p[0]) + "°E");
}
I am zooming in on a map upon click but the latitude longitude points do not scale. They are rendered as circles and I would like them to move with the map. I am following the D3 template here: http://bl.ocks.org/mbostock/2206590
var map_width = 960,
map_height = 500,
jsonRoot = '/static/d3/json/',
centered;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([map_width / 2, map_height / 2]); // default projection type for d3.geo.path
var urls = {
counties: jsonRoot + "us-counties.json",
states: jsonRoot + "us-states.json"
}
, margin = { top: 0, right: 0, bottom: 0, left: 0 }
, width = 960 - margin.right - margin.left
, height = 500
, path = d3.geo.path().projection(projection)
, map;
var q = queue()
.defer(d3.json, jsonRoot + "us-counties.json")
.defer(d3.json, jsonRoot + "us-states.json")
.await(ready);
function ready(error, countylines, statelines) {
window.error = error;
window.countylines = countylines;
window.statelines = statelines;
if (error){
throw error;
}
var stateIds = {};
statelines.features.forEach(function(d) {
stateIds[d.id] = d.properties.name;
});
countylines.features.forEach(function(d) {
d.properties.state = stateIds[d.id.slice(0,2)];
})
// remove the loading text
d3.select('.loading').remove();
map = d3.select('#map').append('svg')
.style('width', width)
.style('height', height);
counties = map.append('g')
.attr('class', 'counties')
.selectAll('path')
.data(countylines.features)
.enter().append('path')
.attr('d', path);
counties.on('mouseover', showCaption)
.on('mousemove', showCaption)
.on('mouseout', function() {
caption.html(starter);
})
.on('click', clicked);
states = map.append('g')
.attr('class', 'states')
.selectAll('path')
.data(statelines.features)
.enter().append('path')
.attr('d', path);
// Captions
var caption = d3.select('#caption')
, starter = caption.html();
function showCaption(d, i) {
var name = [d.properties.name, d.properties.state].join(', ');
caption.html(name);
}
var systemSuccess = function(result){
console.log(result);
}
var site = map.append("circle")
.attr("r",5)
.classed("system", true)
.attr("latitude",37.77521)
.attr("longitude",-122.42854)
.attr("transform", function() {
return "translate(" + projection([-122.42854,37.77521]) + ")";
});
});
})
};
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;
}
counties.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
counties.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
states.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
map.selectAll(".system")
.attr("transform", function(d) { return "translate(" + projection([-122.42854, 37.77521 ]) + ")" });
}
});
The map scales appropriately. But not the points.
All help is appreciated!
As Lars suggested, you could do the following.
//Same projection and transformation as applicable to the path elements.
d3.selectAll("circle")
.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
I am not sure if the above code would work correctly...although I have used a similar premise through the "zoom" d3 behavior.
If you want your points to retain their size, but be at the right position; you could try semantic zooming
OR
you could keep the resize the circle's radius based on the scale like this:
d3.selectAll("circle")
.attr("r", 5/k);