Skip to content

Instantly share code, notes, and snippets.

@alokkshukla
Last active August 3, 2017 08:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alokkshukla/5e415d37971fcd9f234b0a964c97fa65 to your computer and use it in GitHub Desktop.
Save alokkshukla/5e415d37971fcd9f234b0a964c97fa65 to your computer and use it in GitHub Desktop.
d3-annotation: Responsive Types and Hover
license: mit
date close
date price
1852 0.5
1856 0.2
1862 0.333333333
1864 0.333333333
1865 0.142857143
1866 0.166666667
1881 0.03030303
1882 0.12
1883 0.214285714
1884 0.157894737
1885 0.19047619
1886 0.105263158
1887 0.344827586
1888 0.152173913
1889 0.333333333
1890 0.298245614
1891 0.294736842
1892 0.222222222
1893 0.189189189
1894 0.202702703
1895 0.234234234
1896 0.336
1897 0.266272189
1898 0.433333333
1899 0.314102564
1900 0.216776909
1901 0.192664605
1905 0.416666667
1906 0.353765324
1907 0.41468254
1908 0.551282051
1909 1
1910 0.107142857
1912 0.700757576
1913 0.501754386
1914 0.563432836
1915 0.542750929
1916 0.514450867
1917 0.501689189
1918 0.661616162
1919 0.709923664
1920 0.642276423
1921 0.219512195
1924 0.5
1925 0.433333333
1926 0.566666667
1927 0.421052632
1928 0.48
1929 0.472222222
1930 0.8
1931 0.62962963
1932 0.351851852
1933 0.618320611
1934 0.41025641
1935 0.272727273
1937 0.186602871
1938 0.201183432
1939 0.165048544
1940 0.24
1941 0.31372549
1942 0.333333333
1943 0.141304348
1944 0.087719298
1945 0.114754098
1946 0.175
1947 0.195652174
1948 0.115942029
1949 0.095238095
1950 0.068965517
1953 0.012987013
1954 0.051948052
1955 0.022988506
1957 0.00862069
1958 0.008130081
1959 0.030769231
1961 0.010309278
1962 0.021052632
1963 0.011235955
1964 0.033898305
1967 0.013333333
1970 0.020408163
1972 0.1
1977 0.043478261
1989 0.045454545
2012 0.142857143
// line chart code: https://bl.ocks.org/d3noob/402dd382a51a4f6eea487f9a35566de0
// time series from: http://bl.ocks.org/mbostock/3883245
// set the dimensions and margins of the graph
const margin = {top: 20, right: 20, bottom: 30, left: 50},
height = 500 - margin.top - margin.bottom;
const maxWidth = 860 - margin.left - margin.right;
let width = 860 - margin.left - margin.right;
const parseTime = d3.timeParse("%d-%b-%y");
const x = d3.scaleTime().range([0, width]);
const y = d3.scaleLinear().range([height, 0]);
const valueline = d3.line()
.x(d => x(d.date))
.y(d => y(d.close));
const svg = d3.select("svg")
.attr("width", 960)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.hidden.tsv", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
x.domain(d3.extent(data, d => d.date));
y.domain([0, d3.max(data, d => d.close)]);
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
//Add annotations
const labels = [
{
data: {date: "9-Apr-12", close: 636.23},
dy: 37,
dx: -142,
subject: { text: 'C', y:"bottom" },
id: "minimize-badge"
},
{
data: {date: "26-Feb-08", close: 119.15},
dy: -137,
dx: 0,
note: { align: "middle"},
subject: { text: 'A', y:"bottom" },
id: "minimize-badge"
},
{
data: {date: "18-Sep-09", close: 185.02},
dy: 37,
dx: 42,
subject: { text: 'B'},
id: "minimize-badge"
}
].map(l => {
l.note = Object.assign({}, l.note, { title: `Close: ${l.data.close}`,
label: `${l.data.date}`})
return l
})
//using a separate type of annotation to control the resize functionality
const resize = [{
subject: {
y1: 0,
y2: height
},
x: width,
dx: 10,
dy: height/2,
disable: ["connector"],
note: {
title: "< >",
label: "drag to resize",
lineType: "none"
}
}]
const timeFormat = d3.timeFormat("%d-%b-%y")
window.makeAnnotations = d3.annotation()
.annotations(labels)
.type(d3.annotationCalloutElbow)
.accessors({ x: d => x(parseTime(d.date)),
y: d => y(d.close)
})
.accessorsInverse({
date: d => timeFormat(x.invert(d.x)),
close: d => y.invert(d.y)
})
.on('subjectover', function(annotation) {
//cannot reference this if you are using es6 function syntax
this.append('text')
.attr('class', 'hover')
.text(annotation.note.title)
.attr('text-anchor', 'middle')
.attr('y', (annotation.subject.y && annotation.subject.y == "bottom") ? 50 : -40)
.attr('x', -15)
this.append('text')
.attr('class', 'hover')
.text(annotation.note.label)
.attr('text-anchor', 'middle')
.attr('y', (annotation.subject.y && annotation.subject.y == "bottom") ? 70 : -60)
.attr('x', -15)
})
.on('subjectout', function(annotation) {
this.selectAll('text.hover')
.remove()
})
//Isn't using data for placement so accessors and accessorsInverse
//are not necessary
window.makeResize = d3.annotation()
.annotations(resize)
.type(d3.annotationXYThreshold)
svg.append("g")
.attr("class", "annotation-test")
.call(makeAnnotations)
svg.append("g")
.attr("class", "annotation-resize")
.call(makeResize)
svg.select(".annotation.xythreshold")
.call(d3.drag()
.on("drag", function(d) {
const newWidth = Math.max(0, Math.min(maxWidth, d.x + d3.event.dx))
d.x = newWidth
const threshold = 400
if (newWidth < threshold && width >= threshold){
makeAnnotations.type(d3.annotationBadge)
svg.select("g.annotation-test").call(makeAnnotations)
} else if (newWidth > threshold && width <= threshold) {
makeAnnotations.type(d3.annotationCalloutElbow)
svg.select("g.annotation-test").call(makeAnnotations)
}
width = newWidth
x.range([0, width])
makeAnnotations.updatedAccessors()
makeResize.updatedAccessors()
svg.select("g.x-axis")
.call(d3.axisBottom(x));
svg.select("path.line")
.attr("d", valueline);
})
)
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link href='https://fonts.googleapis.com/css?family=Lato:300,900' rel='stylesheet' type='text/css'>
<style>
body{
background-color: whitesmoke;
}
:root {
--accent-color: #E8336D;
}
svg {
background-color: white;
font-family: 'Lato';
}
path.line {
fill: none;
stroke: black;
stroke-width: 1px;
}
.annotation path {
stroke: var(--accent-color);
fill: none;
}
.annotation path.connector-arrow{
fill: var(--accent-color);
}
.annotation text {
fill: var(--accent-color);
}
.annotation-note-title {
font-weight: bold;
}
.annotation.xythreshold {
cursor: move;
}
.annotation.xythreshold .annotation-subject{
stroke-width: 3px;
}
.annotation.badge path.subject-pointer, .annotation.badge path.subject {
fill: var(--accent-color);
stroke-width: 3px;
stroke-linecap: round;
}
.annotation.badge path.subject-ring {
fill: white;
stroke-width: 3px;
}
.annotation.badge .badge-text {
fill: white;
font-size: .7em;
}
rect.annotation-note-bg {
fill: rgba(255, 255, 255, 0);
}
text.hover {
font-size: .7em;
}
</style>
</head>
<body>
<svg width=960 height=500></svg>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://rawgit.com/susielu/d3-annotation/master/d3-annotation.min.js"></script>
<script src="index.js"></script>
</body>
</html>
"use strict";
// line chart code: https://bl.ocks.org/d3noob/402dd382a51a4f6eea487f9a35566de0
// time series from: http://bl.ocks.org/mbostock/3883245
// set the dimensions and margins of the graph
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
height = 500 - margin.top - margin.bottom;
var maxWidth = 860 - margin.left - margin.right;
var width = 860 - margin.left - margin.right;
var parseTime = d3.timeParse("%Y");
var _x = d3.scaleTime().range([0, width]);
var _y = d3.scaleLinear().range([height, 0]);
var valueline = d3.line().x(function (d) {
return _x(d.date);
}).y(function (d) {
return _y(d.close);
});
var svg = d3.select("svg").attr("width", 960).attr("height", height + margin.top + margin.bottom).append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.hidden.tsv", function (error, data) {
if (error) throw error;
data.forEach(function (d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
_x.domain(d3.extent(data, function (d) {
return d.date;
}));
_y.domain([0, d3.max(data, function (d) {
return d.close;
})]);
svg.append("path").data([data]).attr("class", "line").attr("d", valueline);
svg.append("g").attr("class", "x-axis").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(_x));
svg.append("g").call(d3.axisLeft(_y));
//Add annotations
var labels = [{
data: { date: "9-Apr-12", close: 636.23 },
dy: 37,
dx: -142,
subject: { text: 'C', y: "bottom" },
id: "minimize-badge"
}, {
data: { date: "26-Feb-08", close: 119.15 },
dy: -137,
dx: 0,
note: { align: "middle" },
subject: { text: 'A', y: "bottom" },
id: "minimize-badge"
}, {
data: { date: "18-Sep-09", close: 185.02 },
dy: 37,
dx: 42,
subject: { text: 'B' },
id: "minimize-badge"
}].map(function (l) {
l.note = Object.assign({}, l.note, { title: "Close: " + l.data.close,
label: "" + l.data.date });
return l;
});
//using a separate type of annotation to control the resize functionality
var resize = [{
subject: {
y1: 0,
y2: height
},
x: width,
dx: 10,
dy: height / 2,
disable: ["connector"],
note: {
title: "< >",
label: "drag to resize",
lineType: "none"
}
}];
var timeFormat = d3.timeFormat("%d-%b-%y");
window.makeAnnotations = d3.annotation().annotations(labels).type(d3.annotationCalloutElbow).accessors({ x: function x(d) {
return _x(parseTime(d.date));
},
y: function y(d) {
return _y(d.close);
}
}).accessorsInverse({
date: function date(d) {
return timeFormat(_x.invert(d.x));
},
close: function close(d) {
return _y.invert(d.y);
}
}).on('subjectover', function (annotation) {
//cannot reference this if you are using es6 function syntax
this.append('text').attr('class', 'hover').text(annotation.note.title).attr('text-anchor', 'middle').attr('y', annotation.subject.y && annotation.subject.y == "bottom" ? 50 : -40).attr('x', -15);
this.append('text').attr('class', 'hover').text(annotation.note.label).attr('text-anchor', 'middle').attr('y', annotation.subject.y && annotation.subject.y == "bottom" ? 70 : -60).attr('x', -15);
}).on('subjectout', function (annotation) {
this.selectAll('text.hover').remove();
});
//Isn't using data for placement so accessors and accessorsInverse
//are not necessary
window.makeResize = d3.annotation().annotations(resize).type(d3.annotationXYThreshold);
svg.append("g").attr("class", "annotation-test").call(makeAnnotations);
svg.append("g").attr("class", "annotation-resize").call(makeResize);
svg.select(".annotation.xythreshold").call(d3.drag().on("drag", function (d) {
var newWidth = Math.max(0, Math.min(maxWidth, d.x + d3.event.dx));
d.x = newWidth;
var threshold = 400;
if (newWidth < threshold && width >= threshold) {
makeAnnotations.type(d3.annotationBadge);
svg.select("g.annotation-test").call(makeAnnotations);
} else if (newWidth > threshold && width <= threshold) {
makeAnnotations.type(d3.annotationCalloutElbow);
svg.select("g.annotation-test").call(makeAnnotations);
}
width = newWidth;
_x.range([0, width]);
makeAnnotations.updatedAccessors();
makeResize.updatedAccessors();
svg.select("g.x-axis").call(d3.axisBottom(_x));
svg.select("path.line").attr("d", valueline);
}));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment