├── .babelrc
├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── d3-annotation.independent.js
├── d3-annotation.independent.js.map
├── d3-annotation.independent.min.js
├── d3-annotation.js
├── d3-annotation.js.map
├── d3-annotation.min.js
├── docs
├── About.md
├── browserify-docs.js
├── bundle.js
├── bundle.map
├── content
│ ├── api.md
│ ├── contents.md
│ ├── extend-explanation.md
│ ├── extend.md
│ ├── inpractice.md
│ ├── introduction.md
│ ├── notes.md
│ └── start.md
├── custom-highlightjs-build
│ ├── README.md
│ ├── highlight.js
│ └── index.js
├── docs-compiled.js
├── docs.js
├── img
│ ├── a-badge.png
│ ├── a-callout.png
│ ├── a-circle.png
│ ├── a-curve.png
│ ├── a-elbow.png
│ ├── a-label.png
│ ├── a-rect.png
│ ├── a-threshold.png
│ ├── anatomy.png
│ ├── arrow.png
│ ├── basis.png
│ ├── cardinal.png
│ ├── circle-pack.png
│ ├── classes.png
│ ├── curve.png
│ ├── dot.png
│ ├── dots.png
│ ├── down.png
│ ├── dynamic.png
│ ├── elbow.png
│ ├── encircle.png
│ ├── example1.png
│ ├── example2.png
│ ├── example3.png
│ ├── heart.png
│ ├── horizontal.png
│ ├── json.png
│ ├── left.png
│ ├── leftRight.png
│ ├── line.png
│ ├── linear.png
│ ├── makeover.png
│ ├── map-edit-mode.jpg
│ ├── menu.png
│ ├── middle.png
│ ├── none.png
│ ├── overlapping.png
│ ├── post.png
│ ├── resize.png
│ ├── right.png
│ ├── step.png
│ ├── tooltip.png
│ ├── top.png
│ ├── topBottom.png
│ └── vertical.png
├── index.html
└── vendor
│ ├── d3-dispatch.js
│ ├── d3-drag.js
│ ├── d3-path.js
│ ├── d3-selection.js
│ ├── d3-shape.js
│ ├── d3-transition.js
│ ├── highlight.css
│ ├── hightlight.js
│ ├── jquery2.1.1.min.js
│ ├── materialize.min.css
│ └── materialize.min.js
├── index.js
├── indexRollup.js
├── indexRollup.js.map
├── indexRollupNext.js
├── indexRollupNext.js.map
├── package.json
├── rollup.config.js
├── rollupNext.config.js
├── rollupWeb.config.js
├── rollupWebIndependent.config.js
├── src
├── Adapter-d3.js
├── Annotation.js
├── AnnotationCollection.js
├── Builder.js
├── Connector
│ ├── end-arrow.js
│ ├── end-dot.js
│ ├── type-curve.js
│ ├── type-elbow.js
│ └── type-line.js
├── Handles.js
├── Note
│ ├── alignment.js
│ ├── lineType-horizontal.js
│ ├── lineType-vertical.js
│ └── type-text.js
├── Subject
│ ├── badge.js
│ ├── circle.js
│ ├── rect.js
│ └── threshold.js
└── Types-d3.js
├── test
├── connector.test.js
├── note.test.js
├── subject.badge.test.js
└── utils.js
├── tsconfig.json
├── types
└── d3-svg-annotation.d.ts
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [ "es2015" ],
3 | "plugins": ["transform-object-rest-spread", "transform-object-assign"]
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "rules": {
4 | "array-bracket-spacing": 0,
5 | "curly": 0,
6 | "indent": [2, 2],
7 | "no-var": 2,
8 | "object-curly-spacing": 0,
9 | "quotes": 0,
10 | "eqeqeq": 2,
11 | "eol-last": 0,
12 | "prefer-const": 0,
13 | "no-cond-assign": 0,
14 | "space-before-blocks": 2,
15 | "no-unused-vars": 2,
16 | "no-loop-func": 0,
17 | "no-extra-parens": 2,
18 | "dot-notation": 2,
19 | "comma-dangle": 2,
20 | "no-unused-expressions": [
21 | 2,
22 | { "allowShortCircuit": true, "allowTernary": true }
23 | ],
24 | "no-multiple-empty-lines": 0,
25 | "no-sequences": 0
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | docs/ga.js
4 | docs/favicon.ico
5 | bower_components
6 | npm
7 | .vscode
8 | gifs
9 | *.psd
10 | *.zip
11 | blocks
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 |
180 | APPENDIX: How to apply the Apache License to your work.
181 |
182 | To apply the Apache License to your work, attach the following
183 | boilerplate notice, with the fields enclosed by brackets "[]"
184 | replaced with your own identifying information. (Don't include
185 | the brackets!) The text should be enclosed in the appropriate
186 | comment syntax for the file format. We also recommend that a
187 | file or class name and description of purpose be included on the
188 | same "printed page" as the copyright notice for easier
189 | identification within third-party archives.
190 |
191 | Copyright (c) 2017, Susie Lu
192 |
193 | Licensed under the Apache License, Version 2.0 (the "License");
194 | you may not use this file except in compliance with the License.
195 | You may obtain a copy of the License at
196 |
197 | http://www.apache.org/licenses/LICENSE-2.0
198 |
199 | Unless required by applicable law or agreed to in writing, software
200 | distributed under the License is distributed on an "AS IS" BASIS,
201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
202 | See the License for the specific language governing permissions and
203 | limitations under the License.
204 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # d3-annotation
2 |
3 | Full documentation: [http://d3-annotation.susielu.com](http://d3-annotation.susielu.com)
4 |
5 | ## Setup
6 | ### Include the file directly
7 |
8 | You must include the [d3 library](http://d3js.org/) before including the annotation file. Then you can add the compiled js file to your website
9 |
10 | - [Unminified](https://github.com/susielu/d3-annotation/blob/master/d3-annotation.js)
11 | - [Minified](https://github.com/susielu/d3-annotation/blob/master/d3-annotation.min.js)
12 |
13 | ### Using CDN
14 |
15 | You can add the latest version of [d3-annotation hosted on cdnjs](https://cdnjs.com/libraries/d3-annotation).
16 |
17 | ### Using NPM
18 |
19 | You can add d3-annotation as a node module by running
20 |
21 | ```bash
22 | npm i d3-svg-annotation -S
23 | ```
24 |
25 | ## Feedback
26 | I would love to hear from you about any additional features that would be useful, please say hi on twitter [@DataToViz](https://www.twitter.com/DataToViz).
27 |
28 | ## Prior art
29 |
30 | - [Andrew Mollica](https://bl.ocks.org/armollica/67f3cf7bf08a02d95d48dc9f0c91f26c), [d3-ring-note](https://github.com/armollica/d3-ring-note) D3 plugin for placing circle and text annotation, and [HTML Annotation](http://bl.ocks.org/armollica/78894d0b3cbd46d8d8d19d135c6ca34d)
31 |
32 | - [Scatterplot with d3-annotate](https://bl.ocks.org/cmpolis/f9805a98b8a455aaccb56e5ee59964f8), by Chris Polis, example using [d3-annotate](https://github.com/cmpolis/d3-annotate)
33 |
34 | - [Rickshaw](http://code.shutterstock.com/rickshaw/) has an annotation tool
35 |
36 | - [Benn Stancil](https://modeanalytics.com/benn/reports/21ebfb6b6138) has an annotation example for a line chart
37 |
38 | - [Adam Pearce](http://blockbuilder.org/1wheel/68073eeba4d19c454a8c25fcd6e9e68a) has arc-arrows and [swoopy drag](http://1wheel.github.io/swoopy-drag/)
39 |
40 | - [Micah Stubbs](http://bl.ocks.org/micahstubbs/fa129089b7989975e96b166077f74de4#annotations.json) has a nice VR chart based on swoopy drag
41 |
42 | - [Scott Logic](http://blog.scottlogic.com/2014/08/26/two-line-components-for-d3-charts.html) evokes “line annotation” in a graph (different concept).
43 |
44 | - [Seven Features You’ll Want In Your Next Charting Tool](http://vis4.net/blog/posts/seven-features-youll-wantin-your-next-charting-tool/) shows how the NYT does annotations
45 |
46 | - [John Burn-Murdoch](https://bl.ocks.org/johnburnmurdoch/bcdb4e85c7523a2b0e64961f0d227154) example with adding/removing and repositioning annotations
47 |
--------------------------------------------------------------------------------
/docs/About.md:
--------------------------------------------------------------------------------
1 | # svg-annotation
2 |
3 |
4 | ## Annotation Properties
5 | - location
6 | - associated data
7 | - priority
8 | - draggable
9 |
10 | ## Annotation Types
11 | - callout
12 | - label
13 | - threshold
14 |
15 | ## Annotation Components
16 | - connector
17 | - line
18 | - elbow
19 | - text
20 | - title
21 | - point
22 | - circle
23 | - path
24 | - rect
25 | - badge
26 |
27 | ## Annotation Intents
28 | - anomaly
29 | - insight
30 | - details/context
31 | -
32 |
33 | ## Annotation States
34 | - expanded
35 | - minimized
36 |
37 | ## Interactivity
38 | - hover expand
39 |
40 | ## Adaptors
41 | - d3
42 |
43 | ##
44 |
--------------------------------------------------------------------------------
/docs/browserify-docs.js:
--------------------------------------------------------------------------------
1 | var browserify = require('browserify');
2 | var fs = require('fs');
3 | var path = require('path');
4 | var markedify = require('markedify');
5 | var marked = require('marked');
6 | var babelify = require('babelify');
7 | var highlight = require('highlight.js')
8 |
9 | marked.setOptions({
10 | highlight: function(code, lang) {
11 | return highlight.highlight(lang, code).value;
12 | }
13 | });
14 |
15 | var renderer = new marked.Renderer();
16 | var markedOptions = {
17 | renderer: renderer,
18 | };
19 |
20 | browserify(path.join(__dirname, 'docs.js'))
21 | .transform(markedify, {marked: markedOptions})
22 | .transform(babelify, {presets: ['es2015']})
23 | .bundle()
24 | .on('error', function(err) { console.log('ERROR: ' + err); })
25 | .pipe(fs.createWriteStream(path.join(__dirname, 'docs-compiled.js')));
26 |
--------------------------------------------------------------------------------
/docs/content/api.md:
--------------------------------------------------------------------------------
1 |
# API
2 |
3 | **d3.annotation()**
4 |
5 | annotation.**annotations([ objects ])**
6 |
7 | Pass an array of objects with annotation properties:
8 |
9 | - **id**: This can be anything that will help you filter and parse your annotations
10 |
11 | 
12 |
13 | - **x,y (number:pixels)**: Position of the subject and one end of the connector
14 | - **data (object)**: If you also set accessor functions, you can give data instead of x,y coordinates for placing your annotations
15 | - **dx, dy (number:pixels)**: Position of the note and one end of the connector, as an offset from x,y
16 | - **nx, ny (number:pixels)**: Position of the note and one end of the connector, as the raw x,y position **not** an offset
17 | - **type ([d3annotationType](#types))**: Type for this annotation. Recommended to set the base type at the d3.annotation().type() property and use this to override the base
18 | - **disable ([string])**: takes the values 'connector', 'subject', and 'note' pass them in this array if you want to disable those parts from rendering
19 | - **color([string])**: only in version 2.0, you can pass a color string that will be applied to the annotation. This color can be overridden via css or inline-styles
20 | - **note (object)**: You can specify a title and label property here. All of the annotation types that come with d3-annotation have built in functionality to take the title and the label and add them to the note, however the underlying system is composable in a way that you could customize the note to contain any type of content. You can also use this to overwrite the default note properties (align, orientation, lineType, wrap, padding) in the type. For example if on one of the notes you wanted to align it differently. In v2.1.0 and higher you can pass a regex or string to customize the wrapping { wrapSplitter: /\n/ }
. In v2.3.0 and higher, you can pass a bgPadding
that accepts a number or an object with one or more top
, bottom
, left
, and right
properties, to increase the size of the rectangle behind the text.
21 | - **connector (object)**: Some connectors such as the curve connector require additional parameters to set up the annotation. You can also use this to overwrite the default connector properties (type, end) in the type. For example if you wanted to add an arrow to the end of some of the annotations in the array you could add { end: "arrow" }
to this connector property on the relevant annotations. In v2.1.0 and higher, there is also a { endScale: 2 }
that allows you to scale the size of the dot
or arrow
end types
22 | - **subject (object)**: Some subjects such as the circle require additional parameters to set up the annotation.
23 |
24 | If you don't pass anything to this function, it returns the current array of annotations.
25 |
26 | annotation.**accessors({ x: function, y: function })**
27 |
28 | Functions that would map the .data attribute of your annotation to x and y positions:
29 |
30 | ```js
31 | //Sample .data for an annotation
32 | //{date: "2-Jan-08", close: 194.84}
33 | const parseTime = d3.timeParse("%d-%b-%y")
34 |
35 | d3.annotation().accessors({
36 | x: d => x(parseTime(d.date)),
37 | y: d => y(d.close)
38 | })
39 | ```
40 |
41 | annotation.**accessorsInverse({ <x property mapping>: function, <y property mapping>: function })**
42 |
43 | The inverse of the accessor function. If you are given x, y coordinates, how to get back to the original data properties. Only for the x and y accessors:
44 |
45 | ```js
46 | //Sample .data for an annotation
47 | //{date: "2-Jan-08", close: 194.84}
48 | const timeFormat = d3.timeFormat("%d-%b-%y")
49 |
50 | d3.annotation().accessorsInverse({
51 | date: d => timeFormat(x.invert(d.x)),
52 | close: d => y.invert(d.y)
53 | })
54 | ```
55 |
56 | annotation.**editMode(boolean)**
57 |
58 | If this is true, then the annotation will create handles for parts of the annotation that are draggable. You can style these handles with the circle.handle
selector. If you are hooking this up to a button, you will need to run the update function below, after changing the editMode. Example in [Map with Tooltips and Edit Mode](#map)
59 |
60 | annotation.**update()**
61 |
62 | Redraws all of the annotations. Typically used to reflect updated settings. If you are only updating the position (x, y) or the offset (dx, dy) you do not need to run `call` on makeAnnotations afterwards. Example in [Layout - Encircling Annotation](#encircle).
63 |
64 | annotation.**updateText()**
65 |
66 | If you only want to update the text then use this function. It will re-evaluate with the new text and text wrapping settings. This is separated from the `update()` function for flexibility with performance. If you call the entire set again it will run both functions.
67 |
68 | annotation.**updatedAccessors()**
69 |
70 | Redraws all the annotations with updated accessor scales. Example in [Responsive with Types and Hover](#responsive)
71 |
72 | annotation.**type( d3annotationType )**
73 | You can pass different types into the annotation objects themselves, but you can also set a default type here. If you want to change the type, you will need to re-call the d3.annotation function on your element to recreate the annotations with the new type. Example in [Responsive with Types and Hover](#responsive)
74 |
75 | annotation.**json()**
76 |
77 | You can run this in the developer console and it will print out the current annotation settings and copy them to your clipboard. Please note that in the console each annotation will also include the type that you've associated with it.
78 |
79 | annotation.**collection()**
80 |
81 | Access to the collection of annotations with the instantiated types.
82 |
83 | annotation.**textWrap()**
84 | Change the overall textWrap, otherwise in the annotation object array you can change each individual one with the {note: {wrap: 30}}
property. This function calls `updateText()` internally so you do not need to call both functions when updating `textWrap`.
85 |
86 | annotation.**notePadding()**
87 | Change the overall notePadding, otherwise in the annotation object array you can change each individual one with the {note: {padding: 30}}
property
88 |
89 | annotation.**disable()**
90 | Takes the values 'connector', 'subject', and 'note' pass them in this array if you want to disable those parts from rendering
91 |
92 | annotation.**on()**
93 | Takes the values 'subjectover', 'subjectout', 'subjectclick', 'connectorover', 'connectorout', 'connectorclick', 'noteover', 'noteout', 'noteclick', 'dragend', 'dragstart' as custom dispatch events you can hook into.
94 |
--------------------------------------------------------------------------------
/docs/content/contents.md:
--------------------------------------------------------------------------------
1 | - [Introduction](#introduction)
2 | - [Setup](#setup)
3 | - [Types](#types)
4 | - [In Practice](#in-practice)
5 | - [Examples](#examples)
6 | - [Essays](#essays)
7 | - [API](#api)
8 | - [Extending Types](#extend)
9 | - [Notes](#notes)
10 |
--------------------------------------------------------------------------------
/docs/content/extend-explanation.md:
--------------------------------------------------------------------------------
1 | ## Extending Annotation Types
2 |
3 | ### Javascript Classes
4 |
5 | The underlying structure for the annotations and types are built with es6 JavaScript classes. To make your own custom type you can take any of the base types and extend them.
6 |
7 | ### Static Functions
8 |
9 | **STATIC className**
10 |
11 | A class that return a string for the class name you want to give your custom type.
12 |
13 | **STATIC init**
14 |
15 | An init function that is looped through
16 |
17 | Example, default init function
18 |
19 | Exampe, xyThreshold init function
20 |
21 | ### Drawing Functions
22 | These functions have a context parameter. Context is an object with gives you access to the annotation with all of its properties, and the relevant bounding box.
23 |
24 | **drawNote(context)**
25 |
26 | **drawNoteContent(context)**
27 |
28 | **drawConnector(context)**
29 |
30 | **drawSubject(context)**
31 |
32 | ### Overall Code structure
33 |
34 | **Annotation Class**
35 |
36 | Each annotation is an instantiation of this class.
37 |
38 | Reference the [souce code](https://github.com/susielu/d3-annotation/blob/master/src/Annotation.js) for the full set of properties and functions. Most relevant properties:
39 |
40 | - dx
41 | - dy
42 | - x
43 | - y
44 | - data
45 | - offset: returns the dx, and dy, values as an object {x, y}
46 | - position: returns the x, and y, values as an object {x, y}
47 |
48 |
49 | **Annotation Collection Class**
50 |
51 | When you run d3.annotation() it creates all of the annotations you pass it as Annotation Class instances and places them into an array as part of an Annotation Collection.
52 |
53 | **Types Class**
54 |
55 | Each of the annotation types is created
56 |
--------------------------------------------------------------------------------
/docs/content/extend.md:
--------------------------------------------------------------------------------
1 | # Extending Annotation Types
2 |
3 |
4 | The underlying code for d3-annotation has a base annotation type that all of the annotation types extend. The settings and components that make up the different types are customizable.
5 |
6 | The goal was to make a system that was easy to add new types and implement layout algorithms with. A longer post with details about how you can make your own type will be coming out soon.
7 |
8 | If you're interested in looking at the architecture before the post you can find the [source code here](https://github.com/susielu/d3-annotation/tree/master/src).
--------------------------------------------------------------------------------
/docs/content/inpractice.md:
--------------------------------------------------------------------------------
1 | # In Practice
2 |
3 | All annotations are made of just three parts, a **note**, a **connector**, and a **subject**.
4 |
5 |
6 |
7 | They are the foundational blocks of this library.
8 |
9 | ### Customize the Subject by picking a base annotation
10 |
11 | Settings for subject types are in the annotation object's .subject
:
12 |
13 | ```js
14 | const annotations = [
15 | {
16 | note: { label: "Hi" },
17 | x: 100,
18 | y: 100,
19 | dy: 137,
20 | dx: 162,
21 | subject: { radius: 50, radiusPadding: 10 },
22 | },
23 | ];
24 |
25 | d3.annotation().annotations(annotations);
26 | ```
27 |
28 | **d3.annotationCalloutCircle**
29 |
30 | - radius or outerRadius and innerRadius: Number, pixels
31 | - radiusPadding: Number, pixels
32 |
33 | **d3.annotationCalloutRect**
34 |
35 | - width: Number, pixels
36 | - height: Number, pixels
37 |
38 | **d3.annotationXYThreshold**
39 |
40 | - x1, x2 or y1, y2: Number, pixels
41 |
42 | **d3.annotationBadge**: this is the only base annotation that doesn't have a connector or note
43 |
44 | - text: String
45 | - radius: Number, pixels
46 | - x: "left" or "right"
47 | - y: "top" or "bottom"
48 |
49 | **No subject**
50 |
51 | - d3.annotationLabel
52 | - d3.annotationCallout
53 | - d3.annotationCalloutElbow
54 | - d3.annotationCalloutCurve
55 |
56 | ### Customize the Connector and Note
57 |
58 | The Options panel in the [Annotation Types UI](#types) exposes all of the options for connectors and notes. So the "Line Type" in the UI maps to { connector: { lineType : "horizontal" } }
59 |
60 | There are two ways to customize the connectors and notes. You can either change these properties per annotation:
61 |
62 | ```js
63 | const annotations = [
64 | {
65 | note: { label: "Hi" },
66 | x: 100,
67 | y: 100,
68 | dy: 137,
69 | dx: 162,
70 | type: d3.annotationCalloutElbow,
71 | connector: { end: "arrow" },
72 | },
73 | ];
74 |
75 | d3.annotation().annotations(annotations);
76 | ```
77 |
78 | Or if you want all of the annotations to have these settings create a custom type with
79 | **d3.annotationCustomType(annotationType, typeSettings)**:
80 |
81 | ```js
82 | const calloutWithArrow = d3.annotationCustomType(d3.annotationCalloutElbow, {
83 | connector: { end: "arrow" },
84 | });
85 |
86 | d3.annotation()
87 | .type(calloutWithArrow)
88 | .annotations([
89 | {
90 | text: "Plant paradise",
91 | data: { date: "18-Sep-09", close: 185.02 },
92 | dy: 37,
93 | dx: 42,
94 | },
95 | ])
96 | .editMode(true);
97 | ```
98 |
99 | Both examples above produce the same results.
100 |
101 | # Selecting Elements
102 |
103 | - All of the visible shapes (aside from the edit handles) in the default annotations are **paths**
104 | - There is an invisible rect (rect.annotation-note-bg
) behind the text in the notes as a helper for more click area etc.
105 | - Hierarchy of classes:
106 | 
107 | - Within the g.annotation-note-content there could be three additional elements: text.annotation-note-label
, text.annotation-note-title
, rect.annotation-note-bg
108 |
109 | # Basic Styles
110 |
111 | Now the library comes with default styles, read more about it in the [2.0 release](http://www.susielu.com/data-viz/d3-annotation-2) post.
112 |
113 | Before v2, there were style sheets you needed to use:
114 |
115 | Available on [github](https://github.com/susielu/d3-annotation/blob/e7ba1e83f279a63e056964b080019d647f57e34c/d3-annotation.css).
116 |
117 | # Tips
118 |
119 | - In addition to the alignment settings for the note, you can also use the css `text-anchor` attribute to align the text within the note
120 | - When you update the d3.annotation().type() you will need to use the call functionality again to set up the annotations with the new type. See the [Responsive with Types and Hover](#responsive) example
121 | - You do not need to call d3.annotation().update() if you are only changing the position(x,y) or the offset(dx, dy). See the [Overlapping](#overlapping) example
122 | - If you are importing custom fonts, you may notice the annotations don't load perfectly with text wrapping and alignment. To fix that you can use, `document.fonts.ready` to make sure the fonts are loaded first to reflect the custom font's spacing for all of the calculations. Here's an example:
123 |
124 | ```js
125 | document.fonts.ready.then(function () {
126 | d3.select("svg")
127 | .append("g")
128 | .attr("class", "annotation-group")
129 | .style("font-size", fontSize(ratio))
130 | .call(makeAnnotations);
131 | });
132 | ```
133 |
--------------------------------------------------------------------------------
/docs/content/introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Annotations **establish context, and direct our users to insights and anomalies**. So why are annotations so few and far between in visualizations on the web? Because **implementing them is difficult.**
4 |
5 | **But it shouldn't be.**
6 |
7 | Use d3-annotation with built-in annotation types, or extend it to make custom annotations. It is made for [d3-v4](https://github.com/d3/d3/blob/master/CHANGES.md) in SVG.
8 |
9 |
10 | Contact me through the [github repo](https://www.github.com/susielu/d3-annotation) or [twitter](https://www.twitter.com/DataToViz).
--------------------------------------------------------------------------------
/docs/content/notes.md:
--------------------------------------------------------------------------------
1 | # Notes
2 |
3 |
4 | Extremely grateful to my team at [Netflix](https://twitter.com/netflixdata) for mentoring me, giving me feedback, and helping out on this project. Cheers [Elijah](https://twitter.com/Elijah_Meeks), [James](https://twitter.com/james_womack), [Jason](https://twitter.com/onemerovingian), and [Nathan](https://twitter.com/nathantowery).
5 |
6 | Invaluable help from [Fil](https://twitter.com/recifs), thanks for jumping in early to help with testing and discussion.
7 |
8 | [Sam](https://twitter.com/samccone) keeps me sane and gets all the perf wins \o/
9 |
10 | This is a data visualization project that wouldn't be possible without [Mike Bostock](https://twitter.com/mbostock)'s work on [d3](https://d3js.org/), and all of the inspiring [prior art](https://github.com/susielu/d3-annotation#prior-art) in annotations, particularly [Adam Pearce](https://twitter.com/adamrpearce)'s [Swoopy Drag](https://1wheel.github.io/swoopy-drag/), and [Andrew Mollica](https://twitter.com/armollica)'s [Ring Note](https://github.com/armollica/d3-ring-note).
11 |
12 | Thumbs up to [Nunito](https://fonts.google.com/specimen/Nunito) and [Bungee](https://fonts.google.com/specimen/Bungee) via Google Fonts and [Materialize](http://materializecss.com/) for making the docs site building a breeze.
13 |
14 |
--------------------------------------------------------------------------------
/docs/content/start.md:
--------------------------------------------------------------------------------
1 | # Setup
2 |
3 | ### Include the file directly
4 |
5 | You must include the [d3 library](http://d3js.org/) before including the annotation file. Then you can add the compiled js file to your website
6 |
7 | - [Unminified](https://github.com/susielu/d3-annotation/blob/master/d3-annotation.js)
8 | - [Minified](https://github.com/susielu/d3-annotation/blob/master/d3-annotation.min.js)
9 |
10 | ### Using CDN
11 |
12 | You can add the latest version of [d3-annotation hosted on cdnjs](https://cdnjs.com/libraries/d3-annotation).
13 |
14 | ### Using NPM
15 |
16 | You can add d3-annotation as a node module by running
17 |
18 | ```bash
19 | npm i d3-svg-annotation -S
20 | ```
21 |
--------------------------------------------------------------------------------
/docs/custom-highlightjs-build/README.md:
--------------------------------------------------------------------------------
1 | build by cloning highlight.js
2 |
3 | From within highlight.js repo folder:
4 |
5 | ```
6 | node tools/build.js -t node -n js
7 | ```
8 |
9 | Then cping the files from build/lib into this folder `\o/`
10 |
--------------------------------------------------------------------------------
/docs/custom-highlightjs-build/index.js:
--------------------------------------------------------------------------------
1 | var hljs = require('./highlight');
2 |
3 |
4 | module.exports = hljs;
--------------------------------------------------------------------------------
/docs/docs.js:
--------------------------------------------------------------------------------
1 | const contents = require("./content/contents.md");
2 | const introduction = require("./content/introduction.md");
3 | const start = require("./content/start.md");
4 | const inpractice = require("./content/inpractice.md");
5 | const api = require("./content/api.md");
6 | const extend = require("./content/extend.md");
7 | const notes = require("./content/notes.md");
8 | const highlight = require("./custom-highlightjs-build");
9 | const highlightjs = require("highlight.js/lib/languages/javascript");
10 | highlight.registerLanguage("js", highlightjs);
11 |
12 | document.getElementById("toc1").innerHTML = contents;
13 | document.getElementById("slide-out").innerHTML =
14 | '
' +
15 | contents;
16 | document.getElementById("introduction").innerHTML = introduction;
17 | document.getElementById("setup").innerHTML = start;
18 | document.getElementById("in-practice").innerHTML = inpractice;
19 | document.getElementById("api").innerHTML = api;
20 | document.getElementById("extend").innerHTML = extend;
21 | document.getElementById("notes").innerHTML = notes;
22 |
23 | $(document).ready(function () {
24 | $(".scrollspy").scrollSpy({ scrollOffset: 0 });
25 | $(".button-collapse").sideNav();
26 | $(".toc").pushpin({
27 | top: 140,
28 | offset: 0,
29 | });
30 |
31 | $(".collapsible").collapsible();
32 |
33 | const defaultSettings = {
34 | className: "custom",
35 | subject: {},
36 | connector: {},
37 | note: {},
38 | };
39 |
40 | let typeSettings = JSON.parse(JSON.stringify(defaultSettings));
41 |
42 | let typeKey = "annotationLabel";
43 | let curve = "curveCatmullRom";
44 | let points = 2;
45 |
46 | const types = {
47 | annotationLabel: {
48 | typeSettings: {
49 | note: {
50 | align: "middle",
51 | orientation: "topBottom",
52 | bgPadding: 20,
53 | padding: 15,
54 | },
55 | connector: { type: "line" },
56 | className: "show-bg",
57 | },
58 | summary: "A centered label annotation",
59 | },
60 | annotationCallout: {
61 | typeSettings: {
62 | note: {
63 | align: "dynamic",
64 | lineType: "horizontal",
65 | bgPadding: { top: 15, left: 10, right: 10, bottom: 10 },
66 | padding: 15,
67 | },
68 | connector: { type: "line" },
69 | className: "show-bg",
70 | },
71 | summary: "Adds a line along the note",
72 | },
73 | annotationCalloutElbow: {
74 | typeSettings: {
75 | note: { align: "dynamic", lineType: "horizontal" },
76 | connector: { type: "elbow" },
77 | },
78 | summary: "Keeps connector at 45 and 90 degree angles",
79 | },
80 | annotationCalloutCircle: {
81 | typeSettings: {
82 | note: { align: "dynamic", lineType: "horizontal" },
83 | connector: { type: "elbow" },
84 | },
85 | summary: "Subject options: radius, innerRadius, outerRadius, ",
86 | summaryCont: "radiusPadding",
87 | subject: {
88 | radius: 50,
89 | radiusPadding: 5,
90 | },
91 | },
92 | annotationCalloutRect: {
93 | typeSettings: {
94 | note: { align: "dynamic", lineType: "horizontal" },
95 | connector: { type: "elbow" },
96 | },
97 | summary: "Subject options: width, height",
98 | subject: {
99 | width: -50,
100 | height: 100,
101 | },
102 | },
103 | annotationCalloutCurve: {
104 | typeSettings: {
105 | note: { align: "dynamic", lineType: "horizontal" },
106 | connector: { type: "curve" },
107 | },
108 | summary: "Connector options: curve, ",
109 | summaryCont: "points(array of [x,y]s or number)",
110 | },
111 | annotationXYThreshold: {
112 | typeSettings: {
113 | note: { align: "dynamic", lineType: "horizontal" },
114 | connector: { type: "elbow" },
115 | },
116 | summary: "Subject options: x1, x2 or y1, y2",
117 | subject: {
118 | x1: 0,
119 | x2: 1000,
120 | },
121 | },
122 | annotationBadge: {
123 | typeSettings: {
124 | note: { align: "dynamic", lineType: "horizontal" },
125 | connector: { type: "elbow" },
126 | },
127 | summary:
128 | "Subject options: radius, text, x:left or right, y:top or bottom",
129 | subject: {
130 | radius: 14,
131 | text: "A",
132 | },
133 | },
134 | };
135 | let currentType = d3.annotationCustomType(
136 | d3.annotationLabel,
137 | types.annotationLabel.typeSettings
138 | );
139 | let editMode = true;
140 | let textWrap = 120;
141 | let padding = types[typeKey].typeSettings.note.padding || 5;
142 |
143 | const annotation = {
144 | note: {
145 | label: "Longer text to show text wrapping",
146 | title: "Annotations :)",
147 | },
148 | className: types[typeKey].className,
149 | x: 150,
150 | y: 170,
151 | dy: 117,
152 | dx: 162,
153 | };
154 |
155 | const changeOption = function () {
156 | let type = d3.event.target.attributes["data-section"].value;
157 | const value = d3.event.target.attributes["data-setting"].value;
158 | d3.selectAll(`[data-section="${type}"]`).classed("active", false);
159 |
160 | d3.selectAll(`[data-section="${type}"][data-setting="${value}"]`).classed(
161 | "active",
162 | true
163 | );
164 |
165 | if (type === "note:lineType") {
166 | if (value === "none") {
167 | d3.selectAll(".icons .orientation").classed("hidden", false);
168 | } else {
169 | d3.selectAll(".icons .orientation").classed("hidden", true);
170 | }
171 | }
172 |
173 | if (
174 | (type === "note:lineType" && value === "vertical") ||
175 | (type === "note:orientation" && value === "leftRight")
176 | ) {
177 | d3.selectAll("[data-section='note:align'].horizontal").classed(
178 | "hidden",
179 | true
180 | );
181 | d3.selectAll("[data-section='note:align'].vertical").classed(
182 | "hidden",
183 | false
184 | );
185 | } else if (
186 | (type === "note:lineType" && value === "horizontal") ||
187 | (type === "note:orientation" && value === "topBottom")
188 | ) {
189 | d3.selectAll("[data-section='note:align'].vertical").classed(
190 | "hidden",
191 | true
192 | );
193 | d3.selectAll("[data-section='note:align'].horizontal").classed(
194 | "hidden",
195 | false
196 | );
197 | }
198 |
199 | type = type.split(":");
200 | if (value === "none") {
201 | delete typeSettings[type[0]][type[1]];
202 | if (type[0] == "connector" && type[1] == "type") {
203 | makeAnnotations.disable(["connector"]);
204 | makeAnnotations.update();
205 | }
206 | } else {
207 | if (type[0] == "connector" && type[1] == "type") {
208 | const connectorTypes = [
209 | "annotationCallout",
210 | "annotationCalloutElbow",
211 | "annotationCalloutCurve",
212 | ];
213 | if (connectorTypes.indexOf(typeKey) !== -1) {
214 | if (value == "line") {
215 | typeKey = "annotationCallout";
216 | } else if (value == "elbow") {
217 | typeKey = "annotationCalloutElbow";
218 | } else if (value == "curve") {
219 | typeKey = "annotationCalloutCurve";
220 | }
221 |
222 | d3.selectAll(`.icons .presets img`).classed("active", false);
223 |
224 | d3.selectAll(`[data-type="${typeKey}"]`).classed("active", true);
225 | } else {
226 | typeSettings[type[0]][type[1]] = value;
227 | }
228 |
229 | if (value == "curve") {
230 | d3.select("#curveButtons").classed("hidden", false);
231 | } else if (typeKey !== "annotationCalloutCurve") {
232 | d3.select("#curveButtons").classed("hidden", true);
233 | }
234 |
235 | makeAnnotations.disable([]);
236 | makeAnnotations.update();
237 | } else {
238 | typeSettings[type[0]][type[1]] = value;
239 | }
240 | }
241 |
242 | currentType = d3.annotationCustomType(d3[typeKey], typeSettings);
243 |
244 | updateAnnotations();
245 | sandboxCode();
246 | };
247 |
248 | d3.selectAll(".icons .options img").on("click", changeOption);
249 |
250 | d3.selectAll(".icons .presets .types img").on("click", function () {
251 | typeKey = d3.event.target.attributes["data-type"].value;
252 | currentType = d3.annotationCustomType(d3[typeKey], {
253 | className: types[typeKey].typeSettings.className,
254 | note: { bgPadding: types[typeKey].typeSettings.note.bgPadding },
255 | });
256 |
257 | d3.selectAll(`.icons .presets img`).classed("active", false);
258 |
259 | d3.selectAll(`[data-type="${typeKey}"]`).classed("active", true);
260 |
261 | typeSettings = JSON.parse(JSON.stringify(defaultSettings));
262 |
263 | if (typeKey == "annotationBadge") {
264 | d3.select("li.options").classed("hidden", true);
265 | } else {
266 | d3.select("li.options").classed("hidden", false);
267 | }
268 |
269 | //set options
270 | const options = types[typeKey].typeSettings;
271 |
272 | d3.selectAll(".icons .options img").classed("active", false);
273 |
274 | d3.select(
275 | `.icons img[data-section="note:align"][data-setting="${options.note.align}"]`
276 | ).classed("active", true);
277 |
278 | if (options.note.lineType) {
279 | d3.select(
280 | `.icons img[data-section="note:lineType"][data-setting=${options.note.lineType}]`
281 | ).classed("active", true);
282 | d3.selectAll(".icons .orientation").classed("hidden", true);
283 | } else {
284 | d3.select(
285 | `.icons img[data-section="note:lineType"][data-setting="none"]`
286 | ).classed("active", true);
287 | d3.selectAll(".icons .orientation").classed("hidden", false);
288 | d3.select(".icons .orientation img").classed("active", true);
289 | }
290 |
291 | d3.select('.icons img[data-section="connector:end"]').classed(
292 | "active",
293 | true
294 | );
295 |
296 | d3.select(
297 | `.icons img[data-section="connector:type"][data-setting=${options.connector.type}]`
298 | ).classed("active", true);
299 |
300 | if (typeKey == "annotationCalloutCurve") {
301 | d3.select("#curveButtons").classed("hidden", false);
302 | } else {
303 | d3.select("#curveButtons").classed("hidden", true);
304 | }
305 |
306 | d3.select("#sandbox-title").text(`Use d3.${typeKey}:`);
307 |
308 | updateAnnotations();
309 | sandboxCode();
310 | });
311 |
312 | d3.select("#editmode").on("change", function () {
313 | editMode = d3.event.target.checked;
314 |
315 | makeAnnotations.editMode(editMode);
316 | makeAnnotations.update();
317 |
318 | sandboxCode();
319 | });
320 |
321 | d3.select("#textWrap").on("change", function () {
322 | textWrap = parseInt(d3.event.target.value);
323 | makeAnnotations.textWrap(textWrap).update();
324 | sandboxCode();
325 | });
326 |
327 | d3.select("#padding").on("change", function () {
328 | padding = parseInt(d3.event.target.value);
329 | makeAnnotations.notePadding(padding).update();
330 | sandboxCode();
331 | });
332 |
333 | const changeCurve = function () {
334 | curve = d3.event.target.attributes["data-curve"].value;
335 | updateAnnotations({ connector: { curve: d3[curve], points } });
336 | sandboxCode();
337 | };
338 |
339 | d3.selectAll("#curveButtons ul.curves li img")
340 | .on("click", changeCurve)
341 | .on("pointerdown", changeCurve);
342 |
343 | const changePoints = function () {
344 | points = parseInt(d3.event.target.attributes["data-points"].value);
345 | updateAnnotations({ connector: { curve: d3[curve], points } });
346 | sandboxCode();
347 | };
348 |
349 | d3.selectAll("#curveButtons ul.points li a")
350 | .on("click", changePoints)
351 | .on("pointerdown", changePoints);
352 |
353 | window.makeAnnotations = d3
354 | .annotation()
355 | .editMode(editMode)
356 | .type(currentType)
357 | .annotations([annotation]);
358 |
359 | d3.select(".sandbox")
360 | .append("g")
361 | .attr("class", "sandbox-annotations")
362 | .call(makeAnnotations);
363 |
364 | const updateAnnotations = (newSettings) => {
365 | d3.select(".sandbox g.sandbox-annotations").remove();
366 |
367 | const subject = types[typeKey].subject || {};
368 | makeAnnotations.type(currentType, {
369 | subject,
370 | connector: newSettings && newSettings.connector,
371 | });
372 |
373 | d3.select(".sandbox")
374 | .append("g")
375 | .attr("class", "sandbox-annotations")
376 | .call(makeAnnotations);
377 |
378 | d3.select(".sandbox .type").text(`d3.${typeKey}`);
379 |
380 | d3.select(".sandbox .summary").text(types[typeKey].summary);
381 |
382 | d3.select(".sandbox .summaryCont").text(types[typeKey].summaryCont || "");
383 | };
384 |
385 | //change the text to have the right position for the annotation
386 |
387 | const sandboxCode = () => {
388 | const editModeText = editMode ? ` .editMode(true)\n` : "";
389 |
390 | let typeText = "\nconst type = ";
391 |
392 | if (JSON.stringify(typeSettings) == JSON.stringify(defaultSettings)) {
393 | typeText += `d3.${typeKey}\n`;
394 | } else {
395 | let json = JSON.parse(JSON.stringify(typeSettings));
396 |
397 | //if (Object.keys(json.subject).length === 0){
398 | delete json.subject;
399 | //}
400 |
401 | if (Object.keys(json.connector).length === 0) {
402 | delete json.connector;
403 | }
404 | if (Object.keys(json.note).length === 0) {
405 | delete json.note;
406 | }
407 | typeText +=
408 | `d3.annotationCustomType(\n` +
409 | ` d3.${typeKey}, \n` +
410 | ` ${JSON.stringify(json).replace(/,/g, ",\n ")}` +
411 | `)\n`;
412 | }
413 |
414 | let disableText = "";
415 |
416 | if (makeAnnotations.disable().length !== 0) {
417 | disableText =
418 | " //could also be set in the a disable property\n //of the annotation object\n" +
419 | ` .disable(${JSON.stringify(makeAnnotations.disable())})\n`;
420 | }
421 |
422 | let textWrapText = "";
423 |
424 | if (textWrap !== 120) {
425 | textWrapText =
426 | " //also can set and override in the note.wrap property\n //of the annotation object\n" +
427 | ` .textWrap(${textWrap})\n`;
428 | }
429 |
430 | let paddingText = "";
431 | if (padding !== 5) {
432 | paddingText =
433 | " //also can set and override in the note.padding property\n //of the annotation object\n" +
434 | ` .notePadding(${padding})\n`;
435 | }
436 |
437 | let curveText = "";
438 | if (
439 | (typeKey == "annotationCalloutCurve" ||
440 | typeSettings.connector.type == "curve") &&
441 | (curve !== "curveCatmullRom" || points !== 2)
442 | ) {
443 | curveText =
444 | " connector: {\n" +
445 | (curve !== "curveCatmullRom" ? ` curve: d3.${curve}` : "") +
446 | (points !== 2 && curve !== "curveCatmullRom" ? ",\n" : "") +
447 | (points !== 2 ? ` points: ${points}` : "") +
448 | "\n" +
449 | " }";
450 | }
451 |
452 | let subjectText = "";
453 | if (typeKey === "annotationCalloutCircle") {
454 | subjectText =
455 | ` subject: {\n` +
456 | " radius: 50,\n" +
457 | " radiusPadding: 5\n" +
458 | " }\n";
459 | } else if (typeKey == "annotationCalloutRect") {
460 | subjectText =
461 | ` subject: {\n` + " width: -50,\n" + " height: 100\n" + " }\n";
462 | } else if (typeKey == "annotationXYThreshold") {
463 | subjectText =
464 | ` subject: {\n` + " x1: 0,\n" + " x2: 500\n" + " }\n";
465 | } else if (typeKey == "annotationBadge") {
466 | subjectText =
467 | ` subject: {\n` + ' text: "A",\n' + " radius: 14\n" + " }\n";
468 | }
469 |
470 | d3.select("#sandbox-code code").text(
471 | typeText +
472 | "\n" +
473 | "const annotations = [{\n" +
474 | " note: {\n" +
475 | ' label: "Longer text to show text wrapping",\n' +
476 | ((types[typeKey] &&
477 | types[typeKey].typeSettings.note &&
478 | types[typeKey].typeSettings.note.bgPadding &&
479 | ` bgPadding: ${JSON.stringify(
480 | types[typeKey].typeSettings.note.bgPadding
481 | )},\n`) ||
482 | "") +
483 | ' title: "Annotations :)"\n' +
484 | " },\n" +
485 | " //can use x, y directly instead of data\n" +
486 | ' data: { date: "18-Sep-09", close: 185.02 },\n' +
487 | ((types[typeKey] &&
488 | types[typeKey].typeSettings.className &&
489 | ` className: ${JSON.stringify(
490 | types[typeKey].typeSettings.className
491 | )},\n`) ||
492 | "") +
493 | " dy: 137,\n" +
494 | ` dx: 162${curveText !== "" || subjectText !== "" ? "," : ""}\n` +
495 | curveText +
496 | (subjectText !== "" && curveText !== "" ? ",\n" : "") +
497 | subjectText +
498 | "}]\n" +
499 | "\n" +
500 | 'const parseTime = d3.timeParse("%d-%b-%y")\n' +
501 | 'const timeFormat = d3.timeFormat("%d-%b-%y")\n' +
502 | "\n" +
503 | "//Skipping setting domains for sake of example\n" +
504 | "const x = d3.scaleTime().range([0, 800])\n" +
505 | "const y = d3.scaleLinear().range([300, 0])\n" +
506 | "\n" +
507 | "const makeAnnotations = d3.annotation()\n" +
508 | editModeText +
509 | disableText +
510 | textWrapText +
511 | paddingText +
512 | ` .type(type)\n` +
513 | " //accessors & accessorsInverse not needed\n" +
514 | " //if using x, y in annotations JSON\n" +
515 | " .accessors({\n" +
516 | " x: d => x(parseTime(d.date)),\n" +
517 | " y: d => y(d.close)\n" +
518 | " })\n" +
519 | " .accessorsInverse({\n" +
520 | " date: d => timeFormat(x.invert(d.x)),\n" +
521 | " close: d => y.invert(d.y)\n" +
522 | " })\n" +
523 | ` .annotations(annotations)\n` +
524 | "\n" +
525 | 'd3.select("svg")\n' +
526 | ' .append("g")\n' +
527 | ' .attr("class", "annotation-group")\n' +
528 | " .call(makeAnnotations)\n"
529 | );
530 |
531 | $("code").each(function (i, block) {
532 | highlight.highlightBlock(block);
533 | });
534 | };
535 |
536 | sandboxCode();
537 | });
538 |
--------------------------------------------------------------------------------
/docs/img/a-badge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/a-badge.png
--------------------------------------------------------------------------------
/docs/img/a-callout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/a-callout.png
--------------------------------------------------------------------------------
/docs/img/a-circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/a-circle.png
--------------------------------------------------------------------------------
/docs/img/a-curve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/a-curve.png
--------------------------------------------------------------------------------
/docs/img/a-elbow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/a-elbow.png
--------------------------------------------------------------------------------
/docs/img/a-label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/a-label.png
--------------------------------------------------------------------------------
/docs/img/a-rect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/a-rect.png
--------------------------------------------------------------------------------
/docs/img/a-threshold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/a-threshold.png
--------------------------------------------------------------------------------
/docs/img/anatomy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/anatomy.png
--------------------------------------------------------------------------------
/docs/img/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/arrow.png
--------------------------------------------------------------------------------
/docs/img/basis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/basis.png
--------------------------------------------------------------------------------
/docs/img/cardinal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/cardinal.png
--------------------------------------------------------------------------------
/docs/img/circle-pack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/circle-pack.png
--------------------------------------------------------------------------------
/docs/img/classes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/classes.png
--------------------------------------------------------------------------------
/docs/img/curve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/curve.png
--------------------------------------------------------------------------------
/docs/img/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/dot.png
--------------------------------------------------------------------------------
/docs/img/dots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/dots.png
--------------------------------------------------------------------------------
/docs/img/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/down.png
--------------------------------------------------------------------------------
/docs/img/dynamic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/dynamic.png
--------------------------------------------------------------------------------
/docs/img/elbow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/elbow.png
--------------------------------------------------------------------------------
/docs/img/encircle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/encircle.png
--------------------------------------------------------------------------------
/docs/img/example1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/example1.png
--------------------------------------------------------------------------------
/docs/img/example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/example2.png
--------------------------------------------------------------------------------
/docs/img/example3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/example3.png
--------------------------------------------------------------------------------
/docs/img/heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/heart.png
--------------------------------------------------------------------------------
/docs/img/horizontal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/horizontal.png
--------------------------------------------------------------------------------
/docs/img/json.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/json.png
--------------------------------------------------------------------------------
/docs/img/left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/left.png
--------------------------------------------------------------------------------
/docs/img/leftRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/leftRight.png
--------------------------------------------------------------------------------
/docs/img/line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/line.png
--------------------------------------------------------------------------------
/docs/img/linear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/linear.png
--------------------------------------------------------------------------------
/docs/img/makeover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/makeover.png
--------------------------------------------------------------------------------
/docs/img/map-edit-mode.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/map-edit-mode.jpg
--------------------------------------------------------------------------------
/docs/img/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/menu.png
--------------------------------------------------------------------------------
/docs/img/middle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/middle.png
--------------------------------------------------------------------------------
/docs/img/none.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/none.png
--------------------------------------------------------------------------------
/docs/img/overlapping.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/overlapping.png
--------------------------------------------------------------------------------
/docs/img/post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/post.png
--------------------------------------------------------------------------------
/docs/img/resize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/resize.png
--------------------------------------------------------------------------------
/docs/img/right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/right.png
--------------------------------------------------------------------------------
/docs/img/step.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/step.png
--------------------------------------------------------------------------------
/docs/img/tooltip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/tooltip.png
--------------------------------------------------------------------------------
/docs/img/top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/top.png
--------------------------------------------------------------------------------
/docs/img/topBottom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/topBottom.png
--------------------------------------------------------------------------------
/docs/img/vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/susielu/d3-annotation/18c85b076387b99dc9a196d5bd0b9073198fab39/docs/img/vertical.png
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
d3-annotation
291 |
Made with
292 | by
293 | Susie Lu
294 |
295 |
296 |
297 |
298 |
299 |
304 |
305 |
310 |
311 |
312 |
313 |
314 |
315 |
d3-annotation
316 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
503 |
504 |
505 |
615 |
622 |
626 |
627 |
628 |
629 |
630 | Made by
631 | Susie Lu
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
--------------------------------------------------------------------------------
/docs/vendor/d3-dispatch.js:
--------------------------------------------------------------------------------
1 | // https://d3js.org/d3-dispatch/ Version 1.0.3. Copyright 2017 Mike Bostock.
2 | !function(n,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(n.d3=n.d3||{})}(this,function(n){"use strict";function e(){for(var n,e=0,r=arguments.length,o={};e=0&&(t=n.slice(r+1),n=n.slice(0,r)),n&&!e.hasOwnProperty(n))throw new Error("unknown type: "+n);return{type:n,name:t}})}function o(n,e){for(var t,r=0,o=n.length;r0)for(var t,r,o=new Array(t),i=0;i1e-6)if(Math.abs(c*r-a*u)>1e-6&&e){var x=s-n,y=_-o,M=r*r+a*a,l=x*x+y*y,d=Math.sqrt(M),p=Math.sqrt(f),v=e*Math.tan((h-Math.acos((M+f-l)/(2*d*p)))/2),b=v/p,w=v/d;Math.abs(b-1)>1e-6&&(this._+="L"+(t+b*u)+","+(i+b*c)),this._+="A"+e+","+e+",0,0,"+ +(c*x>u*y)+","+(this._x1=t+w*r)+","+(this._y1=i+w*a)}else this._+="L"+(this._x1=t)+","+(this._y1=i);else;},arc:function(t,i,s,n,o,r){t=+t,i=+i,s=+s;var a=s*Math.cos(n),u=s*Math.sin(n),c=t+a,f=i+u,x=1^r,y=r?n-o:o-n;if(s<0)throw new Error("negative radius: "+s);null===this._x1?this._+="M"+c+","+f:(Math.abs(this._x1-c)>1e-6||Math.abs(this._y1-f)>1e-6)&&(this._+="L"+c+","+f),s&&(y<0&&(y=y%_+_),y>e?this._+="A"+s+","+s+",0,1,"+x+","+(t-a)+","+(i-u)+"A"+s+","+s+",0,1,"+x+","+(this._x1=c)+","+(this._y1=f):y>1e-6&&(this._+="A"+s+","+s+",0,"+ +(y>=h)+","+x+","+(this._x1=t+s*Math.cos(o))+","+(this._y1=i+s*Math.sin(o))))},rect:function(t,i,s,h){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+i)+"h"+ +s+"v"+ +h+"h"+-s+"Z"},toString:function(){return this._}},t.path=s,Object.defineProperty(t,"__esModule",{value:!0})});
--------------------------------------------------------------------------------
/docs/vendor/d3-selection.js:
--------------------------------------------------------------------------------
1 | // https://d3js.org/d3-selection/ Version 1.0.6. Copyright 2017 Mike Bostock.
2 | !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(t.d3=t.d3||{})}(this,function(t){"use strict";function n(t){return function(){var n=this.ownerDocument,e=this.namespaceURI;return e===W&&n.documentElement.namespaceURI===W?n.createElement(t):n.createElementNS(e,t)}}function e(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function r(){return new i}function i(){this._="@"+(++et).toString(36)}function o(t,n,e){return t=u(t,n,e),function(n){var e=n.relatedTarget;e&&(e===this||8&e.compareDocumentPosition(this))||t.call(this,n)}}function u(n,e,r){return function(i){var o=t.event;t.event=i;try{n.call(this,this.__data__,e,r)}finally{t.event=o}}}function c(t){return t.trim().split(/^|\s+/).map(function(t){var n="",e=t.indexOf(".");return e>=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}})}function s(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;rn?1:t>=n?0:NaN}function m(t){return function(){this.removeAttribute(t)}}function y(t){return function(){this.removeAttributeNS(t.space,t.local)}}function g(t,n){return function(){this.setAttribute(t,n)}}function w(t,n){return function(){this.setAttributeNS(t.space,t.local,n)}}function A(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e)}}function x(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e)}}function S(t){return function(){this.style.removeProperty(t)}}function b(t,n,e){return function(){this.style.setProperty(t,n,e)}}function E(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}function N(t){return function(){delete this[t]}}function C(t,n){return function(){this[t]=n}}function M(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}function L(t){return t.trim().split(/^|\s+/)}function P(t){return t.classList||new T(t)}function T(t){this._node=t,this._names=L(t.getAttribute("class")||"")}function q(t,n){for(var e=P(t),r=-1,i=n.length;++r=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),Z.hasOwnProperty(n)?{space:Z[n],local:t}:t},nt=function(t){var r=tt(t);return(r.local?e:n)(r)},et=0;i.prototype=r.prototype={constructor:i,get:function(t){for(var n=this._;!(n in t);)if(!(t=t.parentNode))return;return t[n]},set:function(t,n){return t[this._]=n},remove:function(t){return this._ in t&&delete t[this._]},toString:function(){return this._}};var rt=function(t){return function(){return this.matches(t)}};if("undefined"!=typeof document){var it=document.documentElement;if(!it.matches){var ot=it.webkitMatchesSelector||it.msMatchesSelector||it.mozMatchesSelector||it.oMatchesSelector;rt=function(t){return function(){return ot.call(this,t)}}}}var ut=rt,ct={};if(t.event=null,"undefined"!=typeof document){"onmouseenter"in document.documentElement||(ct={mouseenter:"mouseover",mouseleave:"mouseout"})}var st=function(t,n,e){var r,i,o=c(t+""),u=o.length;{if(!(arguments.length<2)){for(l=n?a:s,null==e&&(e=!1),r=0;r=x&&(x=A+1);!(w=y[x])&&++x=0;)(r=i[o])&&(u&&u!==r.nextSibling&&u.parentNode.insertBefore(r,u),u=r);return this},Et=function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=d);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?S:"function"==typeof n?E:b)(t,n,null==e?"":e)):Ot(r=this.node()).getComputedStyle(r,null).getPropertyValue(t)},Dt=function(t,n){return arguments.length>1?this.each((null==n?N:"function"==typeof n?M:C)(t,n)):this.node()[t]};T.prototype={add:function(t){this._names.indexOf(t)<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var n=this._names.indexOf(t);n>=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var Vt=function(t,n){var e=L(t+"");if(arguments.length<2){for(var r=P(this.node()),i=-1,o=e.length;++iH)throw new Error("too late");return e}function s(t,n){var e=t.__transition;if(!e||!(e=e[n])||e.state>K)throw new Error("too late");return e}function l(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("too late");return e}function f(t,n,e){function i(t){e.state=J,e.timer.restart(o,e.delay,e.time),e.delay<=t&&o(t-e.delay)}function o(i){var f,c,h,d;if(e.state!==J)return a();for(f in l)if(d=l[f],d.name===e.name){if(d.state===L)return r.timeout(o);d.state===Q?(d.state=W,d.timer.stop(),d.on.call("interrupt",t,t.__data__,d.index,d.group),delete l[f]):+f=0&&(t=t.slice(0,n)),!t||"start"===t})}function N(t,n,e){var r,i,o=E(n)?a:s;return function(){var u=o(this,t),a=u.on;a!==r&&(i=(r=a).copy()).on(n,e),u.on=i}}function T(t){return function(){var n=this.parentNode;for(var e in this.__transition)if(+e!==t)return;n&&n.removeChild(this)}}function q(t,e){var r,i,o;return function(){var u=n.window(this).getComputedStyle(this,null),a=u.getPropertyValue(t),s=(this.style.removeProperty(t),u.getPropertyValue(t));return a===s?null:a===r&&s===i?o:o=e(r=a,i=s)}}function V(t){return function(){this.style.removeProperty(t)}}function O(t,e,r){var i,o;return function(){var u=n.window(this).getComputedStyle(this,null).getPropertyValue(t);return u===r?null:u===i?o:o=e(i=u,r)}}function j(t,e,r){var i,o,u;return function(){var a=n.window(this).getComputedStyle(this,null),s=a.getPropertyValue(t),l=r(this);return null==l&&(this.style.removeProperty(t),l=a.getPropertyValue(t)),s===l?null:s===i&&l===o?u:u=e(i=s,o=l)}}function k(t,n,e){function r(){var r=this,i=n.apply(r,arguments);return i&&function(n){r.style.setProperty(t,i(n),e)}}return r._value=n,r}function z(t){return function(){this.textContent=t}}function M(t){return function(){var n=t(this);this.textContent=null==n?"":n}}function R(t,n,e,r){this._groups=t,this._parents=n,this._name=e,this._id=r}function I(t){return n.selection().transition(t)}function B(){return++mt}function D(t,n){for(var e;!(e=t.__transition)||!(e=e[n]);)if(!(t=t.parentNode))return wt.time=r.now(),wt;return e}var F=e.dispatch("start","end","interrupt"),G=[],H=0,J=1,K=2,L=3,Q=4,U=5,W=6,X=function(t,n,e,r,i,o){var u=t.__transition;if(u){if(e in u)return}else t.__transition={};f(t,e,{name:n,index:r,group:i,on:F,tween:G,time:o.time,delay:o.delay,duration:o.duration,ease:o.ease,timer:null,state:H})},Y=function(t,n){var e,r,i,o=t.__transition,u=!0;if(o){n=null==n?null:n+"";for(i in o)(e=o[i]).name===n?(r=e.state>K&&e.stateJ&&e.name===n)return new R([[t]],At,n,+r)}return null};t.transition=I,t.active=xt,t.interrupt=Y,Object.defineProperty(t,"__esModule",{value:!0})});
--------------------------------------------------------------------------------
/docs/vendor/highlight.css:
--------------------------------------------------------------------------------
1 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
2 |
3 | /* Tomorrow Comment */
4 | .hljs-comment,
5 | .hljs-quote {
6 | color: #adafac;
7 | }
8 |
9 | /* Tomorrow Red */
10 | .hljs-variable,
11 | .hljs-template-variable,
12 | .hljs-tag,
13 | .hljs-name,
14 | .hljs-selector-id,
15 | .hljs-selector-class,
16 | .hljs-regexp,
17 | .hljs-deletion {
18 | color: #c82829;
19 | }
20 |
21 | /* Tomorrow Orange */
22 | .hljs-number,
23 | .hljs-built_in,
24 | .hljs-builtin-name,
25 | .hljs-literal,
26 | .hljs-type,
27 | .hljs-params,
28 | .hljs-meta,
29 | .hljs-link {
30 | color: #E8336D;
31 | }
32 |
33 | /* Tomorrow Yellow */
34 | .hljs-attribute {
35 | color: #eab700;
36 | }
37 |
38 | /* Tomorrow Green */
39 | .hljs-string,
40 | .hljs-symbol,
41 | .hljs-bullet,
42 | .hljs-addition {
43 | color: #00897b;
44 | }
45 |
46 | /* Tomorrow Blue */
47 | .hljs-title,
48 | .hljs-section {
49 | color: #00897b;
50 | }
51 |
52 | /* Tomorrow Purple */
53 | .hljs-keyword,
54 | .hljs-selector-tag {
55 | color: #00897b;
56 | }
57 |
58 | pre{
59 | display: block;
60 | overflow-x: auto;
61 | background: #f3f3f3;
62 | border-top: 1px dotted #aaa;
63 | border-bottom: 1px dotted #aaa;
64 | color: #695B5B;
65 | padding: 0.5em;
66 | font-size: .9em;
67 | line-height: 1.4em;
68 | }
69 |
70 | .hljs-emphasis {
71 | font-style: italic;
72 | }
73 |
74 | .hljs-strong {
75 | font-weight: bold;
76 | }
77 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import annotation from './src/Adapter-d3'
2 | import { Type, d3Label, d3Callout, d3CalloutCurve, d3CalloutElbow, d3CalloutCircle,
3 | d3CalloutRect, d3XYThreshold, d3Badge, customType } from './src/Types-d3'
4 |
5 | export {
6 | annotation,
7 | Type as annotationTypeBase ,
8 | d3Label as annotationLabel ,
9 | d3Callout as annotationCallout ,
10 | d3CalloutCurve as annotationCalloutCurve ,
11 | d3CalloutElbow as annotationCalloutElbow ,
12 | d3CalloutCircle as annotationCalloutCircle ,
13 | d3CalloutRect as annotationCalloutRect ,
14 | d3XYThreshold as annotationXYThreshold ,
15 | d3Badge as annotationBadge ,
16 | customType as annotationCustomType
17 | }
18 |
19 | export default {
20 | annotation,
21 | annotationTypeBase : Type,
22 | annotationLabel : d3Label,
23 | annotationCallout : d3Callout,
24 | annotationCalloutCurve : d3CalloutCurve,
25 | annotationCalloutElbow : d3CalloutElbow,
26 | annotationCalloutCircle : d3CalloutCircle,
27 | annotationCalloutRect : d3CalloutRect,
28 | annotationXYThreshold : d3XYThreshold,
29 | annotationBadge : d3Badge,
30 | annotationCustomType : customType
31 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "d3-svg-annotation",
3 | "version": "2.5.0",
4 | "description": "",
5 | "main": "indexRollup.js",
6 | "jsnext:main": "indexRollupNext.js",
7 | "web": "d3-annotation.js",
8 | "web:independent": "d3-annotation.independent.js",
9 | "module": "indexRollupNext.js",
10 | "files": [
11 | "index.js",
12 | "indexRollup.js",
13 | "indexRollup.js.map",
14 | "indexRollupNext.js",
15 | "indexRollupNext.js.map",
16 | "d3-annotation.*",
17 | "types/*"
18 | ],
19 | "keywords": [
20 | "d3",
21 | "d3-module",
22 | "annotation"
23 | ],
24 | "directories": {},
25 | "devDependencies": {
26 | "babel-eslint": "7.1.1",
27 | "babel-plugin-external-helpers": "6.18.0",
28 | "babel-plugin-transform-object-assign": "6.22.0",
29 | "babel-plugin-transform-object-rest-spread": "6.20.2",
30 | "babel-preset-es2015": "6.18.0",
31 | "babelify": "7.3.0",
32 | "babelrc-rollup": "3.0.0",
33 | "browserify": "13.1.1",
34 | "eslint": "3.17.1",
35 | "http-server": "0.9.0",
36 | "jest": "^20.0.4",
37 | "marked": "0.3.9",
38 | "markedify": "0.0.2",
39 | "npm-watch": "0.1.6",
40 | "reify": "0.4.16",
41 | "rollup": "0.41.4",
42 | "rollup-plugin-babel": "2.6.1",
43 | "rollup-plugin-node-resolve": "2.0.0",
44 | "uglify-js": "2.8.1",
45 | "@types/d3-dispatch": "1.0.4",
46 | "@types/d3-selection": "1.0.10"
47 | },
48 | "scripts": {
49 | "start": "npm run server & npm run watch",
50 | "server": "http-server",
51 | "rollup:web": "rollup -c rollupWeb.config.js && rollup -c rollupWebIndependent.config.js",
52 | "docs": "node docs/browserify-docs.js && npm run docs-bundle",
53 | "docs-bundle": "uglifyjs --source-map=docs/bundle.map --source-map-url=bundle.map -- docs/vendor/jquery2.1.1.min.js docs/vendor/materialize.min.js docs/vendor/d3-selection.js docs/vendor/d3-path.js docs/vendor/d3-shape.js docs/vendor/d3-dispatch.js docs/vendor/d3-drag.js docs/vendor/d3-transition.js d3-annotation.js docs/docs-compiled.js > docs/bundle.js",
54 | "watch": "npm-watch",
55 | "uglify": "uglifyjs d3-annotation.js -o d3-annotation.min.js -c -m",
56 | "uglify:independent": "uglifyjs d3-annotation.independent.js -o d3-annotation.independent.min.js -c -m",
57 | "prepublishOnly": "eslint src/** && npm run test:only && npm run rollup:web && npm run uglify && npm run uglify:independent && rollup -c rollupWeb.config.js && rollup -c && rollup -c rollupNext.config.js",
58 | "test": "jest --watch",
59 | "test:only": "jest"
60 | },
61 | "watch": {
62 | "rollup:web": [
63 | "src/*.js",
64 | "src/**/*.js"
65 | ],
66 | "docs": {
67 | "patterns": [
68 | "docs/docs.js",
69 | "d3-annotation.js",
70 | "docs/content/*.md"
71 | ],
72 | "extensions": "js,md"
73 | }
74 | },
75 | "repository": {
76 | "type": "git",
77 | "url": "https://github.com/susielu/d3-annotation.git"
78 | },
79 | "license": "Apache-2.0",
80 | "bugs": {
81 | "url": "https://github.com/susielu/d3-annotation/issues"
82 | },
83 | "types": "./types/d3-svg-annotation.d.ts",
84 | "dependencies": {
85 | "d3-dispatch": "1.0.2",
86 | "d3-drag": "1.2.3",
87 | "d3-selection": "1.4.0",
88 | "d3-shape": "1.0.4",
89 | "d3-transition": "1.0.3"
90 | },
91 | "homepage": "http://d3-annotation.susielu.com"
92 | }
93 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import babelrc from 'babelrc-rollup';
3 | import nodeResolve from 'rollup-plugin-node-resolve';
4 |
5 | let pkg = require('./package.json');
6 |
7 | export default {
8 | entry: 'index.js',
9 | plugins: [
10 | babel(babelrc()),
11 | nodeResolve()
12 | ],
13 | targets: [
14 | {
15 | dest: pkg.main,
16 | format: 'umd',
17 | moduleName: 'd3',
18 | sourceMap: true,
19 | globals: {
20 | 'd3-selection': 'd3',
21 | 'd3-dispatch': 'd3',
22 | 'd3-shape': 'd3',
23 | 'd3-drag': 'd3'
24 | }
25 | }
26 | ]
27 | };
28 |
--------------------------------------------------------------------------------
/rollupNext.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import babelrc from 'babelrc-rollup';
3 |
4 | let pkg = require('./package.json');
5 |
6 | export default {
7 | entry: 'index.js',
8 | plugins: [
9 | babel(babelrc())
10 | ],
11 | targets: [
12 | {
13 | dest: pkg['jsnext:main'],
14 | format: 'es',
15 | moduleName: 'd3',
16 | sourceMap: true,
17 | globals: {
18 | 'd3-selection': 'd3',
19 | 'd3-dispatch': 'd3',
20 | 'd3-shape': 'd3',
21 | 'd3-drag': 'd3'
22 | }
23 | }
24 | ]
25 | };
26 |
--------------------------------------------------------------------------------
/rollupWeb.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import babelrc from 'babelrc-rollup';
3 |
4 | let pkg = require('./package.json');
5 |
6 | export default {
7 | entry: 'index.js',
8 | plugins: [
9 | babel(babelrc())
10 | ],
11 | external:['d3-selection', 'd3-dispatch', 'd3-shape', 'd3-drag'],
12 | targets: [
13 | {
14 | dest: pkg.web,
15 | format: 'umd',
16 | moduleName: 'd3',
17 | sourceMap: true,
18 | globals: {
19 | 'd3-selection': 'd3',
20 | 'd3-dispatch': 'd3',
21 | 'd3-shape': 'd3',
22 | 'd3-drag': 'd3'
23 | }
24 | }
25 | ]
26 | };
27 |
--------------------------------------------------------------------------------
/rollupWebIndependent.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import babelrc from 'babelrc-rollup';
3 |
4 | let pkg = require('./package.json');
5 |
6 | export default {
7 | entry: 'index.js',
8 | plugins: [
9 | babel(babelrc())
10 | ],
11 | external:['d3-selection', 'd3-dispatch', 'd3-shape', 'd3-drag'],
12 | targets: [
13 | {
14 | dest: pkg["web:independent"],
15 | format: 'umd',
16 | moduleName: 'd3annotation',
17 | sourceMap: true,
18 | globals: {
19 | 'd3-selection': 'd3',
20 | 'd3-dispatch': 'd3',
21 | 'd3-shape': 'd3',
22 | 'd3-drag': 'd3'
23 | }
24 | }
25 | ]
26 | };
27 |
--------------------------------------------------------------------------------
/src/Adapter-d3.js:
--------------------------------------------------------------------------------
1 | import Annotation from "./Annotation"
2 | import AnnotationCollection from "./AnnotationCollection"
3 | import { newWithClass, d3Callout } from "./Types-d3"
4 | import { select } from "d3-selection"
5 | import { dispatch } from "d3-dispatch"
6 |
7 | export default function annotation() {
8 | let annotations = [],
9 | collection,
10 | context, //TODO: add canvas functionality
11 | disable = [],
12 | accessors = {},
13 | accessorsInverse = {},
14 | editMode = false,
15 | ids,
16 | type = d3Callout,
17 | textWrap,
18 | notePadding,
19 | annotationDispatcher = dispatch(
20 | "subjectover",
21 | "subjectout",
22 | "subjectclick",
23 | "connectorover",
24 | "connectorout",
25 | "connectorclick",
26 | "noteover",
27 | "noteout",
28 | "noteclick",
29 | "dragend",
30 | "dragstart"
31 | ),
32 | sel
33 |
34 | const annotation = function(selection) {
35 | sel = selection
36 | //TODO: check to see if this is still needed
37 | if (!editMode) {
38 | selection.selectAll("circle.handle").remove()
39 | }
40 |
41 | const translatedAnnotations = annotations.map(a => {
42 | if (!a.type) {
43 | a.type = type
44 | }
45 | if (!a.disable) {
46 | a.disable = disable
47 | }
48 | return new Annotation(a)
49 | })
50 |
51 | collection =
52 | collection ||
53 | new AnnotationCollection({
54 | annotations: translatedAnnotations,
55 | accessors,
56 | accessorsInverse,
57 | ids
58 | })
59 |
60 | const annotationG = selection.selectAll("g").data([collection])
61 | annotationG.enter().append("g").attr("class", `annotations`)
62 |
63 | const group = selection.select("g.annotations")
64 | newWithClass(group, collection.annotations, "g", "annotation")
65 |
66 | const annotation = group.selectAll("g.annotation")
67 |
68 | annotation.each(function(d) {
69 | const a = select(this)
70 |
71 | a.attr("class", "annotation")
72 |
73 | newWithClass(a, [d], "g", "annotation-connector")
74 | newWithClass(a, [d], "g", "annotation-subject")
75 | newWithClass(a, [d], "g", "annotation-note")
76 | newWithClass(
77 | a.select("g.annotation-note"),
78 | [d],
79 | "g",
80 | "annotation-note-content"
81 | )
82 | d.type =
83 | d.type.toString() === "[object Object]"
84 | ? d.type
85 | : new d.type({
86 | a,
87 | annotation: d,
88 | textWrap,
89 | notePadding,
90 | editMode,
91 | dispatcher: annotationDispatcher,
92 | accessors
93 | })
94 | d.type.draw()
95 | d.type.drawText && d.type.drawText()
96 | })
97 | }
98 |
99 | annotation.json = function() {
100 | /* eslint-disable no-console */
101 | console.log(
102 | "Annotations JSON was copied to your clipboard. Please note the annotation type is not JSON compatible. It appears in the objects array in the console, but not in the copied JSON.",
103 | collection.json
104 | )
105 | /* eslint-enable no-console */
106 | window.copy(
107 | JSON.stringify(
108 | collection.json.map(a => {
109 | delete a.type
110 | return a
111 | })
112 | )
113 | )
114 | return annotation
115 | }
116 |
117 | annotation.update = function() {
118 | if (annotations && collection) {
119 | annotations = collection.annotations.map(a => {
120 | a.type.draw()
121 | return a
122 | })
123 | }
124 | return annotation
125 | }
126 |
127 | annotation.updateText = function() {
128 | if (collection) {
129 | collection.updateText(textWrap)
130 | annotations = collection.annotations
131 | }
132 | return annotation
133 | }
134 |
135 | annotation.updatedAccessors = function() {
136 | collection.setPositionWithAccessors()
137 | annotations = collection.annotations
138 | return annotation
139 | }
140 |
141 | annotation.disable = function(_) {
142 | if (!arguments.length) return disable
143 | disable = _
144 | if (collection) {
145 | collection.updateDisable(disable)
146 | annotations = collection.annotations
147 | }
148 | return annotation
149 | }
150 |
151 | annotation.textWrap = function(_) {
152 | if (!arguments.length) return textWrap
153 | textWrap = _
154 | if (collection) {
155 | collection.updateTextWrap(textWrap)
156 | annotations = collection.annotations
157 | }
158 | return annotation
159 | }
160 |
161 | annotation.notePadding = function(_) {
162 | if (!arguments.length) return notePadding
163 | notePadding = _
164 | if (collection) {
165 | collection.updateNotePadding(notePadding)
166 | annotations = collection.annotations
167 | }
168 | return annotation
169 | }
170 | //todo think of how to handle when undefined is sent
171 | annotation.type = function(_, settings) {
172 | if (!arguments.length) return type
173 | type = _
174 | if (collection) {
175 | collection.annotations.map(a => {
176 | a.type.note &&
177 | a.type.note.selectAll("*:not(.annotation-note-content)").remove()
178 | a.type.noteContent && a.type.noteContent.selectAll("*").remove()
179 | a.type.subject && a.type.subject.selectAll("*").remove()
180 | a.type.connector && a.type.connector.selectAll("*").remove()
181 | a.type.typeSettings = {}
182 | a.type = type
183 |
184 | a.subject = settings && settings.subject || a.subject
185 | a.connector = settings && settings.connector || a.connector
186 | a.note = settings && settings.note || a.note
187 | })
188 |
189 | annotations = collection.annotations
190 | }
191 | return annotation
192 | }
193 |
194 | annotation.annotations = function(_) {
195 | if (!arguments.length)
196 | return collection && collection.annotations || annotations
197 | annotations = _
198 |
199 | if (collection && collection.annotations) {
200 | const rerun = annotations.some(
201 | d => !d.type || d.type.toString() !== "[object Object]"
202 | )
203 |
204 | if (rerun) {
205 | collection = null
206 | annotation(sel)
207 | } else {
208 | collection.annotations = annotations
209 | }
210 | }
211 | return annotation
212 | }
213 |
214 | annotation.context = function(_) {
215 | if (!arguments.length) return context
216 | context = _
217 | return annotation
218 | }
219 |
220 | annotation.accessors = function(_) {
221 | if (!arguments.length) return accessors
222 | accessors = _
223 | return annotation
224 | }
225 |
226 | annotation.accessorsInverse = function(_) {
227 | if (!arguments.length) return accessorsInverse
228 | accessorsInverse = _
229 | return annotation
230 | }
231 |
232 | annotation.ids = function(_) {
233 | if (!arguments.length) return ids
234 | ids = _
235 | return annotation
236 | }
237 |
238 | annotation.editMode = function(_) {
239 | if (!arguments.length) return editMode
240 | editMode = _
241 |
242 | if (sel) {
243 | sel.selectAll("g.annotation").classed("editable", editMode)
244 | }
245 |
246 | if (collection) {
247 | collection.editMode(editMode)
248 | annotations = collection.annotations
249 | }
250 | return annotation
251 | }
252 |
253 | annotation.collection = function(_) {
254 | if (!arguments.length) return collection
255 | collection = _
256 | return annotation
257 | }
258 |
259 | annotation.on = function() {
260 | const value = annotationDispatcher.on.apply(annotationDispatcher, arguments)
261 | return value === annotationDispatcher ? annotation : value
262 | }
263 |
264 | return annotation
265 | }
266 |
--------------------------------------------------------------------------------
/src/Annotation.js:
--------------------------------------------------------------------------------
1 | export default class Annotation {
2 | constructor({
3 | x = 0,
4 | y = 0,
5 | nx,
6 | ny,
7 | dy = 0,
8 | dx = 0,
9 | color = "grey",
10 | data,
11 | type,
12 | subject,
13 | connector,
14 | note,
15 | disable,
16 | id,
17 | className
18 | }) {
19 | this._dx = nx !== undefined ? nx - x : dx
20 | this._dy = ny !== undefined ? ny - y : dy
21 | this._x = x
22 | this._y = y
23 | this._color = color
24 | this.id = id
25 | this._className = className || ""
26 |
27 | this._type = type || ""
28 | this.data = data
29 |
30 | this.note = note || {}
31 | this.connector = connector || {}
32 | this.subject = subject || {}
33 |
34 | this.disable = disable || []
35 | }
36 |
37 | updatePosition() {
38 | if (this.type.setPosition) {
39 | this.type.setPosition()
40 | if (
41 | this.type.subject &&
42 | this.type.subject.selectAll(":not(.handle)").nodes().length !== 0
43 | ) {
44 | this.type.redrawSubject()
45 | }
46 | }
47 | }
48 |
49 | clearComponents() {
50 | this.type.clearComponents && this.type.clearComponents()
51 | }
52 |
53 | get className() {
54 | return this._className
55 | }
56 |
57 | set className(className) {
58 | this._className = className
59 | if (this.type.setClassName) this.type.setClassName()
60 | }
61 |
62 | updateOffset() {
63 | if (this.type.setOffset) {
64 | this.type.setOffset()
65 |
66 | if (this.type.connector.selectAll(":not(.handle)").nodes().length !== 0) {
67 | this.type.redrawConnector()
68 | }
69 |
70 | this.type.redrawNote()
71 | }
72 | }
73 |
74 | get type() {
75 | return this._type
76 | }
77 |
78 | set type(type) {
79 | this._type = type
80 | this.clearComponents()
81 | }
82 |
83 | get x() {
84 | return this._x
85 | }
86 | set x(x) {
87 | this._x = x
88 | this.updatePosition()
89 | }
90 |
91 | get y() {
92 | return this._y
93 | }
94 | set y(y) {
95 | this._y = y
96 | this.updatePosition()
97 | }
98 |
99 | get color() {
100 | return this._color
101 | }
102 | set color(color) {
103 | this._color = color
104 | this.updatePosition()
105 | }
106 |
107 | get dx() {
108 | return this._dx
109 | }
110 | set dx(dx) {
111 | this._dx = dx
112 | this.updateOffset()
113 | }
114 |
115 | get dy() {
116 | return this._dy
117 | }
118 | set dy(dy) {
119 | this._dy = dy
120 | this.updateOffset()
121 | }
122 |
123 | set nx(nx) {
124 | this._dx = nx - this._x
125 | this.updateOffset()
126 | }
127 |
128 | set ny(ny) {
129 | this._dy = ny - this._y
130 | this.updateOffset()
131 | }
132 |
133 | get offset() {
134 | return { x: this._dx, y: this._dy }
135 | }
136 |
137 | set offset({ x, y }) {
138 | this._dx = x
139 | this._dy = y
140 | this.updateOffset()
141 | }
142 |
143 | get position() {
144 | return { x: this._x, y: this._y }
145 | }
146 |
147 | set position({ x, y }) {
148 | this._x = x
149 | this._y = y
150 | this.updatePosition()
151 | }
152 |
153 | get translation() {
154 | return {
155 | x: this._x + this._dx,
156 | y: this._y + this._dy
157 | }
158 | }
159 |
160 | get json() {
161 | const json = {
162 | x: this._x,
163 | y: this._y,
164 | dx: this._dx,
165 | dy: this._dy
166 | }
167 |
168 | if (this.data && Object.keys(this.data).length > 0) json.data = this.data
169 | if (this.type) json.type = this.type
170 | if (this._className) json.className = this._className
171 |
172 | if (Object.keys(this.connector).length > 0) json.connector = this.connector
173 | if (Object.keys(this.subject).length > 0) json.subject = this.subject
174 | if (Object.keys(this.note).length > 0) json.note = this.note
175 |
176 | return json
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/AnnotationCollection.js:
--------------------------------------------------------------------------------
1 | export default class AnnotationCollection {
2 |
3 | constructor ({ annotations, accessors, accessorsInverse }) {
4 | this.accessors = accessors
5 | this.accessorsInverse = accessorsInverse
6 | this.annotations = annotations
7 | }
8 |
9 | clearTypes (newSettings) {
10 | this.annotations.forEach(d => {
11 | d.type = undefined
12 | d.subject = newSettings && newSettings.subject || d.subject
13 | d.connector = newSettings && newSettings.connector || d.connector
14 | d.note = newSettings && newSettings.note || d.note
15 | })
16 | }
17 |
18 | setPositionWithAccessors () {
19 | this.annotations.forEach(d => {
20 | d.type.setPositionWithAccessors(this.accessors)
21 | })
22 | }
23 |
24 | editMode (editMode) { this.annotations.forEach(a => {
25 | if (a.type) {
26 | a.type.editMode = editMode
27 | a.type.updateEditMode()
28 | }
29 | })
30 | }
31 |
32 | updateDisable (disable) {
33 | this.annotations.forEach(a => {
34 | a.disable = disable
35 | if (a.type) {
36 | disable.forEach(d => {
37 | if (a.type[d]) {
38 | a.type[d].remove && a.type[d].remove()
39 | a.type[d] = undefined
40 | }
41 | })
42 | }
43 | })
44 | }
45 |
46 | updateTextWrap (textWrap) {
47 | this.annotations.forEach(a => {
48 | if (a.type && a.type.updateTextWrap) {
49 | a.type.updateTextWrap(textWrap)
50 | }
51 | })
52 | }
53 |
54 | updateText () {
55 | this.annotations.forEach(a => {
56 | if (a.type && a.type.drawText) {
57 | a.type.drawText()
58 | }
59 | })
60 | }
61 |
62 | updateNotePadding (notePadding) {
63 | this.annotations.forEach(a => {
64 | if (a.type) {
65 | a.type.notePadding = notePadding
66 | }
67 | })
68 | }
69 |
70 | get json () {
71 | return this.annotations.map(a => {
72 | const json = a.json
73 | if (this.accessorsInverse && a.data) {
74 | json.data = {}
75 | Object.keys(this.accessorsInverse).forEach(k => {
76 | json.data[k] = this.accessorsInverse[k]({ x: a.x, y: a.y})
77 |
78 | //TODO make this feasible to map back to data for other types of subjects
79 | })
80 | }
81 | return json
82 | })
83 | }
84 |
85 | get noteNodes () {
86 | return this.annotations.map(a => ({ ...a.type.getNoteBBoxOffset(), positionX: a.x, positionY: a.y }))
87 | }
88 |
89 | //TODO: come back and rethink if a.x and a.y are applicable in all situations
90 | // get connectorNodes() {
91 | // return this.annotations.map(a => ({ ...a.type.getConnectorBBox(), startX: a.x, startY: a.y}))
92 | // }
93 |
94 | // get subjectNodes() {
95 | // return this.annotations.map(a => ({ ...a.type.getSubjectBBox(), startX: a.x, startY: a.y}))
96 | // }
97 |
98 | // get annotationNodes() {
99 | // return this.annotations.map(a => ({ ...a.type.getAnnotationBBox(), startX: a.x, startY: a.y}))
100 | // }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Builder.js:
--------------------------------------------------------------------------------
1 | import { line, arc, curveLinear } from "d3-shape"
2 |
3 | export const lineBuilder = ({ data, curve=curveLinear, canvasContext, className, classID }) => {
4 | const lineGen = line()
5 | .curve(curve)
6 |
7 | const builder = {
8 | type: 'path',
9 | className,
10 | classID,
11 | data
12 | }
13 |
14 | if (canvasContext) {
15 | lineGen.context(canvasContext)
16 | builder.pathMethods = lineGen
17 |
18 | } else {
19 | builder.attrs = {
20 | d: lineGen(data)
21 | }
22 | }
23 |
24 | return builder
25 | }
26 |
27 | export const arcBuilder = ({ data, canvasContext, className, classID }) => {
28 |
29 | const builder = {
30 | type: 'path',
31 | className,
32 | classID,
33 | data
34 | }
35 |
36 | const arcShape = arc()
37 | .innerRadius(data.innerRadius || 0)
38 | .outerRadius(data.outerRadius || data.radius || 2)
39 | .startAngle(data.startAngle || 0)
40 | .endAngle(data.endAngle || 2*Math.PI)
41 |
42 | if (canvasContext) {
43 | arcShape.context(canvasContext)
44 | builder.pathMethods = lineGen
45 |
46 | } else {
47 |
48 | builder.attrs = {
49 | d: arcShape()
50 | }
51 | }
52 |
53 | return builder
54 | }
--------------------------------------------------------------------------------
/src/Connector/end-arrow.js:
--------------------------------------------------------------------------------
1 | import { lineBuilder } from "../Builder"
2 |
3 | export default ({ annotation, start, end, scale = 1 }) => {
4 | const offset = annotation.position
5 | if (!start) {
6 | start = [annotation.dx, annotation.dy]
7 | } else {
8 | start = [-end[0] + start[0], -end[1] + start[1]]
9 | }
10 | if (!end) {
11 | end = [annotation.x - offset.x, annotation.y - offset.y]
12 | }
13 |
14 | let x1 = end[0],
15 | y1 = end[1]
16 |
17 | let dx = start[0]
18 | let dy = start[1]
19 |
20 | let size = 10 * scale
21 | let angleOffset = 16 / 180 * Math.PI
22 | let angle = Math.atan(dy / dx)
23 |
24 | if (dx < 0) {
25 | angle += Math.PI
26 | }
27 |
28 | const data = [
29 | [x1, y1],
30 | [
31 | Math.cos(angle + angleOffset) * size + x1,
32 | Math.sin(angle + angleOffset) * size + y1
33 | ],
34 | [
35 | Math.cos(angle - angleOffset) * size + x1,
36 | Math.sin(angle - angleOffset) * size + y1
37 | ],
38 | [x1, y1]
39 | ]
40 |
41 | //TODO add in reverse
42 | // if (canvasContext.arrowReverse){
43 | // data = [[x1, y1],
44 | // [Math.cos(angle + angleOffset)*size, Math.sin(angle + angleOffset)*size],
45 | // [Math.cos(angle - angleOffset)*size, Math.sin(angle - angleOffset)*size],
46 | // [x1, y1]
47 | // ]
48 | // } else {
49 | // data = [[x1, y1],
50 | // [Math.cos(angle + angleOffset)*size, Math.sin(angle + angleOffset)*size],
51 | // [Math.cos(angle - angleOffset)*size, Math.sin(angle - angleOffset)*size],
52 | // [x1, y1]
53 | // ]
54 | // }
55 |
56 | return {
57 | components: [
58 | lineBuilder({
59 | data,
60 | className: "connector-end connector-arrow",
61 | classID: "connector-end"
62 | })
63 | ]
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Connector/end-dot.js:
--------------------------------------------------------------------------------
1 | import { arcBuilder } from "../Builder"
2 |
3 | export default ({ line, scale = 1 }) => {
4 | let dot = arcBuilder({
5 | className: "connector-end connector-dot",
6 | classID: "connector-end",
7 | data: { radius: 3 * Math.sqrt(scale) }
8 | })
9 | dot.attrs.transform = `translate(${line.data[0][0]}, ${line.data[0][1]})`
10 |
11 | return { components: [dot] }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Connector/type-curve.js:
--------------------------------------------------------------------------------
1 | import { lineBuilder } from '../Builder'
2 | import { event } from 'd3-selection'
3 | import { lineSetup } from './type-line'
4 | import { curveCatmullRom } from 'd3-shape'
5 | import { pointHandle } from '../Handles'
6 |
7 | export default ({ type, connectorData, subjectType }) => {
8 |
9 | if (!connectorData) { connectorData = {} }
10 | if (!connectorData.points || typeof connectorData.points === "number") {
11 | connectorData.points = createPoints(type.annotation.offset, connectorData.points)
12 | }
13 | if (!connectorData.curve) { connectorData.curve = curveCatmullRom }
14 |
15 | let handles = []
16 |
17 | if (type.editMode) {
18 | const cHandles = connectorData.points
19 | .map((c,i) => ({...pointHandle({cx: c[0], cy: c[1]}), index: i}))
20 |
21 | const updatePoint = (index) => {
22 | connectorData.points[index][0] += event.dx
23 | connectorData.points[index][1] += event.dy
24 | type.redrawConnector()
25 | }
26 |
27 | handles = type.mapHandles(cHandles
28 | .map(h => ({ ...h.move, drag: updatePoint.bind(type, h.index)})))
29 |
30 | }
31 |
32 | let data = lineSetup({ type, subjectType })
33 | data = [data[0], ...connectorData.points, data[1]]
34 | const components = [lineBuilder({ data, curve: connectorData.curve, className: "connector" })]
35 |
36 | return { components , handles }
37 | }
38 |
39 | const createPoints = function ( offset, anchors=2) {
40 | const diff = { x: offset.x/(anchors + 1), y: offset.y/(anchors + 1) }
41 | const p = []
42 |
43 | let i = 1
44 | for (; i <= anchors; i++) {
45 | p.push([diff.x*i + i%2*20, diff.y*i - i%2*20])
46 | }
47 | return p
48 | }
--------------------------------------------------------------------------------
/src/Connector/type-elbow.js:
--------------------------------------------------------------------------------
1 | import { lineBuilder } from '../Builder'
2 |
3 | export default ({ type, subjectType }) => {
4 |
5 | const annotation = type.annotation
6 | const offset = annotation.position
7 |
8 | let x1 = annotation.x - offset.x,
9 | x2 = x1 + annotation.dx,
10 | y1 = annotation.y - offset.y,
11 | y2 = y1 + annotation.dy
12 |
13 | const subjectData = annotation.subject
14 |
15 | if (subjectType === "rect") {
16 | const { width, height } = subjectData
17 |
18 | if (width > 0 && annotation.dx > 0 || width < 0 && annotation.dx < 0) {
19 | if (Math.abs(width) > Math.abs(annotation.dx)) x1 = width/2
20 | else x1 = width
21 | }
22 | if (height > 0 && annotation.dy > 0 || height < 0 && annotation.dy < 0) {
23 | if (Math.abs(height) > Math.abs(annotation.dy)) y1 = height/2
24 | else y1 = height
25 | }
26 | if (x1 === width/2 && y1 === height/2) { x1 = x2; y1 = y2;}
27 | }
28 |
29 | let data = [[x1, y1], [x2, y2]]
30 |
31 | let diffY = y2 - y1
32 | let diffX = x2 - x1
33 | let xe = x2
34 | let ye = y2
35 | let opposite = y2 < y1 && x2 > x1 || x2 < x1 && y2 > y1 ? -1 : 1
36 |
37 | if (Math.abs(diffX) < Math.abs(diffY)) {
38 | xe = x2
39 | ye = y1 + diffX*opposite
40 | } else {
41 | ye = y2
42 | xe = x1 + diffY*opposite
43 | }
44 |
45 | if (subjectType === "circle" && (subjectData.outerRadius || subjectData.radius)) {
46 | const r = (subjectData.outerRadius || subjectData.radius) + (subjectData.radiusPadding || 0)
47 | const length = r/Math.sqrt(2)
48 |
49 | if (Math.abs(diffX) > length && Math.abs(diffY) > length) {
50 | x1 = length*(x2 < 0 ? -1 : 1)
51 | y1 = length*(y2 < 0 ? -1 : 1)
52 | data = [[x1, y1], [xe , ye ], [x2, y2]]
53 |
54 | } else if (Math.abs(diffX) > Math.abs(diffY)) {
55 | const angle = Math.asin(-y2/r)
56 | x1 = Math.abs(Math.cos(angle)*r)*(x2 < 0 ? -1 : 1)
57 | data = [[ x1, y2], [x2, y2]]
58 | } else {
59 | const angle = Math.acos(x2/r)
60 | y1 = Math.abs(Math.sin(angle)*r)*(y2 < 0 ? -1 : 1)
61 | data = [[ x2, y1], [x2, y2]]
62 | }
63 | } else {
64 | data = [[x1, y1], [xe , ye], [x2, y2]]
65 | }
66 |
67 | return { components: [lineBuilder({ data, className: "connector"})]}
68 | }
69 |
--------------------------------------------------------------------------------
/src/Connector/type-line.js:
--------------------------------------------------------------------------------
1 | import { lineBuilder } from '../Builder'
2 |
3 | export const lineSetup = ({ type, subjectType }) => {
4 | let annotation = type.annotation
5 | let offset = annotation.position
6 |
7 | let x1 = annotation.x - offset.x,
8 | x2 = x1 + annotation.dx,
9 | y1 = annotation.y - offset.y,
10 | y2 = y1 + annotation.dy
11 |
12 |
13 | const subjectData = annotation.subject
14 |
15 | if (subjectType === "circle" && (subjectData.outerRadius || subjectData.radius)) {
16 | const h = Math.sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2))
17 | const angle = Math.asin(-y2/h)
18 | const r = subjectData.outerRadius || subjectData.radius + (subjectData.radiusPadding || 0)
19 |
20 | x1 = Math.abs(Math.cos(angle)*r)*(x2 < 0 ? -1 : 1)
21 | y1 = Math.abs(Math.sin(angle)*r)*(y2 < 0 ? -1 : 1)
22 |
23 | }
24 |
25 | if (subjectType === "rect") {
26 | const { width, height } = subjectData
27 |
28 | if (width > 0 && annotation.dx > 0 || width < 0 && annotation.dx < 0) {
29 | if (Math.abs(width) > Math.abs(annotation.dx)) x1 = width/2
30 | else x1 = width
31 | }
32 | if (height > 0 && annotation.dy > 0 || height < 0 && annotation.dy < 0) {
33 | if (Math.abs(height) > Math.abs(annotation.dy)) y1 = height/2
34 | else y1 = height
35 | }
36 | if (x1 === width/2 && y1 === height/2) { x1 = x2; y1 = y2;}
37 | }
38 |
39 |
40 | return [[x1, y1], [x2, y2]]
41 | }
42 |
43 | export default (connectorData) => {
44 | const data = lineSetup(connectorData)
45 | return { components: [lineBuilder({ data, className : "connector" })]}
46 | }
--------------------------------------------------------------------------------
/src/Handles.js:
--------------------------------------------------------------------------------
1 | import { select } from "d3-selection"
2 | import { drag } from "d3-drag"
3 |
4 | export const pointHandle = ({ cx = 0, cy = 0 }) => {
5 | return { move: { x: cx, y: cy } }
6 | }
7 |
8 | export const circleHandles = ({ cx = 0, cy = 0, r1, r2, padding }) => {
9 | const h = { move: { x: cx, y: cy } }
10 |
11 | if (r1 !== undefined) {
12 | h.r1 = { x: cx + r1 / Math.sqrt(2), y: cy + r1 / Math.sqrt(2) }
13 | }
14 |
15 | if (r2 !== undefined) {
16 | h.r2 = { x: cx + r2 / Math.sqrt(2), y: cy + r2 / Math.sqrt(2) }
17 | }
18 |
19 | if (padding !== undefined) {
20 | h.padding = { x: cx + r1 + padding, y: cy }
21 | }
22 |
23 | return h
24 | }
25 |
26 | export const rectHandles = ({
27 | x1 = 0,
28 | y1 = 0,
29 | x2 = x1,
30 | y2 = y1,
31 | width,
32 | height
33 | }) => {
34 | const w = width || Math.abs(x2 - x1)
35 | const h = height || Math.abs(y2 - y1)
36 |
37 | return {
38 | move: {
39 | x: Math.min(x1, x2) + w / 2,
40 | y: Math.min(y1, y2) - 10
41 | },
42 | width: {
43 | x: Math.max(x1, x2),
44 | y: Math.min(y1, y2) + h / 2
45 | },
46 | height: {
47 | x: Math.min(x1, x2) + w / 2,
48 | y: Math.max(y1, y2)
49 | }
50 | }
51 | }
52 |
53 | export const lineHandles = ({ x1, y1, x2, y2, x, y }) => {
54 | const minY = Math.min(y1, y2)
55 | const minX = Math.min(x1, x2)
56 |
57 | const height = Math.abs(y2 - y1)
58 | const width = Math.abs(x2 - x1)
59 |
60 | return {
61 | move: {
62 | x: x || minX + width / 2,
63 | y: y || minY + height / 2
64 | }
65 | }
66 | }
67 |
68 | //arc handles
69 | export const addHandles = ({ group, handles, r = 10 }) => {
70 | //give it a group and x,y to draw handles
71 | //then give it instructions on what the handles change
72 | const h = group.selectAll("circle.handle").data(handles)
73 |
74 | h
75 | .enter()
76 | .append("circle")
77 | .attr("class", "handle")
78 | .attr("fill", "grey")
79 | .attr("fill-opacity", 0.1)
80 | .attr("cursor", "move")
81 | .attr("stroke-dasharray", 5)
82 | .attr("stroke", "grey")
83 | .call(
84 | drag()
85 | .container(select("g.annotations").node())
86 | .on("start", d => d.start && d.start(d))
87 | .on("drag", d => d.drag && d.drag(d))
88 | .on("end", d => d.end && d.end(d))
89 | )
90 |
91 | group
92 | .selectAll("circle.handle")
93 | .attr("cx", d => d.x)
94 | .attr("cy", d => d.y)
95 | .attr("r", d => d.r || r)
96 | .attr("class", d => `handle ${d.className || ""}`)
97 |
98 | h.exit().remove()
99 | }
100 |
--------------------------------------------------------------------------------
/src/Note/alignment.js:
--------------------------------------------------------------------------------
1 |
2 | export const leftRightDynamic = (align, y) => {
3 | if (align === "dynamic" || align === "left" || align === "right") {
4 | if (y < 0) { align = "top" }
5 | else { align = "bottom" }
6 | }
7 | return align
8 | }
9 |
10 | export const topBottomDynamic = (align, x) => {
11 | if (align === "dynamic" || align === "top" || align === "bottom") {
12 | if (x < 0) { align = "right" }
13 | else { align = "left" }
14 | }
15 | return align
16 | }
17 |
18 | const orientationTopBottom = ["topBottom", "top", "bottom"]
19 | const orientationLeftRight = ["leftRight", "left", "right"]
20 |
21 | export default ({ padding=0, bbox={x:0, y:0, width:0, height:0}, align, orientation, offset={x:0, y:0} }) => {
22 | let x = -bbox.x
23 | let y = 0//-bbox.y
24 | if ( orientationTopBottom.indexOf(orientation) !== -1 ) {
25 | align = topBottomDynamic(align, offset.x)
26 | if (offset.y < 0 && orientation === "topBottom" || orientation === "top") {
27 | y -= bbox.height + padding
28 | } else {
29 | y += padding
30 | }
31 |
32 | if ( align === "middle" ) {
33 | x -= bbox.width/2
34 | } else if (align === "right" ) {
35 | x -= bbox.width
36 | }
37 |
38 | } else if ( orientationLeftRight.indexOf(orientation) !== -1 ) {
39 | align = leftRightDynamic(align, offset.y)
40 | if (offset.x < 0 && orientation === "leftRight" || orientation === "left") {
41 | x -= bbox.width + padding
42 | } else {
43 | x += padding
44 | }
45 |
46 | if ( align === "middle" ) {
47 | y -= bbox.height/2
48 | } else if (align === "top" ) {
49 | y -= bbox.height
50 | }
51 | }
52 |
53 | return { x, y }
54 | }
--------------------------------------------------------------------------------
/src/Note/lineType-horizontal.js:
--------------------------------------------------------------------------------
1 | import { lineBuilder } from '../Builder'
2 | import { topBottomDynamic } from './alignment'
3 |
4 | export default ({ align, x=0, y=0, offset, bbox }) => {
5 | align = topBottomDynamic(align, offset.x)
6 |
7 | if (align === "right") { x -= bbox.width }
8 | else if (align === "middle") { x -= bbox.width/2 }
9 |
10 | const data = [[x, y], [x + bbox.width, y]]
11 | return { components: [lineBuilder({ data, className : "note-line" })] }
12 | }
--------------------------------------------------------------------------------
/src/Note/lineType-vertical.js:
--------------------------------------------------------------------------------
1 | import { lineBuilder } from '../Builder'
2 | import { leftRightDynamic } from './alignment'
3 |
4 | export default ({ align, x=0, y=0, bbox, offset }) => {
5 | align = leftRightDynamic(align, offset.y)
6 |
7 | if (align === "top") { y -= bbox.height }
8 | else if (align === "middle") { y -= bbox.height/2 }
9 |
10 | const data = [[x, y], [x, y + bbox.height]]
11 | return { components: [lineBuilder({ data, className : "note-line" })] }
12 | }
--------------------------------------------------------------------------------
/src/Note/type-text.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export default ({ noteData }) => {
4 |
5 | let components = []
6 |
7 | if (noteData.text) {
8 | text = {
9 | type: "text",
10 | className: "noteText",
11 | attrs: {
12 | text: noteData.text,
13 | dy: "1.1em"
14 | }
15 | }
16 | }
17 |
18 | if (noteData.title) {
19 | title = {
20 | type: "text",
21 | className: "noteText",
22 | attrs: {
23 | text: noteData.text,
24 | dy: "1em"
25 | }
26 | }
27 |
28 | }
29 |
30 | return { components }
31 | }
--------------------------------------------------------------------------------
/src/Subject/badge.js:
--------------------------------------------------------------------------------
1 | import { lineBuilder, arcBuilder } from "../Builder"
2 | import { event } from "d3-selection"
3 |
4 | export default ({ subjectData = {}, type = {} }, annotation = {}) => {
5 | const typeSettings = type.typeSettings && type.typeSettings.subject
6 |
7 | if (!subjectData.radius) {
8 | if (typeSettings && typeSettings.radius) {
9 | subjectData.radius = typeSettings.radius
10 | } else {
11 | subjectData.radius = 14
12 | }
13 | }
14 | if (!subjectData.x) {
15 | if (typeSettings && typeSettings.x) {
16 | subjectData.x = typeSettings.x
17 | }
18 | }
19 | if (!subjectData.y) {
20 | if (typeSettings && typeSettings.y) {
21 | subjectData.y = typeSettings.y
22 | }
23 | }
24 |
25 | let handles = []
26 | const components = []
27 | const radius = subjectData.radius
28 | const innerRadius = radius * 0.7
29 | let x = 0
30 | let y = 0
31 |
32 | const notCornerOffset = Math.sqrt(2) * radius
33 | const placement = {
34 | xleftcorner: -radius,
35 | xrightcorner: radius,
36 | ytopcorner: -radius,
37 | ybottomcorner: radius,
38 | xleft: -notCornerOffset,
39 | xright: notCornerOffset,
40 | ytop: -notCornerOffset,
41 | ybottom: notCornerOffset
42 | }
43 |
44 | if (subjectData.x && !subjectData.y) {
45 | x = placement[`x${subjectData.x}`]
46 | } else if (subjectData.y && !subjectData.x) {
47 | y = placement[`y${subjectData.y}`]
48 | } else if (subjectData.x && subjectData.y) {
49 | x = placement[`x${subjectData.x}corner`]
50 | y = placement[`y${subjectData.y}corner`]
51 | }
52 |
53 | const transform = `translate(${x}, ${y})`
54 | const circlebg = arcBuilder({ className: "subject", data: { radius } })
55 | circlebg.attrs.transform = transform
56 | circlebg.attrs.fill = annotation.color
57 | circlebg.attrs["stroke-linecap"] = "round"
58 | circlebg.attrs["stroke-width"] = "3px"
59 |
60 | const circle = arcBuilder({
61 | className: "subject-ring",
62 | data: { outerRadius: radius, innerRadius }
63 | })
64 |
65 | circle.attrs.transform = transform
66 | // circle.attrs.fill = annotation.color
67 | circle.attrs["stroke-width"] = "3px"
68 | circle.attrs.fill = "white"
69 |
70 | let pointer
71 | if (x && y || !x && !y) {
72 | pointer = lineBuilder({
73 | className: "subject-pointer",
74 | data: [[0, 0], [x || 0, 0], [0, y || 0], [0, 0]]
75 | })
76 | } else if (x || y) {
77 | const notCornerPointerXY = (v, sign = 1) =>
78 | v && v / Math.sqrt(2) / Math.sqrt(2) || sign * radius / Math.sqrt(2)
79 |
80 | pointer = lineBuilder({
81 | className: "subject-pointer",
82 | data: [
83 | [0, 0],
84 | [notCornerPointerXY(x), notCornerPointerXY(y)],
85 | [notCornerPointerXY(x, -1), notCornerPointerXY(y, -1)],
86 | [0, 0]
87 | ]
88 | })
89 | }
90 |
91 | if (pointer) {
92 | pointer.attrs.fill = annotation.color
93 | pointer.attrs["stroke-linecap"] = "round"
94 | pointer.attrs["stroke-width"] = "3px"
95 | components.push(pointer)
96 | }
97 |
98 | if (type.editMode) {
99 | const dragBadge = () => {
100 | subjectData.x =
101 | event.x < -radius * 2
102 | ? "left"
103 | : event.x > radius * 2 ? "right" : undefined
104 | subjectData.y =
105 | event.y < -radius * 2
106 | ? "top"
107 | : event.y > radius * 2 ? "bottom" : undefined
108 |
109 | type.redrawSubject()
110 | }
111 |
112 | const bHandles = { x: x * 2, y: y * 2, drag: dragBadge.bind(type) }
113 | if (!bHandles.x && !bHandles.y) {
114 | bHandles.y = -radius
115 | }
116 |
117 | handles = type.mapHandles([bHandles])
118 | }
119 |
120 | let text
121 | if (subjectData.text) {
122 | text = {
123 | type: "text",
124 | className: "badge-text",
125 | attrs: {
126 | fill: "white",
127 | stroke: "none",
128 | "font-size": ".7em",
129 | text: subjectData.text,
130 | "text-anchor": "middle",
131 | dy: ".25em",
132 | x,
133 | y
134 | }
135 | }
136 | }
137 |
138 | components.push(circlebg)
139 | components.push(circle)
140 | components.push(text)
141 |
142 | return { components, handles }
143 | }
144 |
--------------------------------------------------------------------------------
/src/Subject/circle.js:
--------------------------------------------------------------------------------
1 | import { circleHandles } from "../Handles"
2 | import { arcBuilder } from "../Builder"
3 | import { event } from "d3-selection"
4 |
5 | export default ({ subjectData, type }) => {
6 | if (!subjectData.radius && !subjectData.outerRadius) {
7 | subjectData.radius = 20
8 | }
9 |
10 | let handles = []
11 | const c = arcBuilder({ data: subjectData, className: "subject" })
12 | if (type.editMode) {
13 | const h = circleHandles({
14 | r1: c.data.outerRadius || c.data.radius,
15 | r2: c.data.innerRadius,
16 | padding: subjectData.radiusPadding
17 | })
18 |
19 | const updateRadius = attr => {
20 | const r = subjectData[attr] + event.dx * Math.sqrt(2)
21 | subjectData[attr] = r
22 | type.redrawSubject()
23 | type.redrawConnector()
24 | }
25 |
26 | const cHandles = [
27 | {
28 | ...h.r1,
29 | drag: updateRadius.bind(
30 | type,
31 | subjectData.outerRadius !== undefined ? "outerRadius" : "radius"
32 | )
33 | }
34 | ]
35 |
36 | if (subjectData.innerRadius) {
37 | cHandles.push({ ...h.r2, drag: updateRadius.bind(type, "innerRadius") })
38 | }
39 | handles = type.mapHandles(cHandles)
40 | }
41 |
42 | c.attrs["fill-opacity"] = 0
43 |
44 | return { components: [c], handles }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Subject/rect.js:
--------------------------------------------------------------------------------
1 | import { lineBuilder } from "../Builder"
2 | import { event } from "d3-selection"
3 |
4 | export default ({ subjectData, type }) => {
5 | if (!subjectData.width) {
6 | subjectData.width = 100
7 | }
8 | if (!subjectData.height) {
9 | subjectData.height = 100
10 | }
11 |
12 | let handles = []
13 | let { width, height } = subjectData
14 |
15 | const data = [[0, 0], [width, 0], [width, height], [0, height], [0, 0]]
16 | let rect = lineBuilder({ data, className: "subject" })
17 |
18 | if (type.editMode) {
19 | const updateWidth = () => {
20 | subjectData.width = event.x
21 | type.redrawSubject()
22 | type.redrawConnector()
23 | }
24 |
25 | const updateHeight = () => {
26 | subjectData.height = event.y
27 | type.redrawSubject()
28 | type.redrawConnector()
29 | }
30 |
31 | const rHandles = [
32 | { x: width, y: height / 2, drag: updateWidth.bind(type) },
33 | { x: width / 2, y: height, drag: updateHeight.bind(type) }
34 | ]
35 |
36 | handles = type.mapHandles(rHandles)
37 | }
38 | rect.attrs["fill-opacity"] = 0.1
39 | return { components: [rect], handles }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Subject/threshold.js:
--------------------------------------------------------------------------------
1 | import { lineBuilder } from '../Builder'
2 |
3 |
4 | export default ({ subjectData, type }) => {
5 | const offset = type.annotation.position
6 |
7 | let x1 = (subjectData.x1 !== undefined ? subjectData.x1 : offset.x) - offset.x,
8 | x2 = (subjectData.x2 !== undefined ? subjectData.x2 : offset.x) - offset.x,
9 | y1 = (subjectData.y1 !== undefined ? subjectData.y1 : offset.y) - offset.y,
10 | y2 = (subjectData.y2 !== undefined ? subjectData.y2 : offset.y) - offset.y
11 |
12 | const data = [[x1, y1], [x2, y2]]
13 | return { components: [lineBuilder({ data, className : 'subject'})]}
14 | }
--------------------------------------------------------------------------------
/src/Types-d3.js:
--------------------------------------------------------------------------------
1 | import { select, event } from "d3-selection"
2 | import { drag } from "d3-drag"
3 | import { addHandles } from "./Handles"
4 |
5 | //Note options
6 | import noteAlignment from "./Note/alignment"
7 | import noteVertical from "./Note/lineType-vertical"
8 | import noteHorizontal from "./Note/lineType-horizontal"
9 |
10 | //Connector options
11 | import connectorLine from "./Connector/type-line"
12 | import connectorElbow from "./Connector/type-elbow"
13 | import connectorCurve from "./Connector/type-curve"
14 | import connectorArrow from "./Connector/end-arrow"
15 | import connectorDot from "./Connector/end-dot"
16 |
17 | //Subject options
18 | import subjectCircle from "./Subject/circle"
19 | import subjectRect from "./Subject/rect"
20 | import subjectThreshold from "./Subject/threshold"
21 | import subjectBadge from "./Subject/badge"
22 |
23 | export class Type {
24 | constructor({ a, annotation, editMode, dispatcher, notePadding, accessors }) {
25 | this.a = a
26 |
27 | this.note =
28 | annotation.disable.indexOf("note") === -1 && a.select("g.annotation-note")
29 | this.noteContent = this.note && a.select("g.annotation-note-content")
30 | this.connector =
31 | annotation.disable.indexOf("connector") === -1 &&
32 | a.select("g.annotation-connector")
33 | this.subject =
34 | annotation.disable.indexOf("subject") === -1 &&
35 | a.select("g.annotation-subject")
36 | this.dispatcher = dispatcher
37 |
38 | if (dispatcher) {
39 | const handler = addHandlers.bind(null, dispatcher, annotation)
40 | handler({ component: this.note, name: "note" })
41 | handler({ component: this.connector, name: "connector" })
42 | handler({ component: this.subject, name: "subject" })
43 | }
44 |
45 | this.annotation = annotation
46 | this.editMode = annotation.editMode || editMode
47 | this.notePadding = notePadding !== undefined ? notePadding : 3
48 | this.offsetCornerX = 0
49 | this.offsetCornerY = 0
50 |
51 | if (accessors && annotation.data) {
52 | this.init(accessors)
53 | }
54 | }
55 |
56 | init(accessors) {
57 | if (!this.annotation.x) {
58 | this.mapX(accessors)
59 | }
60 | if (!this.annotation.y) {
61 | this.mapY(accessors)
62 | }
63 | }
64 |
65 | mapY(accessors) {
66 | if (accessors.y) {
67 | this.annotation.y = accessors.y(this.annotation.data)
68 | }
69 | }
70 |
71 | mapX(accessors) {
72 | if (accessors.x) {
73 | this.annotation.x = accessors.x(this.annotation.data)
74 | }
75 | }
76 |
77 | updateEditMode() {
78 | this.a.selectAll("circle.handle").remove()
79 | }
80 |
81 | drawOnSVG(component, builders) {
82 | if (!Array.isArray(builders)) {
83 | builders = [builders]
84 | }
85 |
86 | builders
87 | .filter(b => b)
88 | .forEach(({ type, className, attrs, handles, classID }) => {
89 | if (type === "handle") {
90 | addHandles({ group: component, r: attrs && attrs.r, handles })
91 | } else {
92 | newWithClass(component, [this.annotation], type, className, classID)
93 | const el = component.select(`${type}.${classID || className}`)
94 | const addAttrs = Object.keys(attrs)
95 | const removeAttrs = []
96 |
97 | const currentAttrs = el.node().attributes
98 | for (let i = currentAttrs.length - 1; i >= 0; i--) {
99 | const name = currentAttrs[i].name
100 | if (addAttrs.indexOf(name) === -1 && name !== "class")
101 | removeAttrs.push(name)
102 | }
103 |
104 | addAttrs.forEach(attr => {
105 | if (attr === "text") {
106 | el.text(attrs[attr])
107 | } else {
108 | el.attr(attr, attrs[attr])
109 | }
110 | })
111 |
112 | removeAttrs.forEach(attr => el.attr(attr, null))
113 | }
114 | })
115 | }
116 |
117 | //TODO: how to extend this to a drawOnCanvas mode?
118 |
119 | getNoteBBox() {
120 | return bboxWithoutHandles(this.note, ".annotation-note-content text")
121 | }
122 | getNoteBBoxOffset() {
123 | const bbox = bboxWithoutHandles(this.note, ".annotation-note-content")
124 | const transform = this.noteContent.attr("transform").split(/\(|\,|\)/g)
125 | bbox.offsetCornerX = parseFloat(transform[1]) + this.annotation.dx
126 | bbox.offsetCornerY = parseFloat(transform[2]) + this.annotation.dy
127 | bbox.offsetX = this.annotation.dx
128 | bbox.offsetY = this.annotation.dy
129 | return bbox
130 | }
131 |
132 | drawSubject(context = {}) {
133 | const subjectData = this.annotation.subject
134 | const type = context.type
135 | const subjectParams = { type: this, subjectData }
136 |
137 | let subject = {}
138 | if (type === "circle") subject = subjectCircle(subjectParams)
139 | else if (type === "rect") subject = subjectRect(subjectParams)
140 | else if (type === "threshold") subject = subjectThreshold(subjectParams)
141 | else if (type === "badge")
142 | subject = subjectBadge(subjectParams, this.annotation)
143 |
144 | let { components = [], handles = [] } = subject
145 | components.forEach(c => {
146 | if (c && c.attrs && !c.attrs.stroke) {
147 | c.attrs.stroke = this.annotation.color
148 | }
149 | })
150 |
151 | if (this.editMode) {
152 | handles = handles.concat(
153 | this.mapHandles([{ drag: this.dragSubject.bind(this) }])
154 | )
155 | components.push({ type: "handle", handles })
156 | }
157 |
158 | return components
159 | }
160 |
161 | drawConnector(context = {}) {
162 | const connectorData = this.annotation.connector
163 | const type = connectorData.type || context.type
164 | const connectorParams = { type: this, connectorData }
165 | connectorParams.subjectType =
166 | this.typeSettings &&
167 | this.typeSettings.subject &&
168 | this.typeSettings.subject.type
169 |
170 | let connector = {}
171 | if (type === "curve") connector = connectorCurve(connectorParams)
172 | else if (type === "elbow") connector = connectorElbow(connectorParams)
173 | else connector = connectorLine(connectorParams)
174 | let { components = [], handles = [] } = connector
175 | const line = components[0]
176 | //TODO: genericize this into fill t/f stroke t/f
177 | if (line) {
178 | line.attrs.stroke = this.annotation.color
179 | line.attrs.fill = "none"
180 | }
181 | const endType = connectorData.end || context.end
182 | let end = {}
183 | if (endType === "arrow") {
184 | let s = line.data[1]
185 | const e = line.data[0]
186 | const distance = Math.sqrt(
187 | Math.pow(s[0] - e[0], 2) + Math.pow(s[1] - e[1], 2)
188 | )
189 | if (distance < 5 && line.data[2]) {
190 | s = line.data[2]
191 | }
192 | end = connectorArrow({
193 | annotation: this.annotation,
194 | start: s,
195 | end: e,
196 | scale: connectorData.endScale
197 | })
198 | } else if (endType === "dot") {
199 | end = connectorDot({ line, scale: connectorData.endScale })
200 | } else if (!endType || endType === "none") {
201 | this.connector && this.connector.select(".connector-end").remove()
202 | }
203 |
204 | if (end.components) {
205 | end.components.forEach(c => {
206 | c.attrs.fill = this.annotation.color
207 | c.attrs.stroke = this.annotation.color
208 | })
209 | components = components.concat(end.components)
210 | }
211 |
212 | if (this.editMode) {
213 | if (handles.length !== 0) components.push({ type: "handle", handles })
214 | }
215 | return components
216 | }
217 |
218 | drawNote(context = {}) {
219 | const noteData = this.annotation.note
220 | const align = noteData.align || context.align || "dynamic"
221 | const noteParams = {
222 | bbox: context.bbox,
223 | align,
224 | offset: this.annotation.offset
225 | }
226 | const lineType = noteData.lineType || context.lineType
227 | let note = {}
228 | if (lineType === "vertical") note = noteVertical(noteParams)
229 | else if (lineType === "horizontal") note = noteHorizontal(noteParams)
230 |
231 | let { components = [], handles = [] } = note
232 | components.forEach(c => {
233 | c.attrs.stroke = this.annotation.color
234 | })
235 |
236 | if (this.editMode) {
237 | handles = this.mapHandles([
238 | { x: 0, y: 0, drag: this.dragNote.bind(this) }
239 | ])
240 | components.push({ type: "handle", handles })
241 |
242 | const dragging = this.dragNote.bind(this),
243 | start = this.dragstarted.bind(this),
244 | end = this.dragended.bind(this)
245 | this.note.call(
246 | drag()
247 | .container(select("g.annotations").node())
248 | .on("start", d => start(d))
249 | .on("drag", d => dragging(d))
250 | .on("end", d => end(d))
251 | )
252 | } else {
253 | this.note.on("mousedown.drag", null)
254 | }
255 | return components
256 | }
257 |
258 | drawNoteContent(context) {
259 | const noteData = this.annotation.note
260 | const padding =
261 | noteData.padding !== undefined ? noteData.padding : this.notePadding
262 | let orientation = noteData.orientation || context.orientation || "topBottom"
263 | const lineType = noteData.lineType || context.lineType
264 | const align = noteData.align || context.align || "dynamic"
265 |
266 | if (lineType === "vertical") orientation = "leftRight"
267 | else if (lineType === "horizontal") orientation = "topBottom"
268 |
269 | const noteParams = {
270 | padding,
271 | bbox: context.bbox,
272 | offset: this.annotation.offset,
273 | orientation,
274 | align
275 | }
276 | const { x, y } = noteAlignment(noteParams)
277 | this.offsetCornerX = x + this.annotation.dx
278 | this.offsetCornerY = y + this.annotation.dy
279 | this.note && this.noteContent.attr("transform", `translate(${x}, ${y})`)
280 |
281 | return []
282 | }
283 |
284 | drawOnScreen(component, drawFunction) {
285 | return this.drawOnSVG(component, drawFunction)
286 | }
287 |
288 | redrawSubject() {
289 | this.subject && this.drawOnScreen(this.subject, this.drawSubject())
290 | }
291 |
292 | redrawConnector() {
293 | this.connector && this.drawOnScreen(this.connector, this.drawConnector())
294 | }
295 |
296 | redrawNote(bbox = this.getNoteBBox()) {
297 | this.noteContent &&
298 | this.drawOnScreen(this.noteContent, this.drawNoteContent({ bbox }))
299 | this.note && this.drawOnScreen(this.note, this.drawNote({ bbox }))
300 | }
301 |
302 | setPosition() {
303 | const position = this.annotation.position
304 | this.a.attr("transform", `translate(${position.x}, ${position.y})`)
305 | }
306 |
307 | clearComponents() {
308 | this.subject && this.subject.select("*").remove()
309 | this.connector && this.connector.select("*").remove()
310 | // this.note && this.note.select("*").remove()
311 | }
312 |
313 | setOffset() {
314 | if (this.note) {
315 | const offset = this.annotation.offset
316 | this.note.attr("transform", `translate(${offset.x}, ${offset.y})`)
317 | }
318 | }
319 |
320 | setPositionWithAccessors(accessors) {
321 | if (accessors && this.annotation.data) {
322 | this.mapX(accessors)
323 | this.mapY(accessors)
324 | }
325 | this.setPosition()
326 | }
327 |
328 | setClassName() {
329 | this.a.attr(
330 | "class",
331 | `annotation ${this.className && this.className()} ${
332 | this.editMode ? "editable" : ""
333 | } ${this.annotation.className || ""}`
334 | )
335 | }
336 |
337 | draw() {
338 | this.setClassName()
339 | this.setPosition()
340 | this.setOffset()
341 | this.redrawSubject()
342 | this.redrawConnector()
343 | this.redrawNote()
344 | }
345 |
346 | dragstarted() {
347 | event.sourceEvent.stopPropagation()
348 | this.dispatcher &&
349 | this.dispatcher.call("dragstart", this.a, this.annotation)
350 | this.a.classed("dragging", true)
351 | this.a.selectAll("circle.handle").style("pointer-events", "none")
352 | }
353 | dragended() {
354 | this.dispatcher && this.dispatcher.call("dragend", this.a, this.annotation)
355 | this.a.classed("dragging", false)
356 | this.a.selectAll("circle.handle").style("pointer-events", "all")
357 | }
358 |
359 | dragSubject() {
360 | const position = this.annotation.position
361 | position.x += event.dx
362 | position.y += event.dy
363 | this.annotation.position = position
364 | }
365 |
366 | dragNote() {
367 | const offset = this.annotation.offset
368 | offset.x += event.dx
369 | offset.y += event.dy
370 | this.annotation.offset = offset
371 | }
372 |
373 | mapHandles(handles) {
374 | return handles.map(h => ({
375 | ...h,
376 | start: this.dragstarted.bind(this),
377 | end: this.dragended.bind(this)
378 | }))
379 | }
380 | }
381 |
382 | export const customType = (initialType, typeSettings, init) => {
383 | return class customType extends initialType {
384 | constructor(settings) {
385 | super(settings)
386 | this.typeSettings = typeSettings
387 |
388 | if (typeSettings.disable) {
389 | typeSettings.disable.forEach(d => {
390 | this[d] && this[d].remove()
391 |
392 | this[d] = undefined
393 | if (d === "note") {
394 | this.noteContent = undefined
395 | }
396 | })
397 | }
398 | }
399 |
400 | static init(annotation, accessors) {
401 | super.init(annotation, accessors)
402 | if (init) {
403 | annotation = init(annotation, accessors)
404 | }
405 | return annotation
406 | }
407 |
408 | className() {
409 | return `${typeSettings.className ||
410 | super.className && super.className() ||
411 | ""}`
412 | }
413 |
414 | drawSubject(context) {
415 | this.typeSettings.subject = Object.assign(
416 | {},
417 | typeSettings.subject,
418 | this.typeSettings.subject
419 | )
420 | return super.drawSubject({ ...context, ...this.typeSettings.subject })
421 | }
422 |
423 | drawConnector(context) {
424 | this.typeSettings.connector = Object.assign(
425 | {},
426 | typeSettings.connector,
427 | this.typeSettings.connector
428 | )
429 | return super.drawConnector({
430 | ...context,
431 | ...typeSettings.connector,
432 | ...this.typeSettings.connector
433 | })
434 | }
435 |
436 | drawNote(context) {
437 | this.typeSettings.note = Object.assign(
438 | {},
439 | typeSettings.note,
440 | this.typeSettings.note
441 | )
442 | return super.drawNote({
443 | ...context,
444 | ...typeSettings.note,
445 | ...this.typeSettings.note
446 | })
447 | }
448 |
449 | drawNoteContent(context) {
450 | return super.drawNoteContent({
451 | ...context,
452 | ...typeSettings.note,
453 | ...this.typeSettings.note
454 | })
455 | }
456 | }
457 | }
458 |
459 | export class d3NoteText extends Type {
460 | constructor(params) {
461 | super(params)
462 | this.textWrap = params.textWrap || 120
463 | this.drawText()
464 | }
465 |
466 | updateTextWrap(textWrap) {
467 | this.textWrap = textWrap
468 | this.drawText()
469 | }
470 |
471 | //TODO: add update text functionality
472 |
473 | drawText() {
474 | if (this.note) {
475 | newWithClass(this.note, [this.annotation], "g", "annotation-note-content")
476 |
477 | const noteContent = this.note.select("g.annotation-note-content")
478 | newWithClass(noteContent, [this.annotation], "rect", "annotation-note-bg")
479 | newWithClass(
480 | noteContent,
481 | [this.annotation],
482 | "text",
483 | "annotation-note-label"
484 | )
485 | newWithClass(
486 | noteContent,
487 | [this.annotation],
488 | "text",
489 | "annotation-note-title"
490 | )
491 |
492 | let titleBBox = { height: 0 }
493 | const label = this.a.select("text.annotation-note-label")
494 | const wrapLength =
495 | this.annotation.note && this.annotation.note.wrap ||
496 | this.typeSettings &&
497 | this.typeSettings.note &&
498 | this.typeSettings.note.wrap ||
499 | this.textWrap
500 |
501 | const wrapSplitter =
502 | this.annotation.note && this.annotation.note.wrapSplitter ||
503 | this.typeSettings &&
504 | this.typeSettings.note &&
505 | this.typeSettings.note.wrapSplitter
506 |
507 | let bgPadding =
508 | this.annotation.note && this.annotation.note.bgPadding ||
509 | this.typeSettings &&
510 | this.typeSettings.note &&
511 | this.typeSettings.note.bgPadding
512 |
513 | let bgPaddingFinal = { top: 0, bottom: 0, left: 0, right: 0 }
514 | if (typeof bgPadding === "number") {
515 | bgPaddingFinal = {
516 | top: bgPadding,
517 | bottom: bgPadding,
518 | left: bgPadding,
519 | right: bgPadding
520 | }
521 | } else if (bgPadding && typeof bgPadding === "object") {
522 | bgPaddingFinal = Object.assign(bgPaddingFinal, bgPadding)
523 | }
524 |
525 | if (this.annotation.note.title) {
526 | const title = this.a.select("text.annotation-note-title")
527 | title.text(this.annotation.note.title)
528 | title.attr("fill", this.annotation.color)
529 | title.attr("font-weight", "bold")
530 | title.call(wrap, wrapLength, wrapSplitter)
531 | titleBBox = title.node().getBBox()
532 | }
533 |
534 | label.text(this.annotation.note.label).attr("dx", "0")
535 | label.call(wrap, wrapLength, wrapSplitter)
536 |
537 | label.attr("y", titleBBox.height * 1.1 || 0)
538 | label.attr("fill", this.annotation.color)
539 |
540 | const bbox = this.getNoteBBox()
541 |
542 | this.a
543 | .select("rect.annotation-note-bg")
544 | .attr("width", bbox.width + bgPaddingFinal.left + bgPaddingFinal.right)
545 | .attr(
546 | "height",
547 | bbox.height + bgPaddingFinal.top + bgPaddingFinal.bottom
548 | )
549 | .attr("x", bbox.x - bgPaddingFinal.left)
550 | .attr("y", -bgPaddingFinal.top)
551 | .attr("fill", "white")
552 | .attr("fill-opacity", 0)
553 | }
554 | }
555 | }
556 |
557 | export const d3Label = customType(d3NoteText, {
558 | className: "label",
559 | note: { align: "middle" }
560 | })
561 |
562 | export const d3Callout = customType(d3NoteText, {
563 | className: "callout",
564 | note: { lineType: "horizontal" }
565 | })
566 |
567 | export const d3CalloutElbow = customType(d3Callout, {
568 | className: "callout elbow",
569 | connector: { type: "elbow" }
570 | })
571 |
572 | export const d3CalloutCurve = customType(d3Callout, {
573 | className: "callout curve",
574 | connector: { type: "curve" }
575 | })
576 |
577 | export const d3Badge = customType(Type, {
578 | className: "badge",
579 | subject: { type: "badge" },
580 | disable: ["connector", "note"]
581 | })
582 |
583 | export const d3CalloutCircle = customType(d3NoteText, {
584 | className: "callout circle",
585 | subject: { type: "circle" },
586 | note: { lineType: "horizontal" },
587 | connector: { type: "elbow" }
588 | })
589 |
590 | export const d3CalloutRect = customType(d3NoteText, {
591 | className: "callout rect",
592 | subject: { type: "rect" },
593 | note: { lineType: "horizontal" },
594 | connector: { type: "elbow" }
595 | })
596 |
597 | class ThresholdMap extends d3Callout {
598 | mapY(accessors) {
599 | super.mapY(accessors)
600 | const a = this.annotation
601 | if ((a.subject.x1 || a.subject.x2) && a.data && accessors.y) {
602 | a.y = accessors.y(a.data)
603 | }
604 | if ((a.subject.x1 || a.subject.x2) && !a.x) {
605 | a.x = a.subject.x1 || a.subject.x2
606 | }
607 | }
608 |
609 | mapX(accessors) {
610 | super.mapX(accessors)
611 | const a = this.annotation
612 | if ((a.subject.y1 || a.subject.y2) && a.data && accessors.x) {
613 | a.x = accessors.x(a.data)
614 | }
615 | if ((a.subject.y1 || a.subject.y2) && !a.y) {
616 | a.y = a.subject.y1 || a.subject.y2
617 | }
618 | }
619 | }
620 |
621 | export const d3XYThreshold = customType(ThresholdMap, {
622 | className: "callout xythreshold",
623 | subject: { type: "threshold" }
624 | })
625 |
626 | export const newWithClass = (a, d, type, className, classID) => {
627 | const group = a.selectAll(`${type}.${classID || className}`).data(d)
628 | group
629 | .enter()
630 | .append(type)
631 | .merge(group)
632 | .attr("class", className)
633 |
634 | group.exit().remove()
635 | return a
636 | }
637 |
638 | const addHandlers = (dispatcher, annotation, { component, name }) => {
639 | if (component) {
640 | component
641 | .on("mouseover.annotations", () => {
642 | dispatcher.call(`${name}over`, component, annotation)
643 | })
644 | .on("mouseout.annotations", () =>
645 | dispatcher.call(`${name}out`, component, annotation)
646 | )
647 | .on("click.annotations", () =>
648 | dispatcher.call(`${name}click`, component, annotation)
649 | )
650 | }
651 | }
652 |
653 | //Text wrapping code adapted from Mike Bostock
654 | const wrap = (text, width, wrapSplitter, lineHeight = 1.2) => {
655 | text.each(function() {
656 | const text = select(this),
657 | words = text
658 | .text()
659 | .split(wrapSplitter || /[ \t\r\n]+/)
660 | .reverse()
661 | .filter(w => w !== "")
662 | let word,
663 | line = [],
664 | tspan = text
665 | .text(null)
666 | .append("tspan")
667 | .attr("x", 0)
668 | .attr("dy", 0.8 + "em")
669 |
670 | while (word = words.pop()) {
671 | line.push(word)
672 | tspan.text(line.join(" "))
673 | if (tspan.node().getComputedTextLength() > width && line.length > 1) {
674 | line.pop()
675 | tspan.text(line.join(" "))
676 | line = [word]
677 | tspan = text
678 | .append("tspan")
679 | .attr("x", 0)
680 | .attr("dy", lineHeight + "em")
681 | .text(word)
682 | }
683 | }
684 | })
685 | }
686 |
687 | const bboxWithoutHandles = (selection, selector = ":not(.handle)") => {
688 | if (!selection) {
689 | return { x: 0, y: 0, width: 0, height: 0 }
690 | }
691 |
692 | return selection
693 | .selectAll(selector)
694 | .nodes()
695 | .reduce(
696 | (p, c) => {
697 | const bbox = c.getBBox()
698 | p.x = Math.min(p.x, bbox.x)
699 | p.y = Math.min(p.y, bbox.y)
700 | p.width = Math.max(p.width, bbox.width)
701 |
702 | const yOffset = c && c.attributes && c.attributes.y
703 | p.height = Math.max(
704 | p.height,
705 | (yOffset && parseFloat(yOffset.value) || 0) + bbox.height
706 | )
707 | return p
708 | },
709 | { x: 0, y: 0, width: 0, height: 0 }
710 | )
711 | }
712 |
713 | export default {
714 | Type,
715 | d3Label,
716 | d3Callout,
717 | d3CalloutElbow,
718 | d3CalloutCurve,
719 | d3CalloutCircle,
720 | d3CalloutRect,
721 | d3XYThreshold,
722 | d3Badge,
723 | customType
724 | }
725 |
--------------------------------------------------------------------------------
/test/connector.test.js:
--------------------------------------------------------------------------------
1 | import endArrow from "../src/Connector/end-arrow"
2 |
3 | describe("Connector-end-arrow", function() {
4 | it("gives back points for an arrow", function() {
5 | const arrow = endArrow({
6 | annotation: {
7 | x: 331,
8 | y: 186,
9 | dx: 36,
10 | dy: -41,
11 | position: { x: 331, y: 186 }
12 | }
13 | })
14 |
15 | expect(arrow.components.length).toEqual(1)
16 |
17 | const { data } = arrow.components[0]
18 | expect(data).toEqual([
19 | [0, 0],
20 | [8.413665914342925, -5.404648543783771],
21 | [4.271165980593775, -9.041965558783025],
22 | [0, 0]
23 | ])
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/test/note.test.js:
--------------------------------------------------------------------------------
1 | import alignment from "../src/Note/alignment"
2 | import { assign } from "./utils"
3 |
4 | describe("Note-alignment", function() {
5 | const set = {
6 | bbox: {
7 | x: 0,
8 | y: 0,
9 | width: 100,
10 | height: 50
11 | },
12 | orientation: "top"
13 | }
14 | let a
15 |
16 | it("stays if orientation is top", function() {
17 | a = alignment(set)
18 | expect(a.x).toEqual(-0)
19 | expect(a.y).toEqual(-50)
20 |
21 | a = alignment(
22 | assign(set, { orientation: "topBottom", offset: { x: 0, y: -100 } })
23 | )
24 |
25 | expect(a.x).toEqual(-0)
26 | expect(a.y).toEqual(-50)
27 | })
28 |
29 | it("stays if orientation is left", function() {
30 | a = alignment(assign(set, { orientation: "left" }))
31 |
32 | expect(a.x).toEqual(-100)
33 | expect(a.y).toEqual(0)
34 | })
35 |
36 | it("honors alignment", function() {
37 | a = alignment(assign(set, { align: "right" }))
38 | expect(a.x).toEqual(-100)
39 | expect(a.y).toEqual(-50)
40 | })
41 |
42 | it("honors padding", function() {
43 | a = alignment(assign(set, { padding: 20 }))
44 |
45 | expect(a.x).toEqual(-0)
46 | expect(a.y).toEqual(-70)
47 |
48 | a = alignment(assign(set, { padding: 20, orientation: "left" }))
49 |
50 | expect(a.x).toEqual(-120)
51 | expect(a.y).toEqual(0)
52 | })
53 | })
54 |
--------------------------------------------------------------------------------
/test/subject.badge.test.js:
--------------------------------------------------------------------------------
1 | import badge from "../src/Subject/badge"
2 |
3 | describe("Subject badge", () => {
4 | it("renders", () => {
5 | badge({})
6 | })
7 |
8 | it("gives back the positioning", () => {
9 | const position = badge({})
10 | const badgeObject = {
11 | components: [
12 | {
13 | attrs: {
14 | d: "M0,0L0,0L0,0L0,0",
15 | fill: undefined,
16 | "stroke-linecap": "round",
17 | "stroke-width": "3px"
18 | },
19 | classID: undefined,
20 | className: "subject-pointer",
21 | data: [[0, 0], [0, 0], [0, 0], [0, 0]],
22 | type: "path"
23 | },
24 | {
25 | attrs: {
26 | d:
27 | "M8.572527594031472e-16,-14A14,14,0,1,1,-8.572527594031472e-16,14A14,14,0,1,1,8.572527594031472e-16,-14Z",
28 | fill: undefined,
29 | "stroke-linecap": "round",
30 | "stroke-width": "3px",
31 | transform: "translate(0, 0)"
32 | },
33 | classID: undefined,
34 | className: "subject",
35 | data: { radius: 14 },
36 | type: "path"
37 | },
38 | {
39 | attrs: {
40 | d:
41 | "M8.572527594031472e-16,-14A14,14,0,1,1,-8.572527594031472e-16,14A14,14,0,1,1,8.572527594031472e-16,-14M-1.8002307947466087e-15,-9.799999999999999A9.799999999999999,9.799999999999999,0,1,0,1.8002307947466087e-15,9.799999999999999A9.799999999999999,9.799999999999999,0,1,0,-1.8002307947466087e-15,-9.799999999999999Z",
42 | fill: "white",
43 | "stroke-width": "3px",
44 | transform: "translate(0, 0)"
45 | },
46 | classID: undefined,
47 | className: "subject-ring",
48 | data: { innerRadius: 9.799999999999999, outerRadius: 14 },
49 | type: "path"
50 | },
51 | undefined
52 | ],
53 | handles: []
54 | }
55 |
56 | expect(position).toEqual(badgeObject)
57 | })
58 |
59 | it("gives back the positioning, top", () => {
60 | //should abstract out subjectData and noteData
61 | const position = badge({ subjectData: { y: "top" } })
62 | const badgeObject = {
63 | components: [
64 | {
65 | attrs: {
66 | d:
67 | "M0,0L9.899494936611665,-9.899494936611664L-9.899494936611665,-9.899494936611664L0,0",
68 | fill: undefined,
69 | "stroke-linecap": "round",
70 | "stroke-width": "3px"
71 | },
72 | classID: undefined,
73 | className: "subject-pointer",
74 | data: [
75 | [0, 0],
76 | [9.899494936611665, -9.899494936611664],
77 | [-9.899494936611665, -9.899494936611664],
78 | [0, 0]
79 | ],
80 | type: "path"
81 | },
82 | {
83 | attrs: {
84 | d:
85 | "M8.572527594031472e-16,-14A14,14,0,1,1,-8.572527594031472e-16,14A14,14,0,1,1,8.572527594031472e-16,-14Z",
86 | fill: undefined,
87 | "stroke-linecap": "round",
88 | "stroke-width": "3px",
89 | transform: "translate(0, -19.79898987322333)"
90 | },
91 | classID: undefined,
92 | className: "subject",
93 | data: { radius: 14 },
94 | type: "path"
95 | },
96 | {
97 | attrs: {
98 | d:
99 | "M8.572527594031472e-16,-14A14,14,0,1,1,-8.572527594031472e-16,14A14,14,0,1,1,8.572527594031472e-16,-14M-1.8002307947466087e-15,-9.799999999999999A9.799999999999999,9.799999999999999,0,1,0,1.8002307947466087e-15,9.799999999999999A9.799999999999999,9.799999999999999,0,1,0,-1.8002307947466087e-15,-9.799999999999999Z",
100 | fill: "white",
101 | "stroke-width": "3px",
102 | transform: "translate(0, -19.79898987322333)"
103 | },
104 | classID: undefined,
105 | className: "subject-ring",
106 | data: { innerRadius: 9.799999999999999, outerRadius: 14 },
107 | type: "path"
108 | },
109 | undefined
110 | ],
111 | handles: []
112 | }
113 |
114 | expect(position).toEqual(badgeObject)
115 | })
116 |
117 | it("gives back the positioning, right", () => {
118 | //should abstract out subjectData and noteData
119 | const position = badge({ subjectData: { x: "right" } })
120 | const badgeObject = {
121 | components: [
122 | {
123 | attrs: {
124 | d:
125 | "M0,0L9.899494936611664,9.899494936611665L9.899494936611664,-9.899494936611665L0,0",
126 | fill: undefined,
127 | "stroke-linecap": "round",
128 | "stroke-width": "3px"
129 | },
130 | classID: undefined,
131 | className: "subject-pointer",
132 | data: [
133 | [0, 0],
134 | [9.899494936611664, 9.899494936611665],
135 | [9.899494936611664, -9.899494936611665],
136 | [0, 0]
137 | ],
138 | type: "path"
139 | },
140 | {
141 | attrs: {
142 | d:
143 | "M8.572527594031472e-16,-14A14,14,0,1,1,-8.572527594031472e-16,14A14,14,0,1,1,8.572527594031472e-16,-14Z",
144 | fill: undefined,
145 | "stroke-linecap": "round",
146 | "stroke-width": "3px",
147 | transform: "translate(19.79898987322333, 0)"
148 | },
149 | classID: undefined,
150 | className: "subject",
151 | data: { radius: 14 },
152 | type: "path"
153 | },
154 | {
155 | attrs: {
156 | d:
157 | "M8.572527594031472e-16,-14A14,14,0,1,1,-8.572527594031472e-16,14A14,14,0,1,1,8.572527594031472e-16,-14M-1.8002307947466087e-15,-9.799999999999999A9.799999999999999,9.799999999999999,0,1,0,1.8002307947466087e-15,9.799999999999999A9.799999999999999,9.799999999999999,0,1,0,-1.8002307947466087e-15,-9.799999999999999Z",
158 | fill: "white",
159 | "stroke-width": "3px",
160 | transform: "translate(19.79898987322333, 0)"
161 | },
162 | classID: undefined,
163 | className: "subject-ring",
164 | data: { innerRadius: 9.799999999999999, outerRadius: 14 },
165 | type: "path"
166 | },
167 | undefined
168 | ],
169 | handles: []
170 | }
171 |
172 | expect(position).toEqual(badgeObject)
173 | })
174 |
175 | it("gives back, top right ", () => {
176 | const position = badge({ subjectData: { x: "right", y: "top" } })
177 |
178 | const badgeObject = {
179 | components: [
180 | {
181 | attrs: {
182 | d: "M0,0L14,0L0,-14L0,0",
183 | fill: undefined,
184 | "stroke-linecap": "round",
185 | "stroke-width": "3px"
186 | },
187 | classID: undefined,
188 | className: "subject-pointer",
189 | data: [[0, 0], [14, 0], [0, -14], [0, 0]],
190 | type: "path"
191 | },
192 | {
193 | attrs: {
194 | d:
195 | "M8.572527594031472e-16,-14A14,14,0,1,1,-8.572527594031472e-16,14A14,14,0,1,1,8.572527594031472e-16,-14Z",
196 | fill: undefined,
197 | "stroke-linecap": "round",
198 | "stroke-width": "3px",
199 | transform: "translate(14, -14)"
200 | },
201 | classID: undefined,
202 | className: "subject",
203 | data: { radius: 14 },
204 | type: "path"
205 | },
206 | {
207 | attrs: {
208 | d:
209 | "M8.572527594031472e-16,-14A14,14,0,1,1,-8.572527594031472e-16,14A14,14,0,1,1,8.572527594031472e-16,-14M-1.8002307947466087e-15,-9.799999999999999A9.799999999999999,9.799999999999999,0,1,0,1.8002307947466087e-15,9.799999999999999A9.799999999999999,9.799999999999999,0,1,0,-1.8002307947466087e-15,-9.799999999999999Z",
210 | fill: "white",
211 | "stroke-width": "3px",
212 | transform: "translate(14, -14)"
213 | },
214 | classID: undefined,
215 | className: "subject-ring",
216 | data: { innerRadius: 9.799999999999999, outerRadius: 14 },
217 | type: "path"
218 | },
219 | undefined
220 | ],
221 | handles: []
222 | }
223 |
224 | expect(position).toEqual(badgeObject)
225 | })
226 | it("gives back, bottom right ", () => {
227 | const position = badge({ subjectData: { x: "right", y: "bottom" } })
228 |
229 | const badgeObject = {
230 | components: [
231 | {
232 | attrs: {
233 | d: "M0,0L14,0L0,14L0,0",
234 | fill: undefined,
235 | "stroke-linecap": "round",
236 | "stroke-width": "3px"
237 | },
238 | classID: undefined,
239 | className: "subject-pointer",
240 | data: [[0, 0], [14, 0], [0, 14], [0, 0]],
241 | type: "path"
242 | },
243 | {
244 | attrs: {
245 | d:
246 | "M8.572527594031472e-16,-14A14,14,0,1,1,-8.572527594031472e-16,14A14,14,0,1,1,8.572527594031472e-16,-14Z",
247 | fill: undefined,
248 | "stroke-linecap": "round",
249 | "stroke-width": "3px",
250 | transform: "translate(14, 14)"
251 | },
252 | classID: undefined,
253 | className: "subject",
254 | data: { radius: 14 },
255 | type: "path"
256 | },
257 | {
258 | attrs: {
259 | d:
260 | "M8.572527594031472e-16,-14A14,14,0,1,1,-8.572527594031472e-16,14A14,14,0,1,1,8.572527594031472e-16,-14M-1.8002307947466087e-15,-9.799999999999999A9.799999999999999,9.799999999999999,0,1,0,1.8002307947466087e-15,9.799999999999999A9.799999999999999,9.799999999999999,0,1,0,-1.8002307947466087e-15,-9.799999999999999Z",
261 | fill: "white",
262 | "stroke-width": "3px",
263 | transform: "translate(14, 14)"
264 | },
265 | classID: undefined,
266 | className: "subject-ring",
267 | data: { innerRadius: 9.799999999999999, outerRadius: 14 },
268 | type: "path"
269 | },
270 | undefined
271 | ],
272 | handles: []
273 | }
274 |
275 | expect(position).toEqual(badgeObject)
276 | })
277 | })
278 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | export const assign = (base, additional) => {
2 | return Object.assign({}, base, additional)
3 | }
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "typeRoots": ["./types"],
4 | "allowJs": true
5 | },
6 | "include": ["types/*"]
7 | }
--------------------------------------------------------------------------------
/types/d3-svg-annotation.d.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from 'd3-dispatch';
2 | import { BaseType, Selection } from 'd3-selection';
3 |
4 | type Accessors = {
5 | [k: string]: () => number
6 | };
7 |
8 | // TODO figure out what these actually are.
9 | type Components = any[];
10 |
11 | type Orientation = 'topBottom' | 'leftRight' | 'fixed';
12 | type Align = 'dynamic' | 'left' | 'right' | 'top' | 'bottom' | 'middle';
13 | type LineType = 'horizontal' | 'vertical' | 'none';
14 |
15 | type BBox = {
16 | x: number,
17 | y: number,
18 | width: number,
19 | height: number
20 | };
21 |
22 | export default class Annotation {
23 |
24 | className: string;
25 | x: number;
26 | y: number;
27 | dx: number;
28 | dy: number;
29 | offset: {
30 | x: number;
31 | y: number;
32 | };
33 | position: {
34 | x: number;
35 | y: number;
36 | };
37 | readonly translation: {
38 | x: number;
39 | y: number;
40 | };
41 | readonly json: {
42 | x: number;
43 | y: number;
44 | dx: number;
45 | dy: number;
46 | };
47 |
48 | constructor({ x, y, dy, dx, data, type, subject, connector, note, disable, id, className }: {
49 | x?: number;
50 | y?: number;
51 | dy?: number;
52 | dx?: number;
53 | data: any;
54 | type: any;
55 | subject: any;
56 | connector: any;
57 | note: any;
58 | disable: any;
59 | id: any;
60 | className: string;
61 | });
62 |
63 | annotations(anotations: any[]): Annotation;
64 | accessors(accessors: { x?: (datum: T) => any, y?: (datum: T) => any }): Annotation;
65 | accessorsInverse(accessors: any): Annotation;
66 | editMode(editMode: boolean): Annotation;
67 | notePadding(padding: number): Annotation;
68 | type(type: any): Annotation;
69 | updatePosition(): void;
70 | updateOffset(): void;
71 | update(): void;
72 | updatedAccessors(): void;
73 | }
74 |
75 | export class Type {
76 | a: Selection;
77 | note: Selection | false;
78 | noteContent: Selection | false;
79 | connector: Selection | false;
80 | subject: Selection | false;
81 | annotation: Annotation;
82 | editMode: boolean;
83 | notePadding: number;
84 | offsetCornerX: number;
85 | offsetCornerY: number;
86 |
87 | constructor(args: {
88 | a: Selection,
89 | annotation: Annotation,
90 | editMode?: boolean,
91 | dispatcher?: Dispatch,
92 | notePadding?: number,
93 | accessors?: Accessors
94 | });
95 |
96 | init(accessors: Accessors): void;
97 |
98 | mapY(accessors: Accessors): void;
99 |
100 | mapX(accessors: Accessors): void;
101 |
102 | updateEditMode(): void;
103 |
104 | drawOnSVG(
105 | component: Selection,
106 | builders: Array<
107 | { type: string, className: string, attributes: {}, handles?: any[] }>):
108 | void;
109 |
110 | getNoteBBox(): BBox;
111 |
112 | getNoteBBoxOffset(): BBox;
113 |
114 | drawSubject(context?: { type: string }): Components;
115 |
116 | drawConnector(context?: { type: string, end: string }): Components;
117 |
118 | drawNote(context: {
119 | orientation: Orientation,
120 | align: Align,
121 | lineType: LineType,
122 | bbox: BBox,
123 | }): Components;
124 |
125 | drawNoteContent(context: {
126 | orientation: Orientation,
127 | lineType: LineType,
128 | align: Align,
129 | bbox: BBox,
130 | }): Array;
131 |
132 | drawOnScreen(component: any, drawFunction: () => any): void;
133 |
134 | redrawSubmit(): void;
135 |
136 | redrawConnector(bbox?: BBox): void;
137 |
138 | redrawNote(bbox?: BBox): void;
139 |
140 | setPosition(): void;
141 |
142 | setOffset(): void;
143 |
144 | setPositionWithAccessors(accessors?: Accessors): void;
145 |
146 | setClassName(): void;
147 |
148 | draw(): void;
149 |
150 | dragstarted(): void;
151 | drawended(): void;
152 | dragSubject(): void;
153 | dragNode(): void;
154 | mapHandler(handles: Array<{}>): { start: () => void, end: () => void };
155 | }
156 |
157 | // TODO define these
158 | export class annotationLabel extends Type { }
159 | export class annotationCallout extends Type { }
160 | export class annotationCalloutElbow extends Type { }
161 | export class annotationCalloutCurve extends Type { }
162 | export class annotationCalloutCircle extends Type { }
163 | export class annotationCalloutRect extends Type { }
164 | export class annotationXYThreshold extends Type { }
165 | export class annotationBadge extends Type { }
166 |
167 | interface CustomAnnotationSettings {
168 | disable?: string[];
169 | className?: string;
170 | subject?: any;
171 | connector?: any;
172 | note?: any;
173 | }
174 |
175 | export class annotationCustomType extends Type {
176 | constructor(type: typeof Type, settings: CustomAnnotationSettings);
177 | }
178 |
179 | export function annotation(): Annotation;
180 |
181 | export as namespace d3;
182 |
--------------------------------------------------------------------------------