Made with d3.annotation. An example showing how you can dynamically change the annotation type and add hover events.
forked from susielu's block: d3-annotation: Responsive Types and Hover
| license: mit |
| .DS_Store |
Made with d3.annotation. An example showing how you can dynamically change the annotation type and add hover events.
forked from susielu's block: d3-annotation: Responsive Types and Hover
| // 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); | |
| })); | |
| }); |