Built with blockbuilder.org
Annotations on charts are very important: A quick test of the the excellent d3-annotation module by Susie Lu. Click anywhere on the chart to create a new annotation in that location.
license: mit |
Built with blockbuilder.org
Annotations on charts are very important: A quick test of the the excellent d3-annotation module by Susie Lu. Click anywhere on the chart to create a new annotation in that location.
<html> | |
<head> | |
<title>d3v4 and d3-annotation</title> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-annotation/1.13.0/d3-annotation.min.js"></script> | |
<style> | |
body{font-family:metric;} | |
.background{fill:#fff1e0;} | |
.anno-capture{fill-opacity:0;} | |
text{font-family:metric;} | |
.title{font-size:1.4em;font-weight:500;} | |
.rect{fill:#bb6d82;} | |
:root { | |
--annotation-color: #6b6e68; | |
} | |
.annotation path { | |
stroke: var(--annotation-color); | |
fill: none; | |
} | |
.annotation path.connector-arrow, | |
.annotation path.connector-dot, | |
.title text, .annotation text { | |
fill: var(--annotation-color); | |
} | |
.annotation-note-bg { | |
fill: rgba(0, 0, 0, 0); | |
} | |
.annotation-note-title, text.title { | |
font-weight: bold; | |
} | |
text.title { | |
font-size: 1.2em; | |
} | |
.annoHTML{background-color: #dedede;color:#aaaaaa;width:70%;} | |
/*hide the handles*/ | |
circle.handle { | |
stroke: none; | |
cursor: move; | |
fill-opacity: 0; | |
} | |
circle.handle.highlight { | |
stroke-opacity: 1; | |
} | |
</style> | |
</head> | |
<body> | |
<script> | |
const mytitle = "d3v4 and d3-annotation"; | |
//basic chart layout | |
const h = 400; | |
const w = 600; | |
const margin = {left:20,right:20,top:40,bottom:20} | |
let option = "circle"; | |
let annoTitle = "Look here!" | |
let annoText = "Something important happened here - and these are the details..." | |
var annoNumber = 0; | |
//imaginary numbers | |
const mydata = [ | |
{name:"SW",value:9}, | |
{name:"SE",value:29}, | |
{name:"NE",value:45}, | |
{name:"NW",value:62}, | |
{name:"MID",value:41}, | |
{name:"EM",value:24}, | |
{name:"E",value:41}, | |
{name:"LONDON",value:24} | |
]; | |
//work out data extents | |
// data values | |
var extent = d3.extent(mydata,function(d){ | |
return d.value; | |
}) | |
//data categories | |
var cats = mydata.map(function(d){ | |
return d.name | |
}) | |
//derive size of inner (graph) space | |
const innerW = w-(margin.left+margin.right); | |
const innerH = h-(margin.top+margin.bottom); | |
//define scales | |
let yScale = d3.scaleLinear() | |
.domain([0,extent[1]]) | |
.range([innerH,0]); | |
let xScale = d3.scaleBand() | |
.domain(cats) | |
.range([0,innerW]) | |
.padding(0.2)//adjust spacing between bars | |
//define axes | |
const xAxis = d3.axisBottom(xScale) | |
.tickSizeInner(0) | |
.tickSizeOuter(0) | |
const yAxis = d3.axisLeft(yScale) | |
.ticks(3) | |
.tickSizeOuter(0) | |
.tickSizeInner(-innerW); | |
//document structure | |
let svg = d3.select("body").append("svg") | |
.attr("width",w) | |
.attr("height",h) | |
svg.append("rect") | |
.attr("class","background") | |
.attr("width",innerW) | |
.attr("height",innerH) | |
.attr("x",margin.left) | |
.attr("y",margin.top) | |
svg.append("text") | |
.attr("class","title") | |
.attr("x",margin.left) | |
.attr("y",margin.top-10) | |
.text(mytitle) | |
//render axes | |
svg.append("g") | |
.attr("id","xAxis") | |
.attr("transform","translate("+margin.left+","+(h-margin.bottom)+")") | |
.call(xAxis); | |
svg.append("g") | |
.attr("id","yAxis") | |
.attr("transform","translate("+margin.left+","+margin.top+")") | |
.call(yAxis); | |
//draw bars | |
let bars = svg.append("g") | |
.attr("id","bars") | |
.attr("transform","translate("+margin.left+","+margin.top+")") | |
.selectAll("rect") | |
.data(mydata) | |
.enter() | |
.append("rect"); | |
bars.attr("x",function(d,i){return xScale(d.name)}) | |
.attr("width",xScale.bandwidth) | |
.attr("y",function(d,i){return yScale(d.value)}) | |
.attr("height",function(d,i){ | |
return innerH-(yScale(d.value)) | |
}) | |
.attr("class","rect") | |
//allow for user create annotations | |
//rect - invisible - to capture clicks, record x,y and fire anno creation | |
svg.append("rect") | |
.attr("class","anno-capture") | |
.attr("width",innerW) | |
.attr("height",innerH) | |
.attr("x",margin.left) | |
.attr("y",margin.top) | |
.on("click",function(){ | |
var coordinates=[0,0]; | |
coordinates=d3.mouse(this); | |
createAnno(coordinates) | |
}) | |
d3.select("body").append("h3").text("Annotation type:") | |
const form = d3.select("body").append("form").on("change",function(){ | |
option = d3.select('input[name="anno"]:checked').node().value; | |
}); | |
form.append("input") | |
.attr("type","radio") | |
.attr("name","anno") | |
.attr("value","circle") | |
.attr("checked",true); | |
form.append("span").html("Circle") | |
form.append("input") | |
.attr("type","radio") | |
.attr("name","anno") | |
.attr("value","arrow"); | |
form.append("span").html("Arrow") | |
form.append("input") | |
.attr("type","radio") | |
.attr("name","anno") | |
.attr("value","dot"); | |
form.append("span").html("Dot") | |
function createAnno(coords){ | |
var type; | |
var annoConnector; | |
if (option=="circle"){ | |
type=d3.annotationCalloutCircle; | |
} | |
if (option=="arrow"||option=="dot"){ | |
type=d3.annotationLabel; | |
} | |
if (option=="arrow"){ | |
annoConnector="arrow" | |
} | |
if (option=="dot"){ | |
annoConnector="dot" | |
} | |
const thisAnno = [{ | |
note: { | |
label:annoText, | |
title:annoTitle, | |
wrap:150, | |
align:"middle", | |
}, | |
connector:{ | |
end:annoConnector | |
}, | |
x:coords[0]-margin.left, | |
y:coords[1]-margin.top, | |
dx:30, | |
dy:-30 | |
}]; | |
const makeThis = d3.annotation() | |
.type(type) | |
.annotations(thisAnno) | |
.editMode(true) | |
svg.append("g") | |
.attr("transform","translate("+margin.left+","+margin.top+")") | |
.attr("class","annotation-group") | |
.call(makeThis) | |
}//end function | |
</script> | |
<body> | |
</html> |