Skip to content

Instantly share code, notes, and snippets.

@markni
Last active August 29, 2015 14:06
Show Gist options
  • Save markni/614a4c403698c18cfbef to your computer and use it in GitHub Desktop.
Save markni/614a4c403698c18cfbef to your computer and use it in GitHub Desktop.
A Pen by xna2.

Apple Watch Radial Chart (sort of)

Tried recreate apple watch's new radial chart with d3. Turns out to be surprisingly hard. D3 natively does not support round corner on arcs, not to mention that conical gradient involves lots of hacks as well (which I did not implement).

Used a modified version of d3 by mbostock, which implemented a round corner for arcs in d3, see more details on this pull request on github (which have never made into production):

d3/d3#1132

A Pen by xna2 on CodePen.

License.

Apple Watch Radial Chart

Tried recreate apple watch's radial chart with d3. Turns out to be surprisingly hard. D3 natively does not support round corner on arcs, not to mention that conical gradient involves lots of hacks as well.

Used a modified version of d3 by mbostock, which implemented a round corner for arcs in d3, see more details on this pull request on github (which have never made into production):

d3/d3#1132

A Pen by xna2 on CodePen.

License.

<head>
<meta charset="utf-8">
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<script src="https://cdn.rawgit.com/bm-w/d3/master/d3.js"></script>
//based on http://bl.ocks.org/mbostock/1096355
//apple design:http://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);
})()
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: '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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment