Skip to content

Instantly share code, notes, and snippets.

@tomshanley
Last active January 16, 2020 13:27
Show Gist options
  • Save tomshanley/77d660ab152d1ed0defda0439c3fb72a to your computer and use it in GitHub Desktop.
Save tomshanley/77d660ab152d1ed0defda0439c3fb72a to your computer and use it in GitHub Desktop.
Line chart labels using textPath
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<link href="https://fonts.googleapis.com/css?family=Oxygen" rel="stylesheet">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body {
font-family: 'Oxygen', sans-serif;
font-size: 12px
margin: 0;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: #222222
}
.line-app {
fill: none;
stroke-width: 4px;
}
</style>
</head>
<body>
<script>
let appData = [
{
"date": "1/03/2015",
"app": "Snapchat",
"percentage": 0.11
},
{
"date": "1/09/2015",
"app": "Snapchat",
"percentage": 0.17
},
{
"date": "1/03/2016",
"app": "Snapchat",
"percentage": 0.24
},
{
"date": "1/09/2016",
"app": "Snapchat",
"percentage": 0.35
},
{
"date": "1/03/2017",
"app": "Snapchat",
"percentage": 0.39
},
{
"date": "1/09/2017",
"app": "Snapchat",
"percentage": 0.47
},
{
"date": "1/03/2015",
"app": "Instagram",
"percentage": 0.29
},
{
"date": "1/09/2015",
"app": "Instagram",
"percentage": 0.29
},
{
"date": "1/03/2016",
"app": "Instagram",
"percentage": 0.23
},
{
"date": "1/09/2016",
"app": "Instagram",
"percentage": 0.24
},
{
"date": "1/03/2017",
"app": "Instagram",
"percentage": 0.23
},
{
"date": "1/09/2017",
"app": "Instagram",
"percentage": 0.24
},
{
"date": "1/03/2015",
"app": "Facebook",
"percentage": 0.12
},
{
"date": "1/09/2015",
"app": "Facebook",
"percentage": 0.13
},
{
"date": "1/03/2016",
"app": "Facebook",
"percentage": 0.15
},
{
"date": "1/09/2016",
"app": "Facebook",
"percentage": 0.13
},
{
"date": "1/03/2017",
"app": "Facebook",
"percentage": 0.11
},
{
"date": "1/09/2017",
"app": "Facebook",
"percentage": 0.09
},
{
"date": "1/03/2015",
"app": "Twitter",
"percentage": 0.21
},
{
"date": "1/09/2015",
"app": "Twitter",
"percentage": 0.18
},
{
"date": "1/03/2016",
"app": "Twitter",
"percentage": 0.16
},
{
"date": "1/09/2016",
"app": "Twitter",
"percentage": 0.13
},
{
"date": "1/03/2017",
"app": "Twitter",
"percentage": 0.11
},
{
"date": "1/09/2017",
"app": "Twitter",
"percentage": 0.07
}
]
const formatDate = d3.timeFormat("%b %y") // Mar 17
const parseDate = d3.timeParse("%d/%m/%Y") // 1/03/2017
function formatPercentage(n) { return Math.round(n * 100) + "%"; }
appData.forEach(function (d) {
d.parsedDate = parseDate(d.date)
})
let nestedData = d3.nest()
.key(function (d) { return d.app; })
.entries(appData)
//////////////////////////////////////////////////////////////////
const width = 700
const height = 500
const margin = { "top": 50, "bottom": 50, "left": 50, "right": 150, }
let xScale = d3.scaleTime()
.domain(d3.extent(appData, function (d) { return d.parsedDate; }))
.range([0, width])
let yScale = d3.scaleLinear()
.domain([0, 0.5])
.range([height, 0])
let xAxis = d3.axisBottom(xScale)
let yAxis = d3.axisLeft(yScale)
.ticks(5)
let colour = d3.scaleOrdinal()
.domain(["Twitter", "Snapchat", "Facebook", "Instagram"])
.range(["#00aced", "#fffc00", "#3b5998", "#cd486b"])
let backgroundColour = "#222222"
let line = d3.line()
.x(function (d) { return xScale(d.parsedDate); })
.y(function (d) { return yScale(d.percentage); })
.curve(d3.curveCardinal.tension(0.5));
let svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
let g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
//////////////////////////////////////////////////////////////////
let yAxixBoxDate = new Date("1/1/2017")
let yAxixBoxWidth = 30
let yAxixBoxHeight = 20
let axes = g.append("g").attr("class", "axes")
let yAxisG = axes.append("g")
.attr("transform", "translate(0,0)")
.call(yAxis)
let xAxisG = axes.append("g")
.attr("transform", "translate(0," + height + ")")
let xTicks = xAxisG.selectAll(".tick")
.data(nestedData[0].values )
.enter()
.append("g")
.attr("class", "tick")
.attr("transform", function(d){ return "translate(" + xScale(d.parsedDate) + ",0)"; })
xTicks.append("text")
.text(function (d) { return formatDate(d.parsedDate); })
.attr("y", 18)
.style("text-anchor", "middle")
xTicks.append("line")
.attr("y1", -height)
.attr("y2", 1)
yAxisG.selectAll(".tick").selectAll("text")
.text(function (d) { return formatPercentage(d); })
.attr("x", xScale(new Date("1/1/2017")))
.style("text-anchor", "middle")
yAxisG.selectAll(".tick").selectAll("line")
.attr("x1", 0)
.attr("x2", width)
yAxisG.selectAll(".tick").append("rect")
.attr("x", xScale(yAxixBoxDate) - (yAxixBoxWidth/2))
.attr("y", -(yAxixBoxHeight/2))
.attr("width", yAxixBoxWidth)
.attr("height", yAxixBoxHeight)
.style("fill", backgroundColour)
yAxisG.selectAll(".tick").selectAll("text")
.text(function (d) { return formatPercentage(d); })
.attr("x", xScale(new Date("1/1/2017")))
.style("text-anchor", "middle")
.raise()
axes.selectAll(".domain").remove()
axes.selectAll(".tick").selectAll("line")
.style("stroke", "grey")
.style("stroke-dasharray", "2,2")
.style("stroke-linecap", "round")
axes.selectAll(".tick").selectAll("text")
.style("fill", "grey")
//////////////////////////////////////////////////////////////////
let lines = g.append("g").attr("class", "lines")
let app = lines.selectAll("g")
.data(nestedData)
.enter()
.append("g")
.attr("id", function (d) { return d.key })
lines.select("#Snapchat").raise()
app.append("path")
.datum(function (d) { return d.values; })
.attr("d", line)
.attr("class", "line-app")
.attr("id", function(d){ return "line-app-" + d[0].app })
.style("stroke", function (d) { return colour(d[0].app); })
.style("stroke-width", "4px")
.style("fill", "none")
app.selectAll("circle")
.data(function (d) { return d.values; })
.enter()
.append("circle")
.attr("cx", function(d){ return xScale(d.parsedDate) })
.attr("cy", function(d){ return yScale(d.percentage) })
.attr("r", 4)
.style("fill", function (d) { return colour(d.app); })
.style("stroke", backgroundColour)
.style("stroke-width", 2)
app.append("text")
.attr("dy", function(d){ return d.key == "Twitter" ? 17 : -7; })
.append("textPath")
.text(function (d) { return d.key })
.attr("xlink:href", function(d){ return "#" + "line-app-" + d.key; })
.attr("startOffset", "99%")
.attr("text-anchor", "end")
.style("fill", function (d) { return colour(d.key); })
//////////////////////////////////////////////////////////////////
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment