d3.js - How to stylize a pie chart - javascript

I have the following pie chart:
var data = [12,44,44];
var width = 300,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null);
var piedata = pie(data);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 50);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(piedata)
.enter().append("path")
.attr("d", arc);
svg.selectAll("text").data(piedata)
.enter()
.append("text")
.attr("text-anchor", "middle")
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (radius - 75);
return d.x = Math.cos(a) * (radius - 30);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cy = Math.sin(a) * (radius - 75);
return d.y = Math.sin(a) * (radius - 30);
})
.text(function(d) { return d.value + '%'; });
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
text {
font: 12px sans-serif;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
The chart data is passed through an array, but how can I pass the color to be used by the chat through the array?
This is my actual array:
var data = [12,44,44];
But I would like to pass the color of items like this:
var data = [
{
color: 'red',
value: 12
},
{
color: 'blue',
value: 44
},
{
color: 'green',
value: 44
}
];
How do I implement my code to achieve the result below?

Given the data structure in your question...
var data = [
{
color: 'red',
value: 12
},
{
color: 'blue',
value: 44
},
{
color: 'green',
value: 44
}
];
... you'll have to, firstly, tell the pie generator the correct property:
var pie = d3.layout.pie()
.value(function(d) {
return d.value
})
.sort(null);
That being done, this is the important part: set the fill of the paths using the color property, that the pie generator puts under the data object:
.style("fill", function(d) {
return d.data.color
})
Here is your code with those changes:
var data = [{
color: 'red',
value: 12
},
{
color: 'blue',
value: 44
},
{
color: 'green',
value: 44
}
];
var width = 300,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.value(function(d) {
return d.value
})
.sort(null);
var piedata = pie(data);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 50);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(piedata)
.enter().append("path")
.style("fill", function(d) {
return d.data.color
})
.attr("d", arc);
svg.selectAll("text").data(piedata)
.enter()
.append("text")
.attr("text-anchor", "middle")
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle) / 2 - Math.PI / 2;
d.cx = Math.cos(a) * (radius - 75);
return d.x = Math.cos(a) * (radius - 30);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle) / 2 - Math.PI / 2;
d.cy = Math.sin(a) * (radius - 75);
return d.y = Math.sin(a) * (radius - 30);
})
.text(function(d) {
return d.value + '%';
});
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
text {
font: 12px sans-serif;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Try this. I think it will give you the answer.
var colorScale = d3.scale.ordinal().domain(["banana", "cherry", "blueberry"])
.range(["#eeff00", "#ff0022", "#2200ff"]);
pie.colors(function(d){ return colorScale(d.fruitType); });
or you can directly give
var colorScale = d3.scale.ordinal().range([/*array of hex values */]);
pie.colors(colorScale);

Related

D3 - add labels to polar plot

I found this working example how to create a polar plot with D3. What I can not figure out is how to add labels (compass labels like N, E, S , W) to the principal axes outside of the plot.
Also scale labels for the distance from the radius inside the plot. All I do is end up overlapping and rotated into seems arbitrary directions.
I'd just create a dummy data structure, with the key being the name, and the value being the angle I want it at. Then use some basic trigonometry to position the nodes correctly.
var width = 960,
height = 500,
radius = Math.min(width, height) / 2 - 30;
var r = d3.scale.linear()
.domain([0, 1])
.range([0, radius]);
var line = d3.svg.line.radial()
.radius(function(d) {
return r(d[1]);
})
.angle(function(d) {
return -d[0] + Math.PI / 2;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gr = svg.append("g")
.attr("class", "r axis")
.selectAll("g")
.data(r.ticks(3).slice(1))
.enter().append("g");
gr.append("circle")
.attr("r", r);
var ga = svg.append("g")
.attr("class", "a axis")
.selectAll("g")
.data(d3.range(0, 360, 30))
.enter().append("g")
.attr("transform", function(d) {
return "rotate(" + -d + ")";
});
ga.append("line")
.attr("x2", radius);
var color = d3.scale.category20();
var line = d3.svg.line.radial()
.radius(function(d) {
return r(d[1]);
})
.angle(function(d) {
return -d[0] + Math.PI / 2;
});
var data = [
[Math.PI / 3, Math.random()],
[Math.PI / 6, Math.random()],
[0 * Math.PI, Math.random()],
[(11 * Math.PI) / 6, Math.random()],
[(5 * Math.PI / 3), Math.random()],
[(3 * Math.PI) / 2, Math.random()],
[(4 * Math.PI / 3), Math.random()],
[(7 * Math.PI) / 6, Math.random()],
[Math.PI, Math.random()],
[(5 * Math.PI) / 6, Math.random()],
[(2 * Math.PI) / 3, Math.random()],
[Math.PI / 2, Math.random()]
];
var angles = {
N: 0,
E: Math.PI / 2,
S: Math.PI,
W: 3 * Math.PI / 2,
};
svg.selectAll("point")
.data(data)
.enter()
.append("circle")
.attr("class", "point")
.attr("transform", function(d) {
var coors = line([d]).slice(1).slice(0, -1);
return "translate(" + coors + ")"
})
.attr("r", 8)
.attr("fill", function(d, i) {
return color(i);
});
svg.selectAll(".angle")
.data(Object.keys(angles))
.enter()
.append("text")
.attr("class", "angle")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("transform", function(d) {
// Subtract because 0degrees is up at (0, 1) on the unit circle, but
// 0 radians is to the right, at (1, 0)
var angle = angles[d] - Math.PI / 2;
var textRadius = radius + 20;
var x = Math.cos(angle) * textRadius;
var y = Math.sin(angle) * textRadius;
return "translate(" + [x, y] + ")";
})
.text(function(d) { return d; })
.frame {
fill: none;
stroke: #000;
}
.axis text {
font: 10px sans-serif;
}
.axis line,
.axis circle {
fill: none;
stroke: steelblue;
stroke-dasharray: 4;
}
.axis:last-of-type circle {
stroke: steelblue;
stroke-dasharray: none;
}
.line {
fill: none;
stroke: orange;
stroke-width: 3px;
}
<script src="//d3js.org/d3.v3.min.js"></script>

d3js : how to put circles at the end of an arc

I'm trying to create a donut chart in d3js where each arc has a circle at its end.
Circle's edge must fit on arc's one.
I tried both by appending a circle and a circle wrapped in marker but with no succes.
Trying to append a marker seems to be the closest solution to the desired one but I can't help the marker oveflowing the arc edges.
Code:
var data = [
{
name: "punti",
count: 3,
color: "#fff000"
},
{
name: "max",
count: 7,
color: "#f8b70a"
}
];
var totalCount = data.reduce((acc, el) => el.count + acc, 0);
var image_width = 32;
var image_height = 32;
var width = 540,
height = 540,
radius = 200,
outerRadius = radius - 10,
innerRadius = 100;
var cornerRadius = innerRadius;
var markerRadius = (outerRadius - innerRadius) / 2;
var arc = d3
.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius)
.cornerRadius(cornerRadius);
var pie = d3
.pie()
.sort(null)
.value(function(d) {
return d.count;
});
var svg = d3
.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pieData = pie(data);
var g = svg
.selectAll(".arc")
.data(pieData)
.enter()
.append("g");
var path = g
.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return d.data.color;
});
var marker = svg
.append("defs")
.append("marker")
.attr("id", "endmarker")
.attr("overflow", "visible")
.append("circle")
.attr("cy", 0)
.attr("cx", 0)
.attr("r", markerRadius)
.attr("fill", "red");
g.attr("marker-end", "url(#endmarker)");
g
.append("circle")
.attr("cx", function(d) {
let path = d3.select(this.parentNode);
var x = arc.centroid(d)[0];
return x;
})
.attr("cy", function(d) {
var y = arc.centroid(d)[1];
console.log(d3.select(this).attr("cx"));
return y;
})
.attr("fill", d => d.data.color)
.attr("stroke", "black")
.attr("r", (outerRadius - innerRadius) / 2);
codepen here
Thanks to anyone who will help!
Assuming that you want your output like:
I found some code from Mike Bostock's Block here which shows how to add circles to rounded Arc Corners.
I adapted the following code for you which performs quite a bit of complex mathematics.
var cornerRadius = (outerRadius - innerRadius)/2;
svg.append("g")
.style("stroke", "#555")
.style("fill", "none")
.attr("class", "corner")
.selectAll("circle")
.data(d3.merge(pieData.map(function(d) {
return [
{angle: d.startAngle + d.padAngle / 2, radius: outerRadius - cornerRadius, start: +1},
{angle: d.endAngle - d.padAngle / 2, radius: outerRadius - cornerRadius, start: -1},
];
})))
.enter().append("circle")
.attr("cx", function(d) { return d.start * cornerRadius * Math.cos(d.angle) + Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.sin(d.angle); })
.attr("cy", function(d) { return d.start * cornerRadius * Math.sin(d.angle) - Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.cos(d.angle); })
.attr("r", cornerRadius);
Full snippet showing the output:
<div id="chart"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js"></script>
<script>
var data = [
{
name: "punti",
count: 3,
color: "#fff000"
},
{
name: "max",
count: 7,
color: "#f8b70a"
},
];
var totalCount = data.reduce((acc, el) => el.count + acc, 0);
var image_width = 32;
var image_height = 32;
var width = 540,
height = 540,
radius = 200,
outerRadius = radius - 10,
innerRadius = 100;
var cornerRadius = innerRadius;
var markerRadius = (outerRadius - innerRadius) / 2;
var arc = d3
.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius)
.cornerRadius(cornerRadius);
var pie = d3
.pie()
.sort(null)
.value(function(d) {
return d.count;
});
var svg = d3
.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pieData = pie(data);
var g = svg
.selectAll(".arc")
.data(pieData)
.enter()
.append("g");
var path = g
.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return d.data.color;
});
var cornerRadius = (outerRadius - innerRadius)/2;
svg.append("g")
.style("stroke", "#555")
.style("fill", "none")
.attr("class", "corner")
.selectAll("circle")
.data(d3.merge(pieData.map(function(d) {
return [
{angle: d.startAngle + d.padAngle / 2, radius: outerRadius - cornerRadius, start: +1},
{angle: d.endAngle - d.padAngle / 2, radius: outerRadius - cornerRadius, start: -1},
];
})))
.enter().append("circle")
.attr("cx", function(d) { return d.start * cornerRadius * Math.cos(d.angle) + Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.sin(d.angle); })
.attr("cy", function(d) { return d.start * cornerRadius * Math.sin(d.angle) - Math.sqrt(d.radius * d.radius - cornerRadius * cornerRadius) * Math.cos(d.angle); })
.attr("r", cornerRadius);
</script>

Positioning of canvas after the conversion from D3

I just managed to convert the respective heatmap from SVG to canvas. (due to large dataset vs performance issue) However the position of the generated heatmap has goes to a new region. I not sure how am I going to do about this. By changing the transform does not change anything as well.
My code:
var units = [];
for(var unit_i = 0; unit_i<=101;){
if(unit_i==0){
units.push(1);
unit_i = unit_i + 5;
}
else{
units.push(unit_i);
unit_i = unit_i + 4;
}
}
var times = [];
for(var times_i = 0; times_i<=1440;){
if(times_i==0){
times.push(1);
times_i = times_i + 10;
}
else{
times.push(times_i);
times_i = times_i + 9;
}
}
var newSample = [{unit:null, timestamp: null, level: null}];
//by using below method we can observe the delay is not due to the data during insertion
for(var unit=1; unit<=99; unit++){
for(var timestamp = 1; timestamp<=100; timestamp++){
var i = Math.random() * 1400;
newSample.push({unit:unit, timestamp: timestamp, level:i});
}
}
var hours = 0;
var hoursIndicator = 0;
var margin = {
top: 170,
right: 100,
bottom: 70,
left: 100
};
var width = 2500,//optimized width
//gridSize = Math.floor(width / times.length),//optimized gridsize
gridSize = 10;//if 20 each interval will have 5
height = 50 * (units.length); //optimized, the greater the length, the greater the height
console.log("this is gridSize:" + gridSize +", this is height: " + height + ", and this is width: " + width);
//SVG container
var svg = d3.select('.trafficCongestions')
.append("svg")
//.style("position", "absolute")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom)//optimization
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var canvas = d3.select('.trafficCongestions').append("canvas")
.attr("id", "canvas")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom);//optimization
var context = canvas.node().getContext("2d");
context.clearRect(0, 0, width, height);
var detachedContainer = document.createElement("custom");
var dataContainer = d3.select(detachedContainer);
//Reset the overall font size
var newFontSize = width * 62.5 / 900;
//heatmap drawing starts from here
var colorScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d, i) {return d.level; })/2, d3.max(newSample, function(d, i) {return d.level; })])
.range(["#009933", "#FFCC00", "#990000"])//obtain the max data value of count
//y-axis (solely based on data of days)
var dayLabels = svg.selectAll(".dayLabel")
.data(units)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) {
return (i) * (gridSize * 4)/*adjusts the interval distance with (n - 1) concept*/; })
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize + ")");
//x-axis (solely based on data of times)
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d, i) {
var hrs = Math.floor(d/60);
var mins = d%60;
if(hrs<10){
if(mins<10){
return "0"+hrs + ":0" + mins;
}
return "0"+ hrs + ":" + mins;
}
return hrs +":"+ mins;
})
.attr("x", function(d, i) { return i * (gridSize * 9)/*adjusts the interval distance with (n - 1) concept*/; })
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(" + 1 + ", -6)")
var heatMap = dataContainer.selectAll("custom.rect")
.data(newSample)
.enter().append("custom")
.attr("x", function(d) {
return (d.timestamp - 1) * (gridSize);
})
.attr("y", function(d) {
console.log(d.unit);
return (d.unit - 1) * (gridSize);
})
.classed("rect", true)
.attr("class", " rect bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.attr("strokeStyle", "rgba(255,255,255, 0.6)")//to have the middle line or not
.attr("fillStyle", function(d,i){
return colorScale(d.level);
});
drawCanvas();
//Append title to the top
svg.append("text")
.attr("class", "title")
.attr("x", width/2)
.attr("y", -90)
.style("text-anchor", "middle")
.text("Sample Result");
svg.append("text")
.attr("class", "subtitle")
.attr("x", width/2)
.attr("y", -60)
.style("text-anchor", "middle")
.text("HEATMAP");
//Append credit at bottom
svg.append("text")
.attr("class", "credit")
.attr("x", width/2)
.attr("y", gridSize * (units.length+1) + 80)
.style("text-anchor", "middle");
//Extra scale since the color scale is interpolated
var countScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d) {return d.level; })])
.range([0, width])
//Calculate the variables for the temp gradient
var numStops = 10;
countRange = countScale.domain();
countRange[2] = countRange[1] - countRange[0];
countPoint = [];
for(var i = 0; i < numStops; i++) {
countPoint.push(i * countRange[2]/(numStops-1) + countRange[0]);
}//for i
//Create the gradient
svg.append("defs")
.append("linearGradient")
.attr("id", "legendLevel")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%")
.selectAll("stop")
.data(d3.range(numStops))
.enter().append("stop")
.attr("offset", function(d,i) {
return countScale( countPoint[i] )/width;
})
.attr("stop-color", function(d,i) {
return colorScale( countPoint[i] );
});
var legendWidth = Math.min(width, 400);//the width of the legend
console.log(width);
//Color Legend container
var legendsvg = svg.append("g")
.attr("class", "legendWrapper")
.attr("transform", "translate(" + (width/2) + "," + (gridSize * 100 + 40) + ")");
//Draw the Rectangle
legendsvg.append("rect")
.attr("class", "legendRect")
.attr("x", -legendWidth/2)
.attr("y", 0)
.attr("width", legendWidth)
.attr("height", 10)
.style("fill", "url(#legendLevel)");
//Append title
legendsvg.append("text")
.attr("class", "legendTitle")
.attr("x", 0)
.attr("y", -10)
.style("text-anchor", "middle")
.text("Level");
//Set scale for x-axis
var xScale = d3.scaleLinear()
.range([-legendWidth/2, legendWidth/2])
.domain([ 0, d3.max(newSample, function(d) { return d.level; })] );
//Define x-axis
var xAxis = d3.axisBottom()
.ticks(5)
.scale(xScale);
//Set up X axis
legendsvg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (10) + ")")
.call(xAxis);
function drawCanvas(){
var elements = dataContainer.selectAll("custom.rect");
elements.each(function(d){
var node = d3.select(this);
context.beginPath();
context.fillStyle = node.attr("fillStyle");
context.rect(node.attr("x"), node.attr("y"), node.attr("width"), node.attr("height"));
context.fill();
context.closePath();
});
}
html { font-size: 100%; }
.timeLabel, .dayLabel {
font-size: 1rem;
fill: #AAAAAA;
font-weight: 300;
}
.title {
font-size: 1.8rem;
fill: #4F4F4F;
font-weight: 300;
}
.subtitle {
font-size: 1.0rem;
fill: #AAAAAA;
font-weight: 300;
}
.credit {
font-size: 1.2rem;
fill: #AAAAAA;
font-weight: 400;
}
.axis path, .axis tick, .axis line {
fill: none;
stroke: none;
}
.legendTitle {
font-size: 1.3rem;
fill: #4F4F4F;
font-weight: 300;
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="trafficCongestions" class="trafficCongestions"></div>
As you're using svg to just show up the text and legend, I'd say you can absolute position the canvas on top of the SVG - by CSS.
Here are the CSS changes:
div#trafficCongestions {
position: relative;
}
canvas {
position: absolute;
top: 170px;
left: 100px;
}
You can do the above using d3 style as well `cause the margins are defined in the script. I just wanted to show that this is an option to use.
var units = [];
for(var unit_i = 0; unit_i<=101;){
if(unit_i==0){
units.push(1);
unit_i = unit_i + 5;
}
else{
units.push(unit_i);
unit_i = unit_i + 4;
}
}
var times = [];
for(var times_i = 0; times_i<=1440;){
if(times_i==0){
times.push(1);
times_i = times_i + 10;
}
else{
times.push(times_i);
times_i = times_i + 9;
}
}
var newSample = [{unit:null, timestamp: null, level: null}];
//by using below method we can observe the delay is not due to the data during insertion
for(var unit=1; unit<=99; unit++){
for(var timestamp = 1; timestamp<=100; timestamp++){
var i = Math.random() * 1400;
newSample.push({unit:unit, timestamp: timestamp, level:i});
}
}
var hours = 0;
var hoursIndicator = 0;
var margin = {
top: 170,
right: 100,
bottom: 70,
left: 100
};
var width = 2500,//optimized width
//gridSize = Math.floor(width / times.length),//optimized gridsize
gridSize = 10;//if 20 each interval will have 5
height = 50 * (units.length); //optimized, the greater the length, the greater the height
//console.log("this is gridSize:" + gridSize +", this is height: " + height + ", and this is width: " + width);
//SVG container
var svg = d3.select('.trafficCongestions')
.append("svg")
//.style("position", "absolute")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom)//optimization
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var canvas = d3.select('.trafficCongestions').append("canvas")
.attr("id", "canvas")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom);//optimization
var context = canvas.node().getContext("2d");
context.clearRect(0, 0, width, height);
var detachedContainer = document.createElement("custom");
var dataContainer = d3.select(detachedContainer);
//Reset the overall font size
var newFontSize = width * 62.5 / 900;
//heatmap drawing starts from here
var colorScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d, i) {return d.level; })/2, d3.max(newSample, function(d, i) {return d.level; })])
.range(["#009933", "#FFCC00", "#990000"])//obtain the max data value of count
//y-axis (solely based on data of days)
var dayLabels = svg.selectAll(".dayLabel")
.data(units)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) {
return (i) * (gridSize * 4)/*adjusts the interval distance with (n - 1) concept*/; })
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize + ")");
//x-axis (solely based on data of times)
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d, i) {
var hrs = Math.floor(d/60);
var mins = d%60;
if(hrs<10){
if(mins<10){
return "0"+hrs + ":0" + mins;
}
return "0"+ hrs + ":" + mins;
}
return hrs +":"+ mins;
})
.attr("x", function(d, i) { return i * (gridSize * 9)/*adjusts the interval distance with (n - 1) concept*/; })
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(" + 1 + ", -6)")
var heatMap = dataContainer.selectAll("custom.rect")
.data(newSample)
.enter().append("custom")
.attr("x", function(d) {
return (d.timestamp - 1) * (gridSize);
})
.attr("y", function(d) {
//console.log(d.unit);
return (d.unit - 1) * (gridSize);
})
.classed("rect", true)
.attr("class", " rect bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.attr("strokeStyle", "rgba(255,255,255, 0.6)")//to have the middle line or not
.attr("fillStyle", function(d,i){
return colorScale(d.level);
});
drawCanvas();
//Append title to the top
svg.append("text")
.attr("class", "title")
.attr("x", width/2)
.attr("y", -90)
.style("text-anchor", "middle")
.text("Sample Result");
svg.append("text")
.attr("class", "subtitle")
.attr("x", width/2)
.attr("y", -60)
.style("text-anchor", "middle")
.text("HEATMAP");
//Append credit at bottom
svg.append("text")
.attr("class", "credit")
.attr("x", width/2)
.attr("y", gridSize * (units.length+1) + 80)
.style("text-anchor", "middle");
//Extra scale since the color scale is interpolated
var countScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d) {return d.level; })])
.range([0, width])
//Calculate the variables for the temp gradient
var numStops = 10;
countRange = countScale.domain();
countRange[2] = countRange[1] - countRange[0];
countPoint = [];
for(var i = 0; i < numStops; i++) {
countPoint.push(i * countRange[2]/(numStops-1) + countRange[0]);
}//for i
//Create the gradient
svg.append("defs")
.append("linearGradient")
.attr("id", "legendLevel")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%")
.selectAll("stop")
.data(d3.range(numStops))
.enter().append("stop")
.attr("offset", function(d,i) {
return countScale( countPoint[i] )/width;
})
.attr("stop-color", function(d,i) {
return colorScale( countPoint[i] );
});
var legendWidth = Math.min(width, 400);//the width of the legend
//console.log(width);
//Color Legend container
var legendsvg = svg.append("g")
.attr("class", "legendWrapper")
.attr("transform", "translate(" + (width/2) + "," + (gridSize * 100 + 40) + ")");
//Draw the Rectangle
legendsvg.append("rect")
.attr("class", "legendRect")
.attr("x", -legendWidth/2)
.attr("y", 0)
.attr("width", legendWidth)
.attr("height", 10)
.style("fill", "url(#legendLevel)");
//Append title
legendsvg.append("text")
.attr("class", "legendTitle")
.attr("x", 0)
.attr("y", -10)
.style("text-anchor", "middle")
.text("Level");
//Set scale for x-axis
var xScale = d3.scaleLinear()
.range([-legendWidth/2, legendWidth/2])
.domain([ 0, d3.max(newSample, function(d) { return d.level; })] );
//Define x-axis
var xAxis = d3.axisBottom()
.ticks(5)
.scale(xScale);
//Set up X axis
legendsvg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (10) + ")")
.call(xAxis);
function drawCanvas(){
var elements = dataContainer.selectAll("custom.rect");
elements.each(function(d){
var node = d3.select(this);
context.beginPath();
context.fillStyle = node.attr("fillStyle");
context.rect(node.attr("x"), node.attr("y"), node.attr("width"), node.attr("height"));
context.fill();
context.closePath();
});
}
html { font-size: 100%; }
div#trafficCongestions {
position: relative;
}
canvas {
position: absolute;
top: 170px;
left: 100px;
}
.timeLabel, .dayLabel {
font-size: 1rem;
fill: #AAAAAA;
font-weight: 300;
}
.title {
font-size: 1.8rem;
fill: #4F4F4F;
font-weight: 300;
}
.subtitle {
font-size: 1.0rem;
fill: #AAAAAA;
font-weight: 300;
}
.credit {
font-size: 1.2rem;
fill: #AAAAAA;
font-weight: 400;
}
.axis path, .axis tick, .axis line {
fill: none;
stroke: none;
}
.legendTitle {
font-size: 1.3rem;
fill: #4F4F4F;
font-weight: 300;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="trafficCongestions" class="trafficCongestions"></div>
Let me know if this makes sense. If not, let's look for other approaches.
Edit as per comments: (visual studio didn't support the CSS styling added by above approach)
Added the style using d3:
canvas.style('position', 'absolute').style('top', margin.top+'px').style('left', margin.left+'px')
And this worked.

How to display D3.js charts in Apple Watch?

In their fitness monitoring app in Apple Watch they are displaying activity charts in the form of colorful concentric circles.
Is there a way to display my D3.js charts on Apple Watch?
You might find this helpful I guess. Apple Watch Activity
HTML
<head>
<meta charset="utf-8">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
</head>
<script src="https://cdn.rawgit.com/bm-w/d3/master/d3.js"></script>
CSS
html{
height: 100%;
}
body {
min-height: 100%;
background: #000000;
padding:0;
margin:0;
}
.icon{
font-family:fontawesome;
font-weight:bold;
font-size:30px;
}
.goal,.completed{
font-family: 'Roboto','Myriad Set Pro', 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif;
fill:white;
text-anchor:middle;
}
.goal{
font-size: 30px;
}
.completed{
font-size: 95px;
}
JS
//based on https://bl.ocks.org/mbostock/1096355
//apple design:https://images.apple.com/watch/features/images/fitness_large.jpg
"use strict";
(function(){
var gap = 2;
var ranDataset = function () {
var ran = Math.random();
return [
{index: 0, name: 'move', icon: "\uF105", percentage: ran * 60 + 30},
{index: 1, name: 'exercise', icon: "\uF101", percentage: ran * 60 + 30},
{index: 2, name: 'stand', icon: "\uF106", percentage: ran * 60 + 30}
];
};
var ranDataset2 = function () {
var ran = Math.random();
return [
{index: 0, name: 'move', icon: "\uF105", percentage: ran * 60 + 30}
];
};
var colors = ["#e90b3a", "#a0ff03", "#1ad5de"];
var width = 500,
height = 500,
τ = 2 * Math.PI;
function build(dataset,singleArcView){
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(function (d) {
return d.percentage / 100 * τ;
})
.innerRadius(function (d) {
return 140 - d.index * (40 + gap)
})
.outerRadius(function (d) {
return 180 - d.index * (40 + gap)
})
.cornerRadius(20);//modified d3 api only
var background = d3.svg.arc()
.startAngle(0)
.endAngle(τ)
.innerRadius(function (d, i) {
return 140 - d.index * (40 + gap)
})
.outerRadius(function (d, i) {
return 180 - d.index * (40 + gap)
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
//add linear gradient, notice apple uses gradient alone the arc..
//meh, close enough...
var gradient = svg.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "100%")
.attr("x2", "50%")
.attr("y2", "0%")
.attr("spreadMethod", "pad");
gradient.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "#fe08b5")
.attr("stop-opacity", 1);
gradient.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "#ff1410")
.attr("stop-opacity", 1);
//add some shadows
var defs = svg.append("defs");
var filter = defs.append("filter")
.attr("id", "dropshadow")
filter.append("feGaussianBlur")
.attr("in", "SourceAlpha")
.attr("stdDeviation", 4)
.attr("result", "blur");
filter.append("feOffset")
.attr("in", "blur")
.attr("dx", 1)
.attr("dy", 1)
.attr("result", "offsetBlur");
var feMerge = filter.append("feMerge");
feMerge.append("feMergeNode")
.attr("in", "offsetBlur");
feMerge.append("feMergeNode")
.attr("in", "SourceGraphic");
var field = svg.selectAll("g")
.data(dataset)
.enter().append("g");
field.append("path").attr("class", "progress").attr("filter", "url(#dropshadow)");
field.append("path").attr("class", "bg")
.style("fill", function (d) {
return colors[d.index];
})
.style("opacity", 0.2)
.attr("d", background);
field.append("text").attr('class','icon');
if(singleArcView){
field.append("text").attr('class','goal').text("OF 600 CALS").attr("transform","translate(0,50)");
field.append("text").attr('class','completed').attr("transform","translate(0,0)");
}
d3.transition().duration(1750).each(update);
function update() {
field = field
.each(function (d) {
this._value = d.percentage;
})
.data(dataset)
.each(function (d) {
d.previousValue = this._value;
});
field.select("path.progress").transition().duration(1750).delay(function (d, i) {
return i * 200
})
.ease("elastic")
.attrTween("d", arcTween)
.style("fill", function (d) {
if(d.index===0){
return "url(#gradient)"
}
return colors[d.index];
});
field.select("text.icon").text(function (d) {
return d.icon;
}).attr("transform", function (d) {
return "translate(10," + -(150 - d.index * (40 + gap)) + ")"
});
field.select("text.completed").text(function (d) {
return Math.round(d.percentage /100 * 600);
});
setTimeout(update, 2000);
}
function arcTween(d) {
var i = d3.interpolateNumber(d.previousValue, d.percentage);
return function (t) {
d.percentage = i(t);
return arc(d);
};
}
}
build(ranDataset);
build(ranDataset2,true);
})()

Can't get d3 donut chart labels to line up correctly

I am having trouble getting a two-ring donut chart's labels to line up correctly. I think it has something to do with appending the labels to the gs var instead of the path var but if I make that switch, the labels are not visible at all. In the end, I would like each label to be centered on the angle and radius of each slice.
Code is here:
<html>
<body>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
text {
font: 10px sans-serif;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
</style>
<svg class="chart"></svg>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var dataset = {
bip: [.2, .1, .3, .05, .05, .2],
position: [0, 25, 35, 25, 15, 0]
};
var width = 450,
height = 450,
cwidth = 250;
var color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null)
.startAngle(Math.PI * -.25)
.endAngle(Math.PI * .25)
;
var arc = d3.svg.arc();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height + ")")
;
var gs = svg.selectAll("g")
.data(d3.values(dataset))
.enter()
.append("g");
var path = gs.selectAll("path")
.data(function(d) { return pie(d); })
.enter()
.append("path")
.attr("fill", function(d,i,j) {
return "rgb(" + 255*(1-j) + "," + (166 + d3.round(89*d.value,0))*(1-j) + "," + d3.round(255*d.value,0)*(1-j) + ")" ;
})
.attr("d", function(d, i, j) {
return arc.innerRadius(cwidth*j).outerRadius(cwidth-5+50*j) (d);
})
.style('stroke', 'white')
.style('stroke-width', 5)
;
//Labels
gs.append("svg:text")
.attr("transform", function(d, i, j) {
d.startAngle = (Math.PI * -.75 + Math.PI*i/6);
d.endAngle = (Math.PI * .25 + Math.PI*i/6);
d.innerRadius = cwidth*j;
d.outerRadius = cwidth-5+50*j;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.style("fill", "white")
.style("font", "bold 12px Arial")
.text(function(d, i, j) { return d; });
</script>
</body>
</html>
Example is here:
http://jsfiddle.net/sgrossman/kzh7c8mn/
Appreciate the help.
I think that you are right about the problem being attaching to the gs vs. the path var.
I have a fiddle here that is working a bit like you want. It creates text and textPaths per arc element and links them via an ID. Centering is not perfect but could be tuned through trial and error.
//Add an id to the path
.attr("id", function(d, i, j){return 'path_' + i + '_'+ j;})
Add a svg:text and textPath per data point:
gs.selectAll("g").data(function (d, i, j) {
return d;
})
.enter().append('svg:text')
.attr("dx", function(d,i,j){
if(j==1) {return (d * 2) + 8;}
else {return (d * 250) - 15;}
})
.attr("dy", 25)
.append('textPath')
.attr("stroke","white")
//Link via xlink:href
.attr("xlink:href",function(d,i,j){
return '#path_' + i + '_' + j;
})
.text(function (d, i, j) {
return d;
});
JSFiddle

Categories