├── .babelrc ├── .circleci └── config.yml ├── .eslintrc ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bundle.js ├── bundle.js.map ├── composable-annotation.gif ├── package.json ├── public ├── css │ └── flexboxgrid.min.css ├── favicon.ico ├── img │ ├── a-badge.png │ ├── a-bracket.png │ ├── a-callout.png │ ├── a-circle.png │ ├── a-curve.png │ ├── a-custom.png │ ├── a-elbow.png │ ├── a-label.png │ ├── a-rect.png │ ├── a-threshold.png │ ├── anatomy.png │ ├── arrow.png │ ├── basis.png │ ├── bottom.png │ ├── cardinal.png │ ├── classes.png │ ├── curve.png │ ├── dot.png │ ├── dots.png │ ├── dynamic.png │ ├── elbow.png │ ├── heart.png │ ├── horizontal.png │ ├── left.png │ ├── leftRight.png │ ├── line.png │ ├── linear.png │ ├── menu.png │ ├── middle.png │ ├── none.png │ ├── right.png │ ├── step.png │ ├── top.png │ ├── topBottom.png │ └── vertical.png ├── index.html └── prism.js ├── src ├── components │ ├── Annotation.js │ ├── Connector │ │ ├── Connector.js │ │ ├── ConnectorCurve.js │ │ ├── ConnectorElbow.js │ │ ├── ConnectorEnd.js │ │ ├── ConnectorEndArrow.js │ │ ├── ConnectorEndDot.js │ │ └── ConnectorLine.js │ ├── EditableAnnotation.js │ ├── Handle.js │ ├── Note │ │ ├── BracketNote.js │ │ ├── JSXNote.js │ │ └── Note.js │ ├── Subject │ │ ├── Subject.js │ │ ├── SubjectBadge.js │ │ ├── SubjectBracket.js │ │ ├── SubjectCircle.js │ │ ├── SubjectCustom.js │ │ ├── SubjectRect.js │ │ └── SubjectThreshold.js │ ├── Types │ │ ├── AnnotationBadge.js │ │ ├── AnnotationBracket.js │ │ ├── AnnotationCallout.js │ │ ├── AnnotationCalloutCircle.js │ │ ├── AnnotationCalloutCurve.js │ │ ├── AnnotationCalloutCustom.js │ │ ├── AnnotationCalloutElbow.js │ │ ├── AnnotationCalloutRect.js │ │ ├── AnnotationLabel.js │ │ ├── AnnotationXYThreshold.js │ │ └── Type.js │ ├── classnames.js │ ├── index.js │ └── web.js ├── docs │ ├── Examples.js │ ├── Icons.js │ ├── Sections.js │ ├── Types.js │ ├── data │ │ ├── stock.json │ │ └── yearNetwork.json │ ├── index.css │ ├── index.js │ ├── prism.css │ └── theme.js └── index.js ├── tests.md ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "production": { 4 | "presets": [ 5 | [ 6 | "@babel/preset-env", 7 | { 8 | "debug": false, 9 | "modules": "commonjs" 10 | } 11 | ], 12 | "@babel/preset-react" 13 | ], 14 | "plugins": [["@babel/plugin-proposal-class-properties"]] 15 | }, 16 | "development": { 17 | "presets": [ 18 | [ 19 | "@babel/preset-env", 20 | { 21 | "debug": false, 22 | "modules": "commonjs" 23 | } 24 | ], 25 | "@babel/preset-react" 26 | ], 27 | "plugins": [["@babel/plugin-proposal-class-properties"]] 28 | }, 29 | "test": { 30 | "presets": [ 31 | [ 32 | "@babel/preset-env", 33 | { 34 | "debug": false, 35 | "modules": "commonjs" 36 | } 37 | ], 38 | "@babel/preset-react" 39 | ], 40 | "plugins": [["@babel/plugin-proposal-class-properties"]] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:7.10 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: npm install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run tests! 37 | - run: npm run cypress 38 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": ["react"], 4 | 5 | "rules": { 6 | "array-bracket-spacing": 0, 7 | "curly": 0, 8 | "indent": [2, 2], 9 | "no-var": 2, 10 | "object-curly-spacing": 0, 11 | "quotes": 0, 12 | "eqeqeq": 2, 13 | "eol-last": 0, 14 | "prefer-const": 0, 15 | "no-cond-assign": 0, 16 | "space-before-blocks": 2, 17 | "no-unused-vars": 2, 18 | "no-loop-func": 0, 19 | "dot-notation": 2, 20 | "comma-dangle": 2, 21 | "no-unused-expressions": [ 22 | 2, 23 | { "allowShortCircuit": true, "allowTernary": true } 24 | ], 25 | "no-multiple-empty-lines": 0, 26 | "react/jsx-pascal-case": 0, 27 | "react/jsx-no-undef": 2, 28 | "react/jsx-uses-vars": 2, 29 | "react/jsx-wrap-multilines": 1, 30 | "no-sequences": 0 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | public/ga.js 4 | 5 | # dependencies 6 | node_modules 7 | 8 | # testing 9 | coverage 10 | 11 | # production 12 | dist 13 | build 14 | build.zip 15 | lib 16 | 17 | # misc 18 | .DS_Store 19 | .env 20 | npm-debug.log 21 | *.zip -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # [1.0.0](https://github.com/reactstrap/component-template/compare/0.2.0...v1.0.0) (2017-01-28) 3 | 4 | 5 | ### Features 6 | 7 | * **lint:** add eslint to test setup ([4756833](https://github.com/reactstrap/component-template/commit/4756833)) 8 | 9 | 10 | 11 | 12 | # 0.2.0 (2016-09-25) 13 | 14 | 15 | ### Features 16 | 17 | * **HelloWorld:** add hello world component ([a2da070](https://github.com/reactstrap/component-template/commit/a2da070)) 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /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 | # react-annotation 2 | 3 | Full documentation: [http://react-annotation.susielu.com](http://react-annotation.susielu.com) 4 | 5 | ![composable-annotation](composable-annotation.gif) 6 | 7 | ## Setup 8 | 9 | ### Using NPM 10 | 11 | You can add react-annotation as a node module by running 12 | 13 | ```bash 14 | npm i react-annotation -S 15 | ``` 16 | 17 | If you're new to using React, I suggest using [create-react-app](https://github.com/facebookincubator/create-react-app) to start your project 18 | 19 | ## Local Setup and Build 20 | 21 | This project uses [yarn](https://yarnpkg.com/lang/en/docs/install/#mac-stable), make sure that is set up prior to installing and building. To test out the library and run the docs locally, clone the repo and then run: 22 | 23 | ```js 24 | yarn install 25 | ``` 26 | 27 | Then run the start command to have a process watch for changes and build the docs site: 28 | 29 | ```js 30 | yarn start 31 | ``` 32 | 33 | If you want to make a production build of the docs run: 34 | 35 | ```js 36 | yarn build 37 | //this includes the yarn run prebuild command below 38 | ``` 39 | 40 | If you want to make a production build of just the components and the bundle.js that can be used as a codepen import run: 41 | 42 | ```js 43 | yarn prebuild 44 | ``` 45 | 46 | ## Feedback 47 | 48 | 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). 49 | 50 | ## Prior art 51 | 52 | - [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) 53 | 54 | - [Scatterplot with d3-annotate](https://bl.ocks.org/cmpolis/f9805a98b8a455aaccb56e5ee59964f8), by Chris Polis, example using [d3-annotate](https://github.com/cmpolis/d3-annotate) 55 | 56 | - [Rickshaw](http://code.shutterstock.com/rickshaw/) has an annotation tool 57 | 58 | - [Benn Stancil](https://modeanalytics.com/benn/reports/21ebfb6b6138) has an annotation example for a line chart 59 | 60 | - [Adam Pearce](http://blockbuilder.org/1wheel/68073eeba4d19c454a8c25fcd6e9e68a) has arc-arrows and [swoopy drag](http://1wheel.github.io/swoopy-drag/) 61 | 62 | - [Micah Stubbs](http://bl.ocks.org/micahstubbs/fa129089b7989975e96b166077f74de4#annotations.json) has a nice VR chart based on swoopy drag 63 | 64 | - [Scott Logic](http://blog.scottlogic.com/2014/08/26/two-line-components-for-d3-charts.html) evokes “line annotation” in a graph (different concept). 65 | 66 | - [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 67 | 68 | - [John Burn-Murdoch](https://bl.ocks.org/johnburnmurdoch/bcdb4e85c7523a2b0e64961f0d227154) example with adding/removing and repositioning annotations 69 | -------------------------------------------------------------------------------- /composable-annotation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/composable-annotation.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-annotation", 3 | "description": "React components for annotating SVG elements ", 4 | "version": "2.2.1", 5 | "homepage": ".", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/susielu/react-annotation.git" 9 | }, 10 | "license": "Apache-2.0", 11 | "bugs": { 12 | "url": "https://github.com/susielu/react-annotation/issues" 13 | }, 14 | "main": "lib/index.js", 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build && npm run webpack", 18 | "test": "react-scripts test --env=jsdom", 19 | "lint": "eslint src", 20 | "webpack": "NODE_ENV=production webpack", 21 | "prebuild": "babel src/components --out-dir lib --ignore test.js" 22 | }, 23 | "devDependencies": { 24 | "@babel/cli": "7.0.0-beta.56", 25 | "@babel/core": "7.0.0-beta.56", 26 | "@babel/plugin-proposal-class-properties": "7.0.0-beta.56", 27 | "@babel/preset-env": "7.0.0-beta.56", 28 | "@babel/preset-react": "7.0.0-beta.56", 29 | "babel-loader": "8.0.0-beta.0", 30 | "conventional-changelog-cli": "1.1.1", 31 | "conventional-recommended-bump": "0.3.0", 32 | "d3-array": "1.2.1", 33 | "d3-axis": "1.0.8", 34 | "d3-scale": "1.0.6", 35 | "d3-shape": "~1.0.4", 36 | "dentist": "1.0.3", 37 | "envify": "4.1.0", 38 | "enzyme": "2.9.1", 39 | "eslint": "5.3.0", 40 | "eslint-plugin-react": "^7.10.0", 41 | "history": "4.2.0", 42 | "material-ui": "^0.17.1", 43 | "react": "^15.5.0", 44 | "react-addons-css-transition-group": "15.3.2", 45 | "react-addons-test-utils": "^15.3.2", 46 | "react-addons-transition-group": "15.3.2", 47 | "react-dom": "^15.4.2", 48 | "react-markdown": "^3.3.0", 49 | "react-prism": "4.3.1", 50 | "react-router": "^4.0.0-beta.6", 51 | "react-router-dom": "4.0.0", 52 | "react-scripts": "0.8.5", 53 | "react-sticky": "git://github.com/susielu/react-sticky.git#23141c78f7e0501dc6a7b31b9e33db2e367b185f", 54 | "react-tap-event-plugin": "2.0.1", 55 | "react-test-renderer": "15.6.1", 56 | "uglify-js": "3.1.4", 57 | "uglifyify": "4.0.4", 58 | "webpack": "^4.16.4", 59 | "webpack-cli": "^3.1.0" 60 | }, 61 | "peerDependencies": { 62 | "react": "^15.0.0 || ^16.0.0", 63 | "react-dom": "^15.0.0 || ^16.0.0" 64 | }, 65 | "dependencies": { 66 | "prop-types": "15.6.2", 67 | "viz-annotation": "0.0.5" 68 | }, 69 | "files": [ 70 | "LICENSE", 71 | "README.md", 72 | "CHANGELOG.md", 73 | "lib" 74 | ], 75 | "keywords": [ 76 | "react", 77 | "component", 78 | "components", 79 | "annotation", 80 | "svg", 81 | "react-component" 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /public/css/flexboxgrid.min.css: -------------------------------------------------------------------------------- 1 | /* .container, 2 | .container-fluid { 3 | margin-right: auto; 4 | margin-left: auto; 5 | } */ 6 | /* .container-fluid { 7 | padding-right: 2rem; 8 | padding-left: 2rem; 9 | } */ 10 | .row { 11 | box-sizing: border-box; 12 | display: -webkit-box; 13 | display: -ms-flexbox; 14 | display: flex; 15 | -webkit-box-flex: 0; 16 | -ms-flex: 0 1 auto; 17 | flex: 0 1 auto; 18 | -webkit-box-orient: horizontal; 19 | -webkit-box-direction: normal; 20 | -ms-flex-direction: row; 21 | flex-direction: row; 22 | -ms-flex-wrap: wrap; 23 | flex-wrap: wrap; 24 | /* margin-right: -.5rem; 25 | margin-left: -.5rem; */ 26 | } 27 | .row.reverse { 28 | -webkit-box-orient: horizontal; 29 | -webkit-box-direction: reverse; 30 | -ms-flex-direction: row-reverse; 31 | flex-direction: row-reverse; 32 | } 33 | .col.reverse { 34 | -webkit-box-orient: vertical; 35 | -webkit-box-direction: reverse; 36 | -ms-flex-direction: column-reverse; 37 | flex-direction: column-reverse; 38 | } 39 | .col-xs, 40 | .col-xs-1, 41 | .col-xs-10, 42 | .col-xs-11, 43 | .col-xs-12, 44 | .col-xs-2, 45 | .col-xs-3, 46 | .col-xs-4, 47 | .col-xs-5, 48 | .col-xs-6, 49 | .col-xs-7, 50 | .col-xs-8, 51 | .col-xs-9, 52 | .col-xs-offset-0, 53 | .col-xs-offset-1, 54 | .col-xs-offset-10, 55 | .col-xs-offset-11, 56 | .col-xs-offset-12, 57 | .col-xs-offset-2, 58 | .col-xs-offset-3, 59 | .col-xs-offset-4, 60 | .col-xs-offset-5, 61 | .col-xs-offset-6, 62 | .col-xs-offset-7, 63 | .col-xs-offset-8, 64 | .col-xs-offset-9 { 65 | box-sizing: border-box; 66 | -webkit-box-flex: 0; 67 | -ms-flex: 0 0 auto; 68 | flex: 0 0 auto; 69 | padding-right: 0.5rem; 70 | padding-left: 0.5rem; 71 | } 72 | .col-xs { 73 | -webkit-box-flex: 1; 74 | -ms-flex-positive: 1; 75 | flex-grow: 1; 76 | -ms-flex-preferred-size: 0; 77 | flex-basis: 0; 78 | max-width: 100%; 79 | } 80 | .col-xs-1 { 81 | -ms-flex-preferred-size: 8.33333333%; 82 | flex-basis: 8.33333333%; 83 | max-width: 8.33333333%; 84 | } 85 | .col-xs-2 { 86 | -ms-flex-preferred-size: 16.66666667%; 87 | flex-basis: 16.66666667%; 88 | max-width: 16.66666667%; 89 | } 90 | .col-xs-3 { 91 | -ms-flex-preferred-size: 25%; 92 | flex-basis: 25%; 93 | max-width: 25%; 94 | } 95 | .col-xs-4 { 96 | -ms-flex-preferred-size: 33.33333333%; 97 | flex-basis: 33.33333333%; 98 | max-width: 33.33333333%; 99 | } 100 | .col-xs-5 { 101 | -ms-flex-preferred-size: 41.66666667%; 102 | flex-basis: 41.66666667%; 103 | max-width: 41.66666667%; 104 | } 105 | .col-xs-6 { 106 | -ms-flex-preferred-size: 50%; 107 | flex-basis: 50%; 108 | max-width: 50%; 109 | } 110 | .col-xs-7 { 111 | -ms-flex-preferred-size: 58.33333333%; 112 | flex-basis: 58.33333333%; 113 | max-width: 58.33333333%; 114 | } 115 | .col-xs-8 { 116 | -ms-flex-preferred-size: 66.66666667%; 117 | flex-basis: 66.66666667%; 118 | max-width: 66.66666667%; 119 | } 120 | .col-xs-9 { 121 | -ms-flex-preferred-size: 75%; 122 | flex-basis: 75%; 123 | max-width: 75%; 124 | } 125 | .col-xs-10 { 126 | -ms-flex-preferred-size: 83.33333333%; 127 | flex-basis: 83.33333333%; 128 | max-width: 83.33333333%; 129 | } 130 | .col-xs-11 { 131 | -ms-flex-preferred-size: 91.66666667%; 132 | flex-basis: 91.66666667%; 133 | max-width: 91.66666667%; 134 | } 135 | .col-xs-12 { 136 | -ms-flex-preferred-size: 100%; 137 | flex-basis: 100%; 138 | max-width: 100%; 139 | } 140 | .col-xs-offset-0 { 141 | margin-left: 0; 142 | } 143 | .col-xs-offset-1 { 144 | margin-left: 8.33333333%; 145 | } 146 | .col-xs-offset-2 { 147 | margin-left: 16.66666667%; 148 | } 149 | .col-xs-offset-3 { 150 | margin-left: 25%; 151 | } 152 | .col-xs-offset-4 { 153 | margin-left: 33.33333333%; 154 | } 155 | .col-xs-offset-5 { 156 | margin-left: 41.66666667%; 157 | } 158 | .col-xs-offset-6 { 159 | margin-left: 50%; 160 | } 161 | .col-xs-offset-7 { 162 | margin-left: 58.33333333%; 163 | } 164 | .col-xs-offset-8 { 165 | margin-left: 66.66666667%; 166 | } 167 | .col-xs-offset-9 { 168 | margin-left: 75%; 169 | } 170 | .col-xs-offset-10 { 171 | margin-left: 83.33333333%; 172 | } 173 | .col-xs-offset-11 { 174 | margin-left: 91.66666667%; 175 | } 176 | .start-xs { 177 | -webkit-box-pack: start; 178 | -ms-flex-pack: start; 179 | justify-content: flex-start; 180 | text-align: start; 181 | } 182 | .center-xs { 183 | -webkit-box-pack: center; 184 | -ms-flex-pack: center; 185 | justify-content: center; 186 | text-align: center; 187 | } 188 | .end-xs { 189 | -webkit-box-pack: end; 190 | -ms-flex-pack: end; 191 | justify-content: flex-end; 192 | text-align: end; 193 | } 194 | .top-xs { 195 | -webkit-box-align: start; 196 | -ms-flex-align: start; 197 | align-items: flex-start; 198 | } 199 | .middle-xs { 200 | -webkit-box-align: center; 201 | -ms-flex-align: center; 202 | align-items: center; 203 | } 204 | .bottom-xs { 205 | -webkit-box-align: end; 206 | -ms-flex-align: end; 207 | align-items: flex-end; 208 | } 209 | .around-xs { 210 | -ms-flex-pack: distribute; 211 | justify-content: space-around; 212 | } 213 | .between-xs { 214 | -webkit-box-pack: justify; 215 | -ms-flex-pack: justify; 216 | justify-content: space-between; 217 | } 218 | .first-xs { 219 | -webkit-box-ordinal-group: 0; 220 | -ms-flex-order: -1; 221 | order: -1; 222 | } 223 | .last-xs { 224 | -webkit-box-ordinal-group: 2; 225 | -ms-flex-order: 1; 226 | order: 1; 227 | } 228 | @media only screen and (min-width: 48em) { 229 | .container { 230 | width: 49rem; 231 | } 232 | .col-sm, 233 | .col-sm-1, 234 | .col-sm-10, 235 | .col-sm-11, 236 | .col-sm-12, 237 | .col-sm-2, 238 | .col-sm-3, 239 | .col-sm-4, 240 | .col-sm-5, 241 | .col-sm-6, 242 | .col-sm-7, 243 | .col-sm-8, 244 | .col-sm-9, 245 | .col-sm-offset-0, 246 | .col-sm-offset-1, 247 | .col-sm-offset-10, 248 | .col-sm-offset-11, 249 | .col-sm-offset-12, 250 | .col-sm-offset-2, 251 | .col-sm-offset-3, 252 | .col-sm-offset-4, 253 | .col-sm-offset-5, 254 | .col-sm-offset-6, 255 | .col-sm-offset-7, 256 | .col-sm-offset-8, 257 | .col-sm-offset-9 { 258 | box-sizing: border-box; 259 | -webkit-box-flex: 0; 260 | -ms-flex: 0 0 auto; 261 | flex: 0 0 auto; 262 | padding-right: 0.5rem; 263 | padding-left: 0.5rem; 264 | } 265 | .col-sm { 266 | -webkit-box-flex: 1; 267 | -ms-flex-positive: 1; 268 | flex-grow: 1; 269 | -ms-flex-preferred-size: 0; 270 | flex-basis: 0; 271 | max-width: 100%; 272 | } 273 | .col-sm-1 { 274 | -ms-flex-preferred-size: 8.33333333%; 275 | flex-basis: 8.33333333%; 276 | max-width: 8.33333333%; 277 | } 278 | .col-sm-2 { 279 | -ms-flex-preferred-size: 16.66666667%; 280 | flex-basis: 16.66666667%; 281 | max-width: 16.66666667%; 282 | } 283 | .col-sm-3 { 284 | -ms-flex-preferred-size: 25%; 285 | flex-basis: 25%; 286 | max-width: 25%; 287 | } 288 | .col-sm-4 { 289 | -ms-flex-preferred-size: 33.33333333%; 290 | flex-basis: 33.33333333%; 291 | max-width: 33.33333333%; 292 | } 293 | .col-sm-5 { 294 | -ms-flex-preferred-size: 41.66666667%; 295 | flex-basis: 41.66666667%; 296 | max-width: 41.66666667%; 297 | } 298 | .col-sm-6 { 299 | -ms-flex-preferred-size: 50%; 300 | flex-basis: 50%; 301 | max-width: 50%; 302 | } 303 | .col-sm-7 { 304 | -ms-flex-preferred-size: 58.33333333%; 305 | flex-basis: 58.33333333%; 306 | max-width: 58.33333333%; 307 | } 308 | .col-sm-8 { 309 | -ms-flex-preferred-size: 66.66666667%; 310 | flex-basis: 66.66666667%; 311 | max-width: 66.66666667%; 312 | } 313 | .col-sm-9 { 314 | -ms-flex-preferred-size: 75%; 315 | flex-basis: 75%; 316 | max-width: 75%; 317 | } 318 | .col-sm-10 { 319 | -ms-flex-preferred-size: 83.33333333%; 320 | flex-basis: 83.33333333%; 321 | max-width: 83.33333333%; 322 | } 323 | .col-sm-11 { 324 | -ms-flex-preferred-size: 91.66666667%; 325 | flex-basis: 91.66666667%; 326 | max-width: 91.66666667%; 327 | } 328 | .col-sm-12 { 329 | -ms-flex-preferred-size: 100%; 330 | flex-basis: 100%; 331 | max-width: 100%; 332 | } 333 | .col-sm-offset-0 { 334 | margin-left: 0; 335 | } 336 | .col-sm-offset-1 { 337 | margin-left: 8.33333333%; 338 | } 339 | .col-sm-offset-2 { 340 | margin-left: 16.66666667%; 341 | } 342 | .col-sm-offset-3 { 343 | margin-left: 25%; 344 | } 345 | .col-sm-offset-4 { 346 | margin-left: 33.33333333%; 347 | } 348 | .col-sm-offset-5 { 349 | margin-left: 41.66666667%; 350 | } 351 | .col-sm-offset-6 { 352 | margin-left: 50%; 353 | } 354 | .col-sm-offset-7 { 355 | margin-left: 58.33333333%; 356 | } 357 | .col-sm-offset-8 { 358 | margin-left: 66.66666667%; 359 | } 360 | .col-sm-offset-9 { 361 | margin-left: 75%; 362 | } 363 | .col-sm-offset-10 { 364 | margin-left: 83.33333333%; 365 | } 366 | .col-sm-offset-11 { 367 | margin-left: 91.66666667%; 368 | } 369 | .start-sm { 370 | -webkit-box-pack: start; 371 | -ms-flex-pack: start; 372 | justify-content: flex-start; 373 | text-align: start; 374 | } 375 | .center-sm { 376 | -webkit-box-pack: center; 377 | -ms-flex-pack: center; 378 | justify-content: center; 379 | text-align: center; 380 | } 381 | .end-sm { 382 | -webkit-box-pack: end; 383 | -ms-flex-pack: end; 384 | justify-content: flex-end; 385 | text-align: end; 386 | } 387 | .top-sm { 388 | -webkit-box-align: start; 389 | -ms-flex-align: start; 390 | align-items: flex-start; 391 | } 392 | .middle-sm { 393 | -webkit-box-align: center; 394 | -ms-flex-align: center; 395 | align-items: center; 396 | } 397 | .bottom-sm { 398 | -webkit-box-align: end; 399 | -ms-flex-align: end; 400 | align-items: flex-end; 401 | } 402 | .around-sm { 403 | -ms-flex-pack: distribute; 404 | justify-content: space-around; 405 | } 406 | .between-sm { 407 | -webkit-box-pack: justify; 408 | -ms-flex-pack: justify; 409 | justify-content: space-between; 410 | } 411 | .first-sm { 412 | -webkit-box-ordinal-group: 0; 413 | -ms-flex-order: -1; 414 | order: -1; 415 | } 416 | .last-sm { 417 | -webkit-box-ordinal-group: 2; 418 | -ms-flex-order: 1; 419 | order: 1; 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/favicon.ico -------------------------------------------------------------------------------- /public/img/a-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/a-badge.png -------------------------------------------------------------------------------- /public/img/a-bracket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/a-bracket.png -------------------------------------------------------------------------------- /public/img/a-callout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/a-callout.png -------------------------------------------------------------------------------- /public/img/a-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/a-circle.png -------------------------------------------------------------------------------- /public/img/a-curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/a-curve.png -------------------------------------------------------------------------------- /public/img/a-custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/a-custom.png -------------------------------------------------------------------------------- /public/img/a-elbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/a-elbow.png -------------------------------------------------------------------------------- /public/img/a-label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/a-label.png -------------------------------------------------------------------------------- /public/img/a-rect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/a-rect.png -------------------------------------------------------------------------------- /public/img/a-threshold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/a-threshold.png -------------------------------------------------------------------------------- /public/img/anatomy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/anatomy.png -------------------------------------------------------------------------------- /public/img/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/arrow.png -------------------------------------------------------------------------------- /public/img/basis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/basis.png -------------------------------------------------------------------------------- /public/img/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/bottom.png -------------------------------------------------------------------------------- /public/img/cardinal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/cardinal.png -------------------------------------------------------------------------------- /public/img/classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/classes.png -------------------------------------------------------------------------------- /public/img/curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/curve.png -------------------------------------------------------------------------------- /public/img/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/dot.png -------------------------------------------------------------------------------- /public/img/dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/dots.png -------------------------------------------------------------------------------- /public/img/dynamic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/dynamic.png -------------------------------------------------------------------------------- /public/img/elbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/elbow.png -------------------------------------------------------------------------------- /public/img/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/heart.png -------------------------------------------------------------------------------- /public/img/horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/horizontal.png -------------------------------------------------------------------------------- /public/img/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/left.png -------------------------------------------------------------------------------- /public/img/leftRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/leftRight.png -------------------------------------------------------------------------------- /public/img/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/line.png -------------------------------------------------------------------------------- /public/img/linear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/linear.png -------------------------------------------------------------------------------- /public/img/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/menu.png -------------------------------------------------------------------------------- /public/img/middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/middle.png -------------------------------------------------------------------------------- /public/img/none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/none.png -------------------------------------------------------------------------------- /public/img/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/right.png -------------------------------------------------------------------------------- /public/img/step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/step.png -------------------------------------------------------------------------------- /public/img/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/top.png -------------------------------------------------------------------------------- /public/img/topBottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/topBottom.png -------------------------------------------------------------------------------- /public/img/vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/susielu/react-annotation/7736da03fc9d8a99fbaf0ea8899d1b02ffeb18aa/public/img/vertical.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | React Annotation 23 | 24 | 25 | 26 |
27 | 28 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/prism.js: -------------------------------------------------------------------------------- 1 | /* http://0.0.0.0:8000/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+bash+css-extras+less+jsx+scss */ 2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(g?b[1].length:0),_=b.index+b[0].length,A=m,S=y,P=r.length;P>A&&_>S;++A)S+=(r[A].matchedStr||r[A]).length,w>=S&&(++m,y=S);if(r[m]instanceof a||r[A-1].greedy)continue;k=A-m,v=e.slice(y,S),b.index-=y}if(b){g&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),j=[m,k];x&&j.push(x);var N=new a(l,c?n.tokenize(b,c):b,d,b,h);j.push(N),O&&j.push(O),Array.prototype.splice.apply(r,j)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.matchedStr=a||null,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var i={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=(o?" ":"")+s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 3 | Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; 4 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); 5 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; 6 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*\*?|\/|~|\^|%|\.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; 7 | !function(e){var t={variable:[{pattern:/\$?\(\([\w\W]+?\)\)/,inside:{variable:[{pattern:/(^\$\(\([\w\W]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b-?(?:0x[\dA-Fa-f]+|\d*\.?\d+(?:[Ee]-?\d+)?)\b/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\([^)]+\)|`[^`]+`/,inside:{variable:/^\$\(|^`|\)$|`$/}},/\$(?:[a-z0-9_#\?\*!@]+|\{[^}]+\})/i]};e.languages.bash={shebang:{pattern:/^#!\s*\/bin\/bash|^#!\s*\/bin\/sh/,alias:"important"},comment:{pattern:/(^|[^"{\\])#.*/,lookbehind:!0},string:[{pattern:/((?:^|[^<])<<\s*)(?:"|')?(\w+?)(?:"|')?\s*\r?\n(?:[\s\S])*?\r?\n\2/g,lookbehind:!0,greedy:!0,inside:t},{pattern:/(["'])(?:\\\\|\\?[^\\])*?\1/g,greedy:!0,inside:t}],variable:t.variable,"function":{pattern:/(^|\s|;|\||&)(?:alias|apropos|apt-get|aptitude|aspell|awk|basename|bash|bc|bg|builtin|bzip2|cal|cat|cd|cfdisk|chgrp|chmod|chown|chroot|chkconfig|cksum|clear|cmp|comm|command|cp|cron|crontab|csplit|cut|date|dc|dd|ddrescue|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|enable|env|ethtool|eval|exec|expand|expect|export|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|getopts|git|grep|groupadd|groupdel|groupmod|groups|gzip|hash|head|help|hg|history|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|jobs|join|kill|killall|less|link|ln|locate|logname|logout|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|make|man|mkdir|mkfifo|mkisofs|mknod|more|most|mount|mtools|mtr|mv|mmv|nano|netstat|nice|nl|nohup|notify-send|npm|nslookup|open|op|passwd|paste|pathchk|ping|pkill|popd|pr|printcap|printenv|printf|ps|pushd|pv|pwd|quota|quotacheck|quotactl|ram|rar|rcp|read|readarray|readonly|reboot|rename|renice|remsync|rev|rm|rmdir|rsync|screen|scp|sdiff|sed|seq|service|sftp|shift|shopt|shutdown|sleep|slocate|sort|source|split|ssh|stat|strace|su|sudo|sum|suspend|sync|tail|tar|tee|test|time|timeout|times|touch|top|traceroute|trap|tr|tsort|tty|type|ulimit|umask|umount|unalias|uname|unexpand|uniq|units|unrar|unshar|uptime|useradd|userdel|usermod|users|uuencode|uudecode|v|vdir|vi|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yes|zip)(?=$|\s|;|\||&)/,lookbehind:!0},keyword:{pattern:/(^|\s|;|\||&)(?:let|:|\.|if|then|else|elif|fi|for|break|continue|while|in|case|function|select|do|done|until|echo|exit|return|set|declare)(?=$|\s|;|\||&)/,lookbehind:!0},"boolean":{pattern:/(^|\s|;|\||&)(?:true|false)(?=$|\s|;|\||&)/,lookbehind:!0},operator:/&&?|\|\|?|==?|!=?|<<>|<=?|>=?|=~/,punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];]/};var a=t.variable[1].inside;a["function"]=e.languages.bash["function"],a.keyword=e.languages.bash.keyword,a.boolean=e.languages.bash.boolean,a.operator=e.languages.bash.operator,a.punctuation=e.languages.bash.punctuation}(Prism); 8 | Prism.languages.css.selector={pattern:/[^\{\}\s][^\{\}]*(?=\s*\{)/,inside:{"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+(?:\(.*\))?/,"class":/\.[-:\.\w]+/,id:/#[-:\.\w]+/,attribute:/\[[^\]]+\]/}},Prism.languages.insertBefore("css","function",{hexcode:/#[\da-f]{3,6}/i,entity:/\\[\da-f]{1,8}/i,number:/[\d%\.]+/}); 9 | Prism.languages.less=Prism.languages.extend("css",{comment:[/\/\*[\w\W]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-]+?(?:\([^{}]+\)|[^(){};])*?(?=\s*\{)/i,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\([^{}]*\)|[^{};@])*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/i,punctuation:/[{}();:,]/,operator:/[+\-*\/]/}),Prism.languages.insertBefore("less","punctuation",{"function":Prism.languages.less.function}),Prism.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-]+.*?(?=[(;])/,lookbehind:!0,alias:"function"}}); 10 | !function(a){var e=a.util.clone(a.languages.javascript);a.languages.jsx=a.languages.extend("markup",e),a.languages.jsx.tag.pattern=/<\/?[\w\.:-]+\s*(?:\s+[\w\.:-]+(?:=(?:("|')(\\?[\w\W])*?\1|[^\s'">=]+|(\{[\w\W]*?\})))?\s*)*\/?>/i,a.languages.jsx.tag.inside["attr-value"].pattern=/=[^\{](?:('|")[\w\W]*?(\1)|[^\s>]+)/i;var s=a.util.clone(a.languages.jsx);delete s.punctuation,s=a.languages.insertBefore("jsx","operator",{punctuation:/=(?={)|[{}[\];(),.:]/},{jsx:s}),a.languages.insertBefore("inside","attr-value",{script:{pattern:/=(\{(?:\{[^}]*\}|[^}])+\})/i,inside:s,alias:"language-javascript"}},a.languages.jsx.tag)}(Prism); 11 | Prism.languages.scss=Prism.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\w\W]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)*url(?=\()/i,selector:{pattern:/(?=\S)[^@;\{\}\(\)]?([^@;\{\}\(\)]|&|#\{\$[-_\w]+\})+(?=\s*\{(\}|\s|[^\}]+(:|\{)[^\}]+))/m,inside:{parent:{pattern:/&/,alias:"important"},placeholder:/%[-_\w]+/,variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}}}),Prism.languages.insertBefore("scss","atrule",{keyword:[/@(?:if|else(?: if)?|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)/i,{pattern:/( +)(?:from|through)(?= )/,lookbehind:!0}]}),Prism.languages.scss.property={pattern:/(?:[\w-]|\$[-_\w]+|#\{\$[-_\w]+\})+(?=\s*:)/i,inside:{variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}},Prism.languages.insertBefore("scss","important",{variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}),Prism.languages.insertBefore("scss","function",{placeholder:{pattern:/%[-_\w]+/,alias:"selector"},statement:{pattern:/\B!(?:default|optional)\b/i,alias:"keyword"},"boolean":/\b(?:true|false)\b/,"null":/\bnull\b/,operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,lookbehind:!0}}),Prism.languages.scss.atrule.inside.rest=Prism.util.clone(Prism.languages.scss); 12 | -------------------------------------------------------------------------------- /src/components/Annotation.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import classnames from "./classnames" 3 | import PropTypes from "prop-types" 4 | 5 | export default class Annotation extends React.Component { 6 | render() { 7 | const { x, y, nx, ny, events } = this.props 8 | 9 | const cleanedProps = Object.assign({}, this.props) 10 | delete cleanedProps.children 11 | 12 | const cleanedWithoutEvents = Object.assign({}, cleanedProps) 13 | delete cleanedWithoutEvents.events 14 | 15 | if (nx !== undefined) cleanedProps.dx = nx - x 16 | if (ny !== undefined) cleanedProps.dy = ny - y 17 | 18 | const childrenWithProps = React.Children.toArray(this.props.children).map( 19 | child => 20 | React.cloneElement(child, { 21 | ...(typeof child.type === "string" 22 | ? cleanedWithoutEvents 23 | : cleanedProps), 24 | 25 | ...child.props 26 | }) 27 | ) 28 | const wrappedEvents = {} 29 | Object.keys(events).forEach(k => { 30 | wrappedEvents[k] = e => { 31 | events[k](this.props, this.state, e) 32 | } 33 | }) 34 | 35 | return ( 36 | 41 | {childrenWithProps} 42 | 43 | ) 44 | } 45 | } 46 | 47 | Annotation.defaultProps = { 48 | x: 0, 49 | y: 0, 50 | dx: 0, 51 | dy: 0, 52 | color: "grey", 53 | events: {} 54 | } 55 | 56 | Annotation.propTypes = { 57 | x: PropTypes.number, 58 | y: PropTypes.number, 59 | dx: PropTypes.number, 60 | dy: PropTypes.number, 61 | color: PropTypes.string, 62 | editMode: PropTypes.bool, 63 | events: PropTypes.object 64 | } 65 | -------------------------------------------------------------------------------- /src/components/Connector/Connector.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Handle from "../Handle" 3 | 4 | export default class Connector extends React.Component { 5 | getComponents() {} 6 | 7 | render() { 8 | const { color, dx, dy, customID, editMode } = this.props 9 | 10 | if (dx === 0 && dy === 0) { 11 | return 12 | } 13 | 14 | const d = this.getComponents(this.props) || [] 15 | const cleanedProps = Object.assign({}, this.props) 16 | delete cleanedProps.children 17 | 18 | const childrenWithProps = React.Children.map(this.props.children, child => 19 | React.cloneElement(child, { 20 | ...cleanedProps, 21 | ...child.props, 22 | scale: cleanedProps.endScale || child.props.endScale, 23 | lineData: d.components[0].data 24 | }) 25 | ) 26 | let handles 27 | 28 | if (editMode && d.handles && d.handles.length > 0) { 29 | handles = d.handles.map((h, i) => ( 30 | { 38 | this.props.dragConnectorSettings(e, d.handleFunction(h, data)) 39 | }} 40 | /> 41 | )) 42 | } 43 | 44 | return ( 45 | 46 | {d.components && 47 | d.components.map((c, i) => { 48 | const attrs = {} 49 | if (!c) return null 50 | Object.keys(c.attrs).forEach(k => { 51 | if (c.attrs[k] && k !== "text") { 52 | attrs[k.replace(/-([a-z])/g, g => g[1].toUpperCase())] = 53 | c.attrs[k] 54 | } 55 | }) 56 | return ( 57 | 65 | {c.attrs.text} 66 | 67 | ) 68 | })} 69 | {childrenWithProps} 70 | {handles} 71 | 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/Connector/ConnectorCurve.js: -------------------------------------------------------------------------------- 1 | import Curve from "viz-annotation/lib/Connector/type-curve" 2 | import Connector from "./Connector" 3 | 4 | export default class ConnectorCurve extends Connector { 5 | getComponents({ 6 | curve, 7 | points, 8 | x, 9 | y, 10 | dx, 11 | dy, 12 | radius, 13 | outerRadius, 14 | width, 15 | height, 16 | editMode 17 | }) { 18 | const components = Curve({ 19 | curve, 20 | points, 21 | x, 22 | y, 23 | dx, 24 | dy, 25 | radius, 26 | outerRadius, 27 | width, 28 | height, 29 | editMode 30 | }) 31 | 32 | components.handleKeys = { points: components.handles } 33 | components.handleFunction = (h, data) => { 34 | const p = components.points.slice(0) 35 | p[h.index] = [h.x + data.oDeltaX, h.y + data.oDeltaY] 36 | 37 | return { 38 | points: p 39 | } 40 | } 41 | 42 | return components 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Connector/ConnectorElbow.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import Elbow from "viz-annotation/lib/Connector/type-elbow" 5 | import Connector from "./Connector" 6 | 7 | export default class ConnectorElbow extends Connector { 8 | getComponents({ 9 | x, 10 | y, 11 | dy, 12 | dx, 13 | radius, 14 | radiusPadding, 15 | outerRadius, 16 | width, 17 | height 18 | }) { 19 | return Elbow({ 20 | x, 21 | y, 22 | dx, 23 | dy, 24 | radius, 25 | radiusPadding, 26 | outerRadius, 27 | width, 28 | height 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Connector/ConnectorEnd.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default class ConnectorEnd extends React.Component { 4 | getComponents() {} 5 | 6 | render() { 7 | const { color } = this.props 8 | const d = this.getComponents(this.props) || [] 9 | const c = d.components[0] 10 | 11 | return 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Connector/ConnectorEndArrow.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import EndArrow from "viz-annotation/lib/Connector/end-arrow" 5 | import ConnectorEnd from "./ConnectorEnd" 6 | import PropTypes from "prop-types" 7 | 8 | export default class ConnectorEndArrow extends ConnectorEnd { 9 | getComponents({ x, y, dy, dx, lineData, scale }) { 10 | let start = lineData[1] 11 | const end = lineData[0] 12 | const distance = Math.sqrt( 13 | Math.pow(start[0] - end[0], 2) + Math.pow(start[1] - end[1], 2) 14 | ) 15 | if (distance < 5 && lineData[2]) { 16 | start = lineData[2] 17 | } 18 | 19 | return EndArrow({ x, y, dx, dy, start, end, scale }) 20 | } 21 | } 22 | 23 | ConnectorEndArrow.propTypes = { 24 | x: PropTypes.number, 25 | y: PropTypes.number, 26 | dx: PropTypes.number, 27 | dy: PropTypes.number, 28 | scale: PropTypes.number, 29 | lineData: PropTypes.array 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Connector/ConnectorEndDot.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import EndDot from "viz-annotation/lib/Connector/end-dot" 5 | import ConnectorEnd from "./ConnectorEnd" 6 | import PropTypes from "prop-types" 7 | 8 | export default class ConnectorEndDot extends ConnectorEnd { 9 | getComponents({ x, y, dy, dx, lineData, scale }) { 10 | return EndDot({ x, y, dx, dy, lineData, scale }) 11 | } 12 | } 13 | 14 | ConnectorEndDot.propTypes = { 15 | x: PropTypes.number, 16 | y: PropTypes.number, 17 | dx: PropTypes.number, 18 | dy: PropTypes.number, 19 | scale: PropTypes.number, 20 | lineData: PropTypes.array 21 | //array of arrays of x,y coordinates for the connector line 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Connector/ConnectorLine.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import Line from "viz-annotation/lib/Connector/type-line" 5 | import Connector from "./Connector" 6 | 7 | export default class ConnectorLine extends Connector { 8 | getComponents({ x, y, dy, dx, radius, outerRadius, width, height }) { 9 | return Line({ x, y, dx, dy, radius, outerRadius, width, height }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/EditableAnnotation.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Annotation from "./Annotation" 3 | import classnames from "./classnames" 4 | 5 | export default class EditableAnnotation extends React.Component { 6 | state = { 7 | x: 0, 8 | y: 0, 9 | dx: 0, 10 | dy: 0 11 | } 12 | 13 | componentWillMount() { 14 | this.setState({ 15 | x: this.props.x, 16 | y: this.props.y, 17 | dx: this.props.dx, 18 | dy: this.props.dy 19 | }) 20 | } 21 | 22 | componentWillReceiveProps(nextProps) { 23 | this.setState({ 24 | x: nextProps.x, 25 | y: nextProps.y, 26 | dx: nextProps.dx, 27 | dy: nextProps.dy 28 | }) 29 | } 30 | 31 | getData() { 32 | return Object.assign({}, this.props, this.state) 33 | } 34 | 35 | dragEnd() { 36 | if (this.props.onDragEnd) { 37 | this.props.onDragEnd(this.getData()) 38 | } 39 | } 40 | 41 | dragStart() { 42 | if (this.props.onDragStart) { 43 | this.props.onDragStart(this.getData()) 44 | } 45 | } 46 | 47 | dragSubject(event, data) { 48 | this.setState( 49 | { 50 | x: this.state.x + data.deltaX, 51 | y: this.state.y + data.deltaY 52 | }, 53 | () => { 54 | if (this.props.onDrag) this.props.onDrag(this.getData()) 55 | } 56 | ) 57 | } 58 | 59 | dragConnectorSettings(event, data) { 60 | this.setState(data, () => { 61 | if (this.props.onDrag) this.props.onDrag(this.getData()) 62 | }) 63 | } 64 | 65 | dragSubjectSettings(event, data) { 66 | this.setState(data, () => { 67 | if (this.props.onDrag) this.props.onDrag(this.getData()) 68 | }) 69 | } 70 | 71 | dragNote(event, data) { 72 | this.setState( 73 | { 74 | dx: this.state.dx + data.deltaX, 75 | dy: this.state.dy + data.deltaY 76 | }, 77 | () => { 78 | if (this.props.onDrag) this.props.onDrag(this.getData()) 79 | } 80 | ) 81 | } 82 | 83 | render() { 84 | const cleanedProps = Object.assign({}, this.props, { 85 | ...this.state, 86 | dragSubject: this.dragSubject.bind(this), 87 | dragNote: this.dragNote.bind(this), 88 | dragSubjectSettings: this.dragSubjectSettings.bind(this), 89 | dragConnectorSettings: this.dragConnectorSettings.bind(this), 90 | dragEnd: this.dragEnd.bind(this), 91 | dragStart: this.dragStart.bind(this), 92 | editMode: true, 93 | className: classnames(this.props.className, "editable") 94 | }) 95 | 96 | return 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/components/Handle.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import PropTypes from "prop-types" 3 | 4 | const events = { 5 | mouse: { 6 | start: "mousedown", 7 | move: "mousemove", 8 | stop: "mouseup" 9 | }, 10 | touch: { 11 | start: "touchstart", 12 | move: "touchemove", 13 | stop: "touchend" 14 | } 15 | } 16 | 17 | const listenerOptions = { passive: false } 18 | 19 | const makeHandler = (type, handleStart, handleStop, handleDrag) => { 20 | return e => { 21 | e.preventDefault() 22 | const xDim = "clientX" 23 | const yDim = "clientY" 24 | const oX = e.nativeEvent[xDim] 25 | const oY = e.nativeEvent[yDim] 26 | let x = oX 27 | let y = oY 28 | handleStart && handleStart() 29 | 30 | const move = d => { 31 | d.preventDefault() 32 | handleDrag && 33 | handleDrag(d, { 34 | deltaX: d[xDim] - x, 35 | deltaY: d[yDim] - y, 36 | oDeltaX: d[xDim] - oX, 37 | oDeltaY: d[yDim] - oY 38 | }) 39 | x = d[xDim] 40 | y = d[yDim] 41 | } 42 | 43 | const stop = e => { 44 | e.preventDefault() 45 | document.removeEventListener(events[type].move, move, listenerOptions) 46 | document.removeEventListener(events[type].stop, stop, listenerOptions) 47 | handleStop && handleStop() 48 | } 49 | 50 | document.addEventListener(events[type].move, move, listenerOptions) 51 | document.addEventListener(events[type].stop, stop, listenerOptions) 52 | } 53 | } 54 | 55 | export default function Handle({ 56 | x = 0, 57 | y = 0, 58 | r = 10, 59 | handleStart, 60 | handleStop, 61 | handleDrag 62 | }) { 63 | return ( 64 | 76 | ) 77 | } 78 | 79 | Handle.propTypes = { 80 | x: PropTypes.number, 81 | y: PropTypes.number, 82 | r: PropTypes.number, 83 | handleStart: PropTypes.func, 84 | handleStop: PropTypes.func, 85 | handleDrag: PropTypes.func 86 | } 87 | -------------------------------------------------------------------------------- /src/components/Note/BracketNote.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | 5 | import Note from "./Note" 6 | 7 | export default function BracketNote({ width, height, depth, ...rest }) { 8 | let dx = rest.dx, 9 | orientation, 10 | align = "middle", 11 | dy = rest.dy 12 | 13 | if (height) { 14 | if (!dy) dy = height / 2 15 | if (!dx) dx = depth 16 | orientation = "leftRight" 17 | } else if (width) { 18 | if (!dx) dx = width / 2 19 | if (!dy) dy = depth 20 | orientation = "topBottom" 21 | } 22 | 23 | return ( 24 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Note/JSXNote.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react"; 3 | /* eslint-enable no-unused-vars */ 4 | import Handle from "../Handle"; 5 | 6 | export default function JSXNote(props) { 7 | const { note, dx, dy, editMode, dragStart, dragEnd, dragNote } = props; 8 | 9 | let handle; 10 | if (editMode) { 11 | handle = ( 12 | 17 | ); 18 | } 19 | 20 | return ( 21 | 22 | {typeof note === "function" ? note(props) : note} 23 | {handle} 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Note/Note.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import alignment from "viz-annotation/lib/Note/alignment" 3 | import Handle from "../Handle" 4 | import noteVertical from "viz-annotation/lib/Note/lineType-vertical" 5 | import noteHorizontal from "viz-annotation/lib/Note/lineType-horizontal" 6 | import PropTypes from "prop-types" 7 | 8 | const getOuterBBox = (...domNodes) => { 9 | return [...domNodes].reduce( 10 | (p, c) => { 11 | if (c) { 12 | const bbox = c.getBBox() 13 | p.x = Math.min(p.x, bbox.x) 14 | p.y = Math.min(p.y, bbox.y) 15 | p.width = Math.max(p.width, bbox.width) 16 | 17 | const yOffset = c && c.attributes && c.attributes.y 18 | p.height = Math.max( 19 | p.height, 20 | ((yOffset && parseFloat(yOffset.value)) || 0) + bbox.height 21 | ) 22 | } 23 | return p 24 | }, 25 | { x: 0, y: 0, width: 0, height: 0 } 26 | ) 27 | } 28 | 29 | export default class Note extends React.Component { 30 | constructor(props) { 31 | super(props) 32 | 33 | this.updateText = this.updateText.bind(this) 34 | 35 | // this.note = React.createRef() 36 | // this.title = React.createRef() 37 | // this.label = React.createRef() 38 | } 39 | state = { 40 | translateX: 0, 41 | translateY: 0, 42 | labelOffset: 0, 43 | changed: 0, 44 | bbox: { width: 0, height: 0, x: 0, y: 0 } 45 | } 46 | componentDidMount() { 47 | this.updateText(this.props) 48 | } 49 | componentWillReceiveProps(nextProps) { 50 | if ( 51 | nextProps.title !== this.props.title || 52 | nextProps.label !== this.props.label || 53 | nextProps.wrap !== this.props.wrap 54 | ) { 55 | this.updateText(nextProps) 56 | } 57 | if ( 58 | nextProps.editMode && 59 | (nextProps.align === "dynamic" || !nextProps.align) 60 | ) { 61 | this.updateText(nextProps) 62 | } 63 | } 64 | updateText({ 65 | orientation, 66 | padding, 67 | align, 68 | lineType, 69 | label, 70 | title, 71 | wrap, 72 | wrapSplitter, 73 | dx, 74 | dy 75 | }) { 76 | const newState = { 77 | titleWrapped: null, 78 | labelWrapped: null 79 | } 80 | newState.changed = this.state.changed + 1 81 | 82 | if (title) { 83 | newState.titleWrapped = 84 | this.title && 85 | this.wrapText(this.title, newState.changed, title, wrap, wrapSplitter) 86 | } 87 | if (label) 88 | newState.labelWrapped = 89 | this.label && 90 | this.wrapText(this.label, newState.changed, label, wrap, wrapSplitter) 91 | 92 | this.setState(newState, () => { 93 | const setLabel = () => { 94 | const bbox = getOuterBBox(this.title, this.label) 95 | const noteParams = { 96 | padding, 97 | bbox, 98 | offset: { x: dx, y: dy }, 99 | orientation, 100 | align 101 | } 102 | if (lineType === "vertical") noteParams.orientation = "leftRight" 103 | else if (lineType === "horizontal") noteParams.orientation = "topBottom" 104 | 105 | const { x, y } = alignment(noteParams) 106 | 107 | this.setState({ 108 | translateX: x, 109 | translateY: y, 110 | bbox 111 | }) 112 | } 113 | 114 | this.setState( 115 | { 116 | labelOffset: (title && this.title.getBBox().height) || 0 117 | }, 118 | setLabel 119 | ) 120 | }) 121 | } 122 | 123 | wrapText(textRef, key, text, width, wrapSplitter) { 124 | const initialAttrs = { 125 | x: 0, 126 | dy: "1.2em" 127 | } 128 | 129 | const words = text 130 | .split(wrapSplitter || /[ \t\r\n]+/) 131 | .reverse() 132 | .filter(w => w !== "") 133 | 134 | let word, 135 | line = [] 136 | 137 | const tspans = [] 138 | 139 | while ((word = words.pop())) { 140 | line.push(word) 141 | textRef.lastChild.textContent = line.join(" ") 142 | 143 | const length = textRef.lastChild.getComputedTextLength() 144 | 145 | textRef.lastChild.textContent = "" 146 | 147 | if (length > width && line.length > 1) { 148 | line.pop() 149 | tspans.push( 150 | 151 | {line.join(" ")} 152 | 153 | ) 154 | line = [word] 155 | } 156 | } 157 | 158 | if (line.length !== 0) { 159 | tspans.push( 160 | 161 | {line.join(" ")} 162 | 163 | ) 164 | } 165 | 166 | return ( 167 | 168 | {tspans} 169 | 170 | ) 171 | } 172 | 173 | componentDidUpdate(prevProps) { 174 | const { orientation, padding, align, dx, dy, lineType } = this.props 175 | 176 | if ( 177 | this.state.bbox.width && 178 | (prevProps.dx !== this.props.dx || prevProps.dy !== this.props.dy) && 179 | (this.title || this.label) 180 | ) { 181 | const bbox = getOuterBBox(this.title, this.label) 182 | const noteParams = { 183 | padding, 184 | bbox, 185 | offset: { x: dx, y: dy }, 186 | orientation, 187 | align 188 | } 189 | 190 | if (lineType === "vertical") noteParams.orientation = "leftRight" 191 | else if (lineType === "horizontal") noteParams.orientation = "topBottom" 192 | 193 | const { x, y } = alignment(noteParams) 194 | const updates = { bbox } 195 | if (this.state.translateX !== x) updates.translateX = x 196 | if (this.state.translateY !== y) updates.translateY = y 197 | if ( 198 | updates.translateX !== undefined || 199 | updates.translateY !== undefined 200 | ) { 201 | this.setState(updates) 202 | } 203 | } else if ( 204 | this.state.align !== prevProps.align || 205 | this.props.orientation !== prevProps.orientation || 206 | this.props.padding !== prevProps.padding 207 | ) { 208 | const noteParams = { 209 | padding, 210 | bbox: this.state.bbox, 211 | offset: { x: dx, y: dy }, 212 | orientation, 213 | align 214 | } 215 | 216 | if (lineType === "vertical") noteParams.orientation = "leftRight" 217 | else if (lineType === "horizontal") noteParams.orientation = "topBottom" 218 | 219 | const { x, y } = alignment(noteParams) 220 | const updates = {} 221 | if (this.state.translateX !== x) updates.translateX = x 222 | if (this.state.translateY !== y) updates.translateY = y 223 | if ( 224 | updates.translateX !== undefined || 225 | updates.translateY !== undefined 226 | ) { 227 | this.setState(updates) 228 | } 229 | } 230 | } 231 | 232 | render() { 233 | const { 234 | dx, 235 | dy, 236 | title, 237 | label, 238 | align, 239 | editMode, 240 | lineType, 241 | color, 242 | titleColor, 243 | labelColor, 244 | bgPadding 245 | } = this.props 246 | 247 | let bgPaddingFinal = { top: 0, bottom: 0, left: 0, right: 0 } 248 | 249 | if (typeof bgPadding === "number") { 250 | bgPaddingFinal = { 251 | top: bgPadding, 252 | bottom: bgPadding, 253 | left: bgPadding, 254 | right: bgPadding 255 | } 256 | } else if (bgPadding && typeof bgPadding === "object") { 257 | bgPaddingFinal = Object.assign(bgPaddingFinal, bgPadding) 258 | } 259 | 260 | let noteTitle, noteText, noteLineType 261 | if (title) { 262 | noteTitle = ( 263 | (this.title = el)} 265 | className="annotation-note-title" 266 | fontWeight="bold" 267 | key="title" 268 | fill={titleColor || color} 269 | > 270 | {this.state.titleWrapped || ( 271 | 272 | {title} 273 | 274 | )} 275 | 276 | ) 277 | } 278 | 279 | if (label) { 280 | noteText = ( 281 | (this.label = el)} 283 | className="annotation-note-label" 284 | y={this.state.labelOffset * 1.1} 285 | key="label" 286 | fill={labelColor || color} 287 | > 288 | {this.state.labelWrapped || ( 289 | 290 | {label} 291 | 292 | )} 293 | 294 | ) 295 | } 296 | 297 | if (lineType && this.state.bbox.width) { 298 | const noteParams = { 299 | bbox: this.state.bbox, 300 | align, 301 | offset: { x: dx, y: dy } 302 | } 303 | 304 | const noteComponent = ( 305 | (lineType === "vertical" && noteVertical(noteParams)) || 306 | (lineType === "horizontal" && noteHorizontal(noteParams)) 307 | ).components[0] 308 | 309 | noteLineType = ( 310 | 315 | ) 316 | } 317 | 318 | let handle 319 | 320 | if (editMode) { 321 | handle = ( 322 | 327 | ) 328 | } 329 | 330 | return ( 331 | 336 | (this.note = el)} 341 | > 342 | 358 | {noteTitle} 359 | {noteText} 360 | 361 | {noteLineType} 362 | {handle} 363 | 364 | ) 365 | } 366 | } 367 | 368 | Note.defaultProps = { 369 | wrap: 120, 370 | align: "dynamic", 371 | orientation: "topBottom", 372 | padding: 3 373 | } 374 | 375 | Note.propTypes = { 376 | dx: PropTypes.number, 377 | dy: PropTypes.number, 378 | title: PropTypes.string, 379 | label: PropTypes.string, 380 | orientation: PropTypes.oneOf(["leftRight", "topBottom"]), 381 | padding: PropTypes.number, 382 | bgPadding: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), 383 | align: PropTypes.oneOf([ 384 | "left", 385 | "right", 386 | "middle", 387 | "top", 388 | "bottom", 389 | "dynamic" 390 | ]), 391 | editMode: PropTypes.bool, 392 | lineType: PropTypes.oneOf(["vertical", "horizontal"]), 393 | color: PropTypes.string, 394 | titleColor: PropTypes.string, 395 | labelColor: PropTypes.string 396 | } 397 | -------------------------------------------------------------------------------- /src/components/Subject/Subject.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Handle from "../Handle" 3 | 4 | const FILLABLE = ["SubjectCircle", "SubjectRect"] 5 | 6 | export default class Subject extends React.Component { 7 | getComponents() {} 8 | 9 | render() { 10 | const { editMode, color, fill = "none", fillOpacity = 1 } = this.props 11 | 12 | const d = this.getComponents(this.props) || {} 13 | 14 | let handles 15 | if (editMode) { 16 | handles = [ 17 | 23 | ] 24 | 25 | if (d.handles) { 26 | handles = handles.concat( 27 | d.handles.map((h, i) => ( 28 | { 36 | this.props.dragSubjectSettings(e, d.handleFunction(h, data)) 37 | }} 38 | /> 39 | )) 40 | ) 41 | } 42 | } 43 | 44 | const honorFill = FILLABLE.indexOf(this.name) !== -1 45 | 46 | return ( 47 | { 51 | this.subject = subject 52 | }} 53 | > 54 | {d.components && 55 | d.components.map((c, i) => { 56 | const attrs = {} 57 | if (!c) return null 58 | Object.keys(c.attrs).forEach(k => { 59 | if (c.attrs[k] && k !== "text") { 60 | attrs[k.replace(/-([a-z])/g, g => g[1].toUpperCase())] = 61 | c.attrs[k] 62 | } 63 | }) 64 | return ( 65 | 73 | {c.attrs.text} 74 | 75 | ) 76 | })} 77 | {handles} 78 | 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/components/Subject/SubjectBadge.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import Badge from "viz-annotation/lib/Subject/badge" 5 | import Subject from "./Subject" 6 | import PropTypes from "prop-types" 7 | 8 | export default class SubjectBadge extends Subject { 9 | getComponents({ leftRight, topBottom, text, editMode, color, radius }) { 10 | const components = Badge({ 11 | leftRight, 12 | topBottom, 13 | text, 14 | editMode, 15 | color, 16 | radius 17 | }) 18 | 19 | components.handleKeys = { leftRight, topBottom } 20 | components.handleFunction = (h, data) => { 21 | const lr = 22 | data.oDeltaX < -radius * 2 23 | ? "left" 24 | : data.oDeltaX > radius * 2 25 | ? "right" 26 | : undefined 27 | const tb = 28 | data.oDeltaY < -radius * 2 29 | ? "top" 30 | : data.oDeltaY > radius * 2 31 | ? "bottom" 32 | : undefined 33 | 34 | return { 35 | leftRight: lr, 36 | topBottom: tb 37 | } 38 | } 39 | 40 | return components 41 | } 42 | } 43 | 44 | SubjectBadge.propTypes = { 45 | leftRight: PropTypes.oneOf(["left", "right"]), 46 | topBottom: PropTypes.oneOf(["top", "bottom"]), 47 | text: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 48 | color: PropTypes.string, 49 | editMode: PropTypes.bool 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Subject/SubjectBracket.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import Bracket from "viz-annotation/lib/Subject/bracket" 5 | import Subject from "./Subject" 6 | import PropTypes from "prop-types" 7 | 8 | export default class SubjectBracket extends Subject { 9 | name = "SubjectBracket" 10 | 11 | getComponents({ height, width, depth = 20, type = "square", editMode }) { 12 | const components = Bracket({ height, width, depth, type, editMode }) 13 | 14 | const handleKeys = { height, width, depth } 15 | components.handleFunction = (h, data) => { 16 | if (h.key === "depth") { 17 | return { 18 | depth: depth + data[`oDelta${h.type}`] 19 | } 20 | } else { 21 | return { 22 | [h.key]: 23 | handleKeys[h.key] + data[h.key === "width" ? "oDeltaX" : "oDeltaY"] 24 | } 25 | } 26 | } 27 | 28 | return components 29 | } 30 | } 31 | 32 | SubjectBracket.propTypes = { 33 | width: PropTypes.number, 34 | height: PropTypes.number, 35 | depth: PropTypes.number, 36 | editMode: PropTypes.bool 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Subject/SubjectCircle.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import Circle from "viz-annotation/lib/Subject/circle" 5 | import Subject from "./Subject" 6 | import PropTypes from "prop-types" 7 | 8 | export default class SubjectCircle extends Subject { 9 | name = "SubjectCircle" 10 | 11 | getComponents({ 12 | radius = 20, 13 | innerRadius, 14 | outerRadius, 15 | radiusPadding, 16 | editMode 17 | }) { 18 | const components = Circle({ 19 | radius, 20 | radiusPadding, 21 | innerRadius, 22 | outerRadius, 23 | editMode 24 | }) 25 | 26 | components.handleKeys = { radius, innerRadius, outerRadius } 27 | components.handleFunction = (h, data) => { 28 | return { 29 | [h.key]: components.handleKeys[h.key] + data.oDeltaX * Math.sqrt(2) 30 | } 31 | } 32 | return components 33 | } 34 | } 35 | 36 | SubjectCircle.propTypes = { 37 | radius: PropTypes.number, 38 | innerRadius: PropTypes.number, 39 | outerRadius: PropTypes.number, 40 | radiusPadding: PropTypes.number, 41 | editMode: PropTypes.bool 42 | } 43 | -------------------------------------------------------------------------------- /src/components/Subject/SubjectCustom.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | // import Bracket from "./bracket" 5 | import Subject from "./Subject" 6 | import PropTypes from "prop-types" 7 | import Handle from "../Handle" 8 | 9 | export default class SubjectCustom extends Subject { 10 | render() { 11 | const { custom = "M0,0", editMode, transform } = this.props 12 | 13 | let handles 14 | if (editMode) { 15 | handles = ( 16 | 21 | ) 22 | } 23 | return ( 24 | 25 | 26 | {typeof custom === "string" ? ( 27 | 28 | ) : ( 29 | {custom} 30 | )} 31 | 32 | {handles} 33 | 34 | ) 35 | } 36 | } 37 | 38 | SubjectCustom.propTypes = { 39 | editMode: PropTypes.bool 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Subject/SubjectRect.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import Rect from "viz-annotation/lib/Subject/rect" 5 | import Subject from "./Subject" 6 | import PropTypes from "prop-types" 7 | 8 | export default class SubjectRect extends Subject { 9 | name = "SubjectRect" 10 | 11 | getComponents({ width = 100, height = 100, editMode }) { 12 | const components = Rect({ width, height, editMode }) 13 | components.handleKeys = { width, height } 14 | 15 | components.handleFunction = (h, data) => { 16 | return { 17 | [h.key]: 18 | h.key === "width" ? width + data.oDeltaX : height + data.oDeltaY 19 | } 20 | } 21 | 22 | return components 23 | } 24 | } 25 | 26 | SubjectRect.propTypes = { 27 | width: PropTypes.number, 28 | height: PropTypes.number, 29 | editMode: PropTypes.bool 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Subject/SubjectThreshold.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import Threshold from "viz-annotation/lib/Subject/threshold" 5 | import Subject from "./Subject" 6 | import PropTypes from "prop-types" 7 | 8 | export default class SubjectThreshold extends Subject { 9 | name = "SubjectThreshold" 10 | 11 | getComponents({ x1, x2, y1, y2, x, y, editMode }) { 12 | return Threshold({ x1, x2, y1, y2, x, y, editMode }) 13 | } 14 | } 15 | 16 | SubjectThreshold.propTypes = { 17 | x: PropTypes.number, 18 | x1: PropTypes.number, 19 | x2: PropTypes.number, 20 | y: PropTypes.number, 21 | y1: PropTypes.number, 22 | y2: PropTypes.number, 23 | editMode: PropTypes.bool 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Types/AnnotationBadge.js: -------------------------------------------------------------------------------- 1 | import SubjectBadge from "../Subject/SubjectBadge" 2 | import classnames from "../classnames" 3 | import annotationMapper from "./Type" 4 | 5 | export default function AnnotationBadge(props) { 6 | const className = classnames("badge", props.className) 7 | return annotationMapper( 8 | { ...props, disable: ["connector", "note"], className }, 9 | null, 10 | null, 11 | SubjectBadge 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Types/AnnotationBracket.js: -------------------------------------------------------------------------------- 1 | import SubjectBracket from "../Subject/SubjectBracket" 2 | import BracketNote from "../Note/BracketNote" 3 | import classnames from "../classnames" 4 | import annotationMapper from "./Type" 5 | 6 | export default function AnnotationBracket(props) { 7 | const className = classnames("bracket", props.className) 8 | return annotationMapper( 9 | { ...props, disable: ["connector"], className }, 10 | null, 11 | null, 12 | SubjectBracket, 13 | { depth: 20 }, 14 | BracketNote 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Types/AnnotationCallout.js: -------------------------------------------------------------------------------- 1 | import classnames from "../classnames" 2 | import annotationMapper from "./Type" 3 | import ConnectorLine from "../Connector/ConnectorLine" 4 | 5 | export default function AnnotationCallout(props) { 6 | const className = classnames("callout", props.className) 7 | return annotationMapper({ ...props, className }, ConnectorLine, { 8 | lineType: "horizontal" 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Types/AnnotationCalloutCircle.js: -------------------------------------------------------------------------------- 1 | import SubjectCircle from "../Subject/SubjectCircle" 2 | 3 | import ConnectorElbow from "../Connector/ConnectorElbow" 4 | import classnames from "../classnames" 5 | import annotationMapper from "./Type" 6 | 7 | export default function AnnotationCalloutCircle(props) { 8 | const className = classnames("callout circle", props.className) 9 | return annotationMapper( 10 | { ...props, className }, 11 | ConnectorElbow, 12 | { lineType: "horizontal" }, 13 | SubjectCircle, 14 | { radius: 20 } 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Types/AnnotationCalloutCurve.js: -------------------------------------------------------------------------------- 1 | import ConnectorCurve from "../Connector/ConnectorCurve" 2 | import classnames from "../classnames" 3 | import annotationMapper from "./Type" 4 | 5 | export default function AnnotationCalloutCurve(props) { 6 | const className = classnames("callout curve", props.className) 7 | return annotationMapper({ ...props, className }, ConnectorCurve, { 8 | lineType: "horizontal" 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Types/AnnotationCalloutCustom.js: -------------------------------------------------------------------------------- 1 | import SubjectCustom from "../Subject/SubjectCustom" 2 | import ConnectorElbow from "../Connector/ConnectorElbow" 3 | import classnames from "../classnames" 4 | import annotationMapper from "./Type" 5 | 6 | export default function AnnotationCalloutCustom(props) { 7 | const className = classnames("callout custom", props.className) 8 | return annotationMapper( 9 | { ...props, className }, 10 | ConnectorElbow, 11 | { lineType: "horizontal" }, 12 | SubjectCustom 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Types/AnnotationCalloutElbow.js: -------------------------------------------------------------------------------- 1 | import ConnectorElbow from "../Connector/ConnectorElbow" 2 | import classnames from "../classnames" 3 | import annotationMapper from "./Type" 4 | 5 | export default function AnnotationCalloutElbow(props) { 6 | const className = classnames("callout elbow", props.className) 7 | return annotationMapper({ ...props, className }, ConnectorElbow, { 8 | lineType: "horizontal" 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Types/AnnotationCalloutRect.js: -------------------------------------------------------------------------------- 1 | import SubjectRect from "../Subject/SubjectRect" 2 | import ConnectorElbow from "../Connector/ConnectorElbow" 3 | import classnames from "../classnames" 4 | import annotationMapper from "./Type" 5 | 6 | export default function AnnotationCalloutRect(props) { 7 | const className = classnames("callout rect", props.className) 8 | return annotationMapper( 9 | { ...props, className }, 10 | ConnectorElbow, 11 | { lineType: "horizontal" }, 12 | SubjectRect, 13 | { width: 100, height: 100 } 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Types/AnnotationLabel.js: -------------------------------------------------------------------------------- 1 | import classnames from "../classnames" 2 | import annotationMapper from "./Type" 3 | import ConnectorLine from "../Connector/ConnectorLine" 4 | 5 | export default function AnnotationLabel(props) { 6 | const className = classnames("label", props.className) 7 | return annotationMapper({ ...props, className }, ConnectorLine, { 8 | align: "middle" 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Types/AnnotationXYThreshold.js: -------------------------------------------------------------------------------- 1 | import SubjectThreshold from "../Subject/SubjectThreshold" 2 | import ConnectorElbow from "../Connector/ConnectorElbow" 3 | import classnames from "../classnames" 4 | import annotationMapper from "./Type" 5 | 6 | export default function AnnotationXYThreshold(props) { 7 | const className = classnames("callout xythreshold", props.className) 8 | return annotationMapper( 9 | { ...props, className }, 10 | ConnectorElbow, 11 | { lineType: "horizontal" }, 12 | SubjectThreshold 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Types/Type.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Annotation from "../Annotation" 3 | import EditableAnnotation from "../EditableAnnotation" 4 | import ConnectorLine from "../Connector/ConnectorLine" 5 | import ConnectorElbow from "../Connector/ConnectorElbow" 6 | import ConnectorCurve from "../Connector/ConnectorCurve" 7 | import ConnectorEndDot from "../Connector/ConnectorEndDot" 8 | import ConnectorEndArrow from "../Connector/ConnectorEndArrow" 9 | import DefaultSubject from "../Subject/Subject" 10 | import Note from "../Note/Note" 11 | import JSXNote from "../Note/JSXNote" 12 | 13 | const getAnnotationType = editMode => 14 | editMode ? EditableAnnotation : Annotation 15 | 16 | export default function( 17 | props, 18 | Connector, 19 | NoteDefaultProps = {}, 20 | Subject = DefaultSubject, 21 | SubjectDefaultProps = {}, 22 | NoteType = Note 23 | ) { 24 | const { 25 | disable = [], 26 | connector, 27 | note, 28 | subject, 29 | x, 30 | y, 31 | dx, 32 | dy, 33 | nx, 34 | ny, 35 | color, 36 | className, 37 | onDrag, 38 | onDragStart, 39 | onDragEnd, 40 | editMode, 41 | events 42 | } = props 43 | const CONNECTORS = { 44 | type: { 45 | curve: ConnectorCurve, 46 | line: ConnectorLine, 47 | elbow: ConnectorElbow 48 | }, 49 | end: { 50 | dot: ConnectorEndDot, 51 | arrow: ConnectorEndArrow 52 | } 53 | } 54 | 55 | let ConnectorType, ConnectorEndType 56 | if (disable.indexOf("connector") === -1) { 57 | ConnectorType = (connector && CONNECTORS.type[connector.type]) || Connector 58 | ConnectorEndType = connector && CONNECTORS.end[connector.end] 59 | } 60 | 61 | const AnnotationType = getAnnotationType(props.editMode) 62 | 63 | return ( 64 | 81 | {ConnectorType && ( 82 | 83 | {ConnectorEndType && } 84 | 85 | )} 86 | {Subject && disable.indexOf("subject") === -1 && } 87 | {note && 88 | disable.indexOf("note") === -1 && 89 | (React.isValidElement(note) || typeof note === "function" ? ( 90 | 91 | ) : ( 92 | 93 | ))} 94 | 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /src/components/classnames.js: -------------------------------------------------------------------------------- 1 | export default (...params) => 2 | params 3 | .filter(d => d) 4 | .join(" ") 5 | .trim() 6 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | // export individual components 2 | import Connector from "./Connector/Connector" 3 | import ConnectorCurve from "./Connector/ConnectorCurve" 4 | import ConnectorElbow from "./Connector/ConnectorElbow" 5 | import ConnectorLine from "./Connector/ConnectorLine" 6 | import ConnectorEndDot from "./Connector/ConnectorEndDot" 7 | import ConnectorEndArrow from "./Connector/ConnectorEndArrow" 8 | 9 | import Subject from "./Subject/Subject" 10 | import SubjectBadge from "./Subject/SubjectBadge" 11 | import SubjectCircle from "./Subject/SubjectCircle" 12 | import SubjectRect from "./Subject/SubjectRect" 13 | import SubjectThreshold from "./Subject/SubjectThreshold" 14 | import SubjectBracket from "./Subject/SubjectBracket" 15 | import SubjectCustom from "./Subject/SubjectCustom" 16 | 17 | import Note from "./Note/Note" 18 | import BracketNote from "./Note/BracketNote" 19 | 20 | import Annotation from "./Annotation" 21 | import EditableAnnotation from "./EditableAnnotation" 22 | 23 | import AnnotationLabel from "./Types/AnnotationLabel" 24 | import AnnotationCallout from "./Types/AnnotationCallout" 25 | import AnnotationCalloutElbow from "./Types/AnnotationCalloutElbow" 26 | import AnnotationCalloutCurve from "./Types/AnnotationCalloutCurve" 27 | import AnnotationCalloutCircle from "./Types/AnnotationCalloutCircle" 28 | import AnnotationCalloutRect from "./Types/AnnotationCalloutRect" 29 | import AnnotationXYThreshold from "./Types/AnnotationXYThreshold" 30 | import AnnotationBadge from "./Types/AnnotationBadge" 31 | import AnnotationBracket from "./Types/AnnotationBracket" 32 | import AnnotationCalloutCustom from "./Types/AnnotationCalloutCustom" 33 | 34 | export { 35 | Connector, 36 | ConnectorCurve, 37 | ConnectorElbow, 38 | ConnectorLine, 39 | ConnectorEndDot, 40 | ConnectorEndArrow, 41 | Subject, 42 | SubjectBadge, 43 | SubjectCircle, 44 | SubjectRect, 45 | SubjectThreshold, 46 | SubjectBracket, 47 | SubjectCustom, 48 | Note, 49 | BracketNote, 50 | Annotation, 51 | EditableAnnotation, 52 | AnnotationLabel, 53 | AnnotationCallout, 54 | AnnotationCalloutCircle, 55 | AnnotationCalloutCurve, 56 | AnnotationCalloutElbow, 57 | AnnotationCalloutRect, 58 | AnnotationXYThreshold, 59 | AnnotationBadge, 60 | AnnotationBracket, 61 | AnnotationCalloutCustom 62 | } 63 | 64 | export default { 65 | Connector, 66 | ConnectorCurve, 67 | ConnectorElbow, 68 | ConnectorLine, 69 | ConnectorEndDot, 70 | ConnectorEndArrow, 71 | Subject, 72 | SubjectBadge, 73 | SubjectCircle, 74 | SubjectRect, 75 | SubjectThreshold, 76 | SubjectBracket, 77 | SubjectCustom, 78 | Note, 79 | BracketNote, 80 | Annotation, 81 | EditableAnnotation, 82 | AnnotationLabel, 83 | AnnotationCallout, 84 | AnnotationCalloutCircle, 85 | AnnotationCalloutCurve, 86 | AnnotationCalloutElbow, 87 | AnnotationCalloutRect, 88 | AnnotationXYThreshold, 89 | AnnotationBadge, 90 | AnnotationBracket, 91 | AnnotationCalloutCustom 92 | } 93 | -------------------------------------------------------------------------------- /src/components/web.js: -------------------------------------------------------------------------------- 1 | import Annotations from "./index" 2 | window.Annotations = Annotations 3 | -------------------------------------------------------------------------------- /src/docs/Examples.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { scaleTime, scaleLinear } from "d3-scale" 4 | import { line } from "d3-shape" 5 | import { extent, max } from "d3-array" 6 | import stock from "./data/stock.json" 7 | import theme from "./theme" 8 | import { 9 | AnnotationCalloutCircle, 10 | AnnotationBadge, 11 | AnnotationLabel, 12 | AnnotationXYThreshold 13 | } from "../components/index" 14 | import { Code } from "./Sections" 15 | import yearNetwork from "./data/yearNetwork.json" 16 | 17 | export class Tooltip extends React.Component { 18 | constructor(props) { 19 | super(props) 20 | this.state = { 21 | hover: false 22 | } 23 | } 24 | 25 | render() { 26 | // line chart code: https://bl.ocks.org/d3noob/402dd382a51a4f6eea487f9a35566de0 27 | // time series from: http://bl.ocks.org/mbostock/3883245 28 | // set the dimensions and margins of the graph 29 | const margin = { top: 20, right: 20, bottom: 30, left: 20 }, 30 | height = 500 - margin.top - margin.bottom 31 | let width = 860 - margin.left - margin.right 32 | 33 | const x = scaleTime().range([0, width]) 34 | const y = scaleLinear().range([height, 0]) 35 | 36 | const valueline = line() 37 | .x(d => x(d.date)) 38 | .y(d => y(d.close)) 39 | 40 | stock.forEach(function(d) { 41 | d.date = new Date(d.date) 42 | d.close = +d.close 43 | }) 44 | 45 | x.domain(extent(stock, d => d.date)) 46 | y.domain([0, max(stock, d => d.close)]) 47 | 48 | //Add annotations 49 | const labels = [ 50 | { 51 | data: { date: "9-Apr-12", close: 636.23 }, 52 | dy: 37, 53 | dx: -142 54 | }, 55 | { 56 | data: { date: "26-Feb-08", close: 119.15 }, 57 | dy: -137, 58 | dx: 0, 59 | note: { align: "middle" } 60 | }, 61 | { 62 | data: { date: "18-Sep-09", close: 185.02 }, 63 | dy: 37, 64 | dx: 42 65 | } 66 | ].map(l => { 67 | l.x = x(new Date(l.data.date)) 68 | l.y = y(l.data.close) 69 | l.note = Object.assign({}, l.note, { 70 | title: `Close: ${l.data.close}`, 71 | label: `${l.data.date}` 72 | }) 73 | l.subject = { radius: 4 } 74 | 75 | return l 76 | }) 77 | 78 | const annotations = labels.map((a, i) => ( 79 | 80 | 90 | 96 | this.setState({ 97 | hover: i 98 | })} 99 | onMouseOut={() => 100 | this.setState({ 101 | hover: null 102 | })} 103 | /> 104 | 105 | )) 106 | 107 | return ( 108 |
109 |

Basic Circle Callouts

110 | 111 | Explore in CodePen 112 | 113 | 114 | 115 | 116 | Hover over the dots to see more information 117 | 118 | Uses react-annotation's AnnotationCallout to make a tooltip 119 | 120 | 121 | 122 | 123 | 124 | 125 | {annotations} 126 | 127 | 128 | 129 | {` 130 | 131 | const margin = { top: 20, right: 20, bottom: 30, left: 50 }, 132 | height = 500 - margin.top - margin.bottom 133 | let width = 860 - margin.left - margin.right 134 | 135 | const x = scaleTime().range([0, width]) 136 | const y = scaleLinear().range([height, 0]) 137 | 138 | const valueline = line() 139 | .x(d => x(d.date)) 140 | .y(d => y(d.close)) 141 | 142 | stock.forEach(function(d) { 143 | d.date = new Date(d.date) 144 | d.close = +d.close 145 | }) 146 | 147 | x.domain(extent(stock, d => d.date)) 148 | y.domain([0, max(stock, d => d.close)]) 149 | 150 | //Add annotations 151 | const labels = [ 152 | { 153 | data: { date: "9-Apr-12", close: 636.23 }, 154 | dy: 37, 155 | dx: -142 156 | }, 157 | { 158 | data: { date: "26-Feb-08", close: 119.15 }, 159 | dy: -137, 160 | dx: 0, 161 | note: { align: "middle" } 162 | }, 163 | { 164 | data: { date: "18-Sep-09", close: 185.02 }, 165 | dy: 37, 166 | dx: 42 167 | } 168 | ].map(l => { 169 | l.x = x(new Date(l.data.date)) 170 | l.y = y(l.data.close) 171 | l.note = Object.assign({}, l.note, { 172 | title: \`Close: \${l.data.close}\`, 173 | label: \`\${l.data.date}\` 174 | }) 175 | l.subject = { radius: 4 } 176 | 177 | return l 178 | }) 179 | 180 | const annotations = labels.map((a, i) => ( 181 | 182 | 192 | 198 | this.setState({ 199 | hover: i 200 | })} 201 | onMouseOut={() => 202 | this.setState({ 203 | hover: null 204 | })} 205 | /> 206 | 207 | )) 208 | 209 | return ( 210 | 211 | 212 | 213 | Hover over the dots to see more information 214 | 215 | Uses react-annotation's AnnotationCallout to make a tooltip 216 | 217 | 218 | 219 | 220 | 221 | 222 | {annotations} 223 | 224 | 225 | ) 226 | `} 227 | 228 |
229 | ) 230 | } 231 | } 232 | 233 | export function Emmys() { 234 | const width = 960, 235 | height = 500, 236 | margin = { top: 30, right: 130, bottom: 50, left: 340 } 237 | 238 | const x = scaleLinear() 239 | .range([margin.left, width - margin.right]) 240 | .domain([2013, 2017]) 241 | const y = scaleLinear().range([height - margin.bottom, margin.top]) 242 | const networkLines = yearNetwork.networkLines 243 | 244 | y.domain([0, Math.max(...networkLines.map(d => d.max))]) 245 | let lineGen = line() 246 | .x(function(d) { 247 | return x(d.year) 248 | }) 249 | .y(function(d) { 250 | return y(d.value) 251 | }) 252 | 253 | const colors = { 254 | HBO: "black", 255 | Netflix: "#D32F2F", 256 | NBC: "#ffc107", 257 | "FX Networks": "#0097a7", 258 | ABC: "#00BFA5", 259 | CBS: "#00BCD4", 260 | FOX: "#3f51b5", 261 | Showtime: "#C5CAE9", 262 | AMC: "#D32F2F", 263 | PBS: "#B39DDB", 264 | Amazon: "#ffc107", 265 | "Nat Geo": "#ff9800", 266 | Hulu: "#00BFA5" 267 | } 268 | const highlight = ["HBO", "Netflix"] 269 | 270 | const lines = networkLines 271 | .sort((a, b) => a.total - b.total) 272 | .map(d => ( 273 | 281 | )) 282 | 283 | /* Code below relevant for annotations */ 284 | let previousNY = 0 285 | const labelAnnotations = networkLines 286 | .sort( 287 | //sort annotations by last data point for ordering 288 | (a, b) => 289 | b.line[b.line.length - 1].value - a.line[a.line.length - 1].value 290 | ) 291 | .reduce((p, c) => { 292 | //push annotation down if it will overlap 293 | const ypx = y(c.line[c.line.length - 1].value) 294 | let ny 295 | 296 | if (ypx - previousNY < 10) { 297 | ny = previousNY + 15 298 | } 299 | 300 | p.push( 301 | 312 | ) 313 | previousNY = ny || ypx 314 | 315 | return p 316 | }, []) 317 | 318 | const axisAnnotations = networkLines 319 | .filter(d => d.network === "HBO")[0] 320 | .line.map(d => ( 321 | 333 | )) 334 | 335 | const labels = networkLines 336 | .filter(d => highlight.indexOf(d.network) !== -1) 337 | .reduce((p, c) => { 338 | p = p.concat( 339 | c.line.map(d => { 340 | return { 341 | network: c.network, 342 | year: d.year, 343 | value: d.value 344 | } 345 | }) 346 | ) 347 | return p 348 | }, []) 349 | 350 | const badgeAnnotations = labels.map(d => { 351 | return ( 352 | 362 | ) 363 | }) 364 | 365 | return ( 366 |
367 | 368 | Explore in CodePen 369 | 370 |
371 | 372 | 373 | {lines} 374 | {labelAnnotations} 375 | {axisAnnotations} 376 | {badgeAnnotations} 377 | 385 | 386 | 387 |
388 | Netflix Challenges HBO at 389 | the 2017 Emmys 390 |
391 |
392 |
393 | ) 394 | } 395 | -------------------------------------------------------------------------------- /src/docs/Icons.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | 5 | export function DonutIcon() { 6 | return ( 7 | 8 | 9 | 10 | 14 | 15 | ) 16 | } 17 | 18 | export function PuppersIcon() { 19 | return [ 20 | , 32 | , 44 | , 54 | , 64 | , 74 | , 84 | , 95 | , 106 | 116 | ] 117 | } 118 | -------------------------------------------------------------------------------- /src/docs/Sections.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import { PrismCode } from "react-prism" 5 | import TypesUI from "./Types" 6 | import Chip from "material-ui/Chip" 7 | import Avatar from "material-ui/Avatar" 8 | import { Tooltip, Emmys } from "./Examples" 9 | 10 | import { 11 | Annotation, 12 | SubjectCircle, 13 | ConnectorElbow, 14 | ConnectorEndDot, 15 | Note 16 | } from "../components/index" 17 | 18 | import { DonutIcon } from "./Icons" 19 | import theme from "./theme" 20 | import { dedent } from "dentist" 21 | import ReactMarkdown from "react-markdown" 22 | import AlertIcon from "material-ui/svg-icons/alert/error-outline" 23 | 24 | const Title = ({ text, id }) => { 25 | return ( 26 |

27 | # {text} 28 |

29 | ) 30 | } 31 | 32 | export const Code = ({ children }) => { 33 | return ( 34 |
 35 |       {dedent(children)}
 36 |     
37 | ) 38 | } 39 | 40 | export function Introduction() { 41 | return ( 42 |
43 | 44 | <p> 45 | Annotations{" "} 46 | <strong> 47 | establish context, and direct our users to insights and anomalies 48 | </strong> 49 | . I made <a href="http://d3-annotation.susielu.com/">d3-annotation</a>{" "} 50 | to tackle this problem, however most of our apps are built in React. The 51 | design is heavily based on d3-annotation, with{" "} 52 | <a href="#migrating">a few differences</a>, two new annotation types, 53 | and easy to compose annotations. 54 | </p> 55 | 56 | <p> 57 | Use react-annotation with built-in annotation types, or extend it to 58 | make custom annotations. It is made for SVG. 59 | </p> 60 | <p> 61 | Contact me through the{" "} 62 | <a href="https://github.com/susielu/react-annotation/">github repo</a>{" "} 63 | or <a href="https://twitter.com/DataToViz">twitter</a>. 64 | </p> 65 | </section> 66 | ) 67 | } 68 | 69 | export function Setup() { 70 | return ( 71 | <section> 72 | <Title text={"Setup"} id="setup" /> 73 | <h3>Using NPM</h3> 74 | <p>You can add the react-annotation as a node module by running:</p> 75 | <Code>{"npm i react-annotation -S"}</Code> 76 | If you're new to using React, I suggest using{" "} 77 | <a href="https://github.com/facebookincubator/create-react-app"> 78 | react-create-app 79 | </a>{" "} 80 | to start your project. 81 | </section> 82 | ) 83 | } 84 | 85 | export function Types() { 86 | const source = ` 87 | All annotations are made of just three parts, a **note**, a **connector**, and a **subject**. 88 | 89 | <img alt="Anatomy of an annotation" src="img/anatomy.png" /> 90 | 91 | They are the foundational blocks of this library. Explore the UI below to understand the built-in types. 92 | ` 93 | 94 | return ( 95 | <section> 96 | <Title text="Annotation Types" id="types" /> 97 | <ReactMarkdown source={source} /> 98 | <TypesUI /> 99 | </section> 100 | ) 101 | } 102 | 103 | export function AnnotationTypesAPI() { 104 | const source = ` 105 | ### **_Built-in Annotations_** 106 | Built-in annotations are a set of preset Subjects, Connectors, and Notes as seen in the [Annotation Types](#types) section. 107 | 108 | All built-in annotation types have the following props 109 | - **x (number)**: X position of the subject and one end of the connector 110 | - **y (number)**: Y position of the subject and one end of the connector 111 | - **dx (number)**: X Position of the note and one end of the connector, as an offset from x,y 112 | - **dx (number)**: Y Position of the note and one end of the connector, as an offset from x,y 113 | - **nx (number)**: X Position of the note and one end of the connector, as the raw x,y position **not** an offset 114 | - **ny (number)**: Y Position of the note and one end of the connector, as the raw x,y position **not** an offset 115 | - **color(string)**: A color string that will be applied to the annotation. This color can be overridden via css 116 | - **events(object)**: An object with [react event handlers](https://reactjs.org/docs/handling-events.html) as keys and functions to handle those events. Each handler is bound with the annotation context and has the prarameters: annotation props, annotation state, event 117 | - **editMode(boolean)**: Turns on handles for moving annotations, the following are only trigged with editMode on: 118 | - **onDragStart(function)**: Passes the current props of the annotation when dragging starts 119 | - **onDrag(function)**: Passes the current props of the annotation while dragging 120 | - **onDragEnd(function)**: Passes the current props of the annotation when dragging ends 121 | - **disable ([string])**: takes the values 'connector', 'subject', and 'note'. Pass them in this array if you want to disable those parts from rendering 122 | - **connector (object with the following properties and (values))** 123 | - **type (string, "line", "elbow", or "curve")** 124 | - **end (string, "dot", or "arrow")** 125 | - **curve (function):** Made to use a curve function from [d3-shape](https://github.com/d3/d3-shape). Defaults to \`curveCatmullRom\`. 126 | - **points (array[[x,y],[x,y]...])**: Anchor points for the curve function 127 | - **endScale (number)**: A multiplying factor for sizing the connector end 128 | - **note (object with the following properties and (values))**: 129 | - **title (string)** 130 | - **label (string)** 131 | - **padding (number)** 132 | - **bgPadding (number or Object with one or more options of top, bottom, left, or right)**: this allows you to add more of a padding to the rectangle behind the text element, only available in version 1.3.0 and higher 133 | - **orientation (string, "leftRight" or "topBottom")**: Determines based on the dx, and dy, which direction to orient the \`Note\`. Default is set to \`"topBottom"\` 134 | - **lineType (string, "vertical" or "horizontal")**: Creates a line along the edge of the note text. **Please Note** if you set this to \`"vertical"\` then \`orientation\` is fixed at \`"leftRight"\` and vice versa if it is \`"horizontal"\` then \`orientation\` is fixed at \`"topBottom"\` 135 | - **align (string, "left", "right", "middle", "top", "bottom", "dynamic")**: When the orientation is set to \`"topBottom"\` or lineType is set to \`"horiztonal"\` you can align the note with \`"top"\`, \`"bottom"\`, \`"middle"\`, or \`"dynamic"\`. When the orientation is set to \`"leftRight"\` or \`lineType\` is set to \`"vertical"\` you can align the note with \`"left"\`, \`"right"\`, \`"middle"\`, or \`"dynamic"\`. 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 136 | - **color (string)**: Color string, inherited from Annotation but can be customized by directly adding to Note as a prop 137 | - **titleColor (string)**: Color string, inherited from Annotation but can be customized by directly adding to Note as a prop, overrides color property 138 | - **labelColor (string)**: Color string, inherited from Annotation but can be customized by directly adding to Note as a prop, overrides color property 139 | - **wrapSplitter (string or regex)**: A string if you want to customize the way your text is split into new lines, such as manual breaks on new lines 140 | - **subject (object)**: The following section details the props that can be sent to each Annotation Type's subject property 141 | 142 | Specific variables can be sent as properties of the \`subject\` 143 | 144 | **<AnnotationCalloutCircle />** 145 | - **radius (number)**: Radius of circle 146 | - **radiusPadding (number)**: Padding outside of circle, affects spacing between the circle and the start of the connector 147 | - **innerRadius (number)**: Inner radius to make a ring annotation 148 | - **outerRadius (number)**: Outer radius to make a ring annotation 149 | 150 | **<AnnotationCalloutRect />** 151 | - **width (number)**: Accepts negative and positive values 152 | - **height (number)**: Accepts negative and positive values 153 | 154 | 155 | **<AnnotationXYThreshold />** 156 | - **x1, x2 or y1, y2 (number)**: x1, x2 for a horizontal line, y1, y2 for a vertical line 157 | 158 | **<AnnotationCalloutCustom />** 159 | - **customID (string: Required)**: Needed for masking the connector by the subject, must be a unique DOM id for the entire page. 160 | - **custom ([array of JSX SVG shapes])**: Array of JSX SVG shapes that are used to compose the custom element. 161 | - **transform (SVG transform string)**: Convenience if you need to offset your custom shape 162 | 163 | **<AnnotationBracket />** 164 | - **width or height (number)**: Using width creates a horizontal bracket, using a height creates a vertical bracket 165 | - **depth (number)**: How far the bracket pops out from the corners. Defaults to 20. 166 | - **type (string, "square" or "curly")**: Type of bracket 167 | 168 | **<AnnotationBadge />**: this is the only base annotation that doesn't have a connector or note 169 | - **text (string)**: Text placed in the center of the badge 170 | - **radius (number)**: Defaults to 14. 171 | - **topBottom (string, "top" or "bottom")**: Location, can be combined with leftRight to offset the badge into a corner such as the top right corner. Default places the badge in the center. 172 | - **leftRight (string, "left" or "right")**: Location, can be combined with topBottom to offset the badge into a corner such as the top right corner. Default places the badge in the center. 173 | 174 | 175 | These built-in types do not have a Subject 176 | - **<AnnotationLabel />** 177 | - **<AnnotationCallout />** 178 | - **<AnnotationCalloutElbow />** 179 | - **<AnnotationCalloutCurve />** 180 | 181 | 182 | ` 183 | return ( 184 | <section> 185 | <Title text="API" id="types-api" /> 186 | <ReactMarkdown source={source} /> 187 | </section> 188 | ) 189 | } 190 | 191 | export function ExtendingTypes() { 192 | const x = 100 193 | const y = 50 194 | 195 | const circle = ( 196 | <circle cx={x} cy={y} r={7} fill="none" stroke={theme.accent} /> 197 | ) 198 | 199 | const NoteCircle = ({ x, y, color }) => { 200 | return <circle cx={x} cy={y} r={3} fill={color} /> 201 | } 202 | 203 | return ( 204 | <section> 205 | <Title text="Composable Annotations" id="composable" /> 206 | <p> 207 | In <code>react-annotation</code> it is easy to add custom elements to an 208 | annotation. All annotations are composable with the different subjects, 209 | connectors, and notes. 210 | </p> 211 | <svg height={300} className="viz"> 212 | <Annotation 213 | x={x} 214 | y={y} 215 | dx={100} 216 | dy={50} 217 | radius={35} 218 | color={theme.accent} 219 | title="Custom annotation" 220 | label="Donut annotations be free!" 221 | events={{ 222 | onClick: (props, state, event) => { 223 | console.log(props, state, event) 224 | } 225 | }} 226 | > 227 | <DonutIcon /> 228 | <SubjectCircle /> 229 | <ConnectorElbow> 230 | <ConnectorEndDot /> 231 | </ConnectorElbow> 232 | <NoteCircle /> 233 | {circle} 234 | <Note align="middle" lineType="vertical" padding={10} /> 235 | </Annotation> 236 | </svg> 237 | <Code> 238 | {` 239 | import { Annotation, SubjectCircle, ConnectorElbow, ConnectorEndDot, 240 | Note } from 'react-annotation' 241 | 242 | const x = 100 243 | const y = 50 244 | 245 | //You can customize just by using the same 246 | //values you would pass to annotation 247 | const circle = ( 248 | <circle cx={x} cy={y} r={7} fill="none" stroke={theme.accent} /> 249 | ) 250 | 251 | //Or you can use a render function that 252 | //inherits all of the props from the parent 253 | //Anotation element 254 | const NoteCircle = ({ x, y, color }) => { 255 | return <circle cx={x} cy={y} r={3} fill={color} /> 256 | } 257 | 258 | <Annotation 259 | x={x} 260 | y={y} 261 | dx={100} 262 | dy={50} 263 | radius={35} 264 | color={theme.accent} 265 | title="Custom annotation" 266 | label="Donut annotations be free!" 267 | events={{ 268 | onClick: (props, state, event) => { 269 | console.log(props, state, event) 270 | } 271 | }} 272 | > 273 | <DonutIcon /> 274 | <SubjectCircle /> 275 | <ConnectorElbow> 276 | <ConnectorEndDot /> 277 | </ConnectorElbow> 278 | 279 | //Two custom elements added to this annotation 280 | <NoteCircle /> 281 | {circle} 282 | 283 | <Note align="middle" lineType="vertical" padding={10} /> 284 | </Annotation>`} 285 | </Code> 286 | </section> 287 | ) 288 | } 289 | 290 | // export function d3Translator () { 291 | // return <section> 292 | // <Title text="" 293 | // </section> 294 | // } 295 | 296 | export function InPractice() { 297 | const source1 = ` 298 | ### Selecting Elements 299 | 300 | - All of the visible shapes (aside from the edit handles) in the default annotations are **paths** 301 | - Hierarchy of classes: 302 | ![Annotation classes](img/classes.png) 303 | - Within the g.annotation-note-content there could be three additional elements: ${"`text.annotation-note-label`"}, ${"`text.annotation-note-title`"}, ${"`rect.annotation-note-bg`"} 304 | 305 | - There is also a color property on annotations that will easily change the color of the entire annotation. 306 | 307 | ### Tips 308 | 309 | - 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 310 | - There is an invisible rect (${"`rect.annotation-note-bg`"}) behind the text in the notes as a helper for more click area etc. 311 | - If you are importing custom fonts, you may notice the annotations don't load perfectly with text wrapping and alignment. To fix that see [this example](https://github.com/susielu/d3-annotation/issues/33). 312 | 313 | ### Examples in Viz Frameworks 314 | 315 | Semiotic 316 | - [Example](https://emeeks.github.io/semiotic/#/semiotic/annotations) with an AnnotationCalloutCircle, AnnotationXYThreshold, and AnnotationClloutElbow 317 | - [Wiki](https://github.com/emeeks/semiotic/wiki/Using-Annotations) on using annotations 318 | 319 | Reach out if you add examples to your framework and would like them listed here. 320 | 321 | ` 322 | 323 | const source2 = ` 324 | Differences from [d3-annotation](http://d3-annotation.susielu.com/) 325 | - Component names are capitalized so \`annotationLabel\` in d3-annotation corresponds with \`AnnotationLabel\` in react-annotation 326 | - There is no longer an annotation collection. This felt unnecessary since the annotation data was being pushed into each annotation directly 327 | - There is no longer accessors. The assumption is that you would map over your annotations and give them the x, y, with any type of scaling functions. 328 | - AnnotationBadge in react-annotation takes \`leftRight\` and \`topBottom\` parameters instead of the corresponding \`x\` and \`y\` parameteres in d3-annotation 329 | 330 | If you want to update your code from d3-annotation to react-annotation, here's an example of translation: 331 | 332 | ` 333 | 334 | return ( 335 | <section> 336 | <Title text="In Practice" id="in-practice" /> 337 | <ReactMarkdown source={source1} /> 338 | <h3 id="migrating">Migrating from d3-annotation</h3> 339 | <ReactMarkdown source={source2} /> 340 | <Code> 341 | {` 342 | /* ---------------------------- */ 343 | /* Annotations in d3-annotation */ 344 | /* ---------------------------- */ 345 | 346 | const annotations = [{ 347 | note: { label: "Steve Jobs Returns" }, 348 | subject: { 349 | y1: margin.top, 350 | y2: height - margin.bottom 351 | }, 352 | y: margin.top, 353 | data: { x: "7/9/1997"} //position the x based on an x scale 354 | }, 355 | { 356 | note: { label: "iPod Release"}, 357 | subject: { 358 | y1: margin.top, 359 | y2: height - margin.bottom 360 | }, 361 | y: margin.top, 362 | data: { x: "10/23/2001"} 363 | }, 364 | { 365 | note: { label: "Stock Split 2:1", 366 | lineType:"none", 367 | orientation: "leftRight", 368 | align: "middle" }, 369 | className: "anomaly", 370 | type: d3.annotationCalloutCircle, 371 | subject: { radius: 35 }, 372 | data: { x: "6/21/2000", y: 76}, 373 | dx: 40 374 | }, 375 | ] 376 | 377 | const type = d3.annotationCustomType( 378 | d3.annotationXYThreshold, 379 | {"note":{ 380 | "lineType":"none", 381 | "orientation": "top", 382 | "align":"middle"} 383 | } 384 | ) 385 | 386 | const makeAnnotations = d3.annotation() 387 | .type(type) 388 | //Gives you access to any data objects in the annotations array 389 | .accessors({ 390 | x: function(d){ return x(new Date(d.x))}, 391 | y: function(d){ return y(d.y) } 392 | }) 393 | .annotations(annotations) 394 | .textWrap(30) 395 | 396 | d3.select("svg") 397 | .append("g") 398 | .attr("class", "annotation-group") 399 | .call(makeAnnotations) 400 | 401 | /* ------------------------------- */ 402 | /* Annotations in react-annotation */ 403 | /* ------------------------------- */ 404 | 405 | import { AnnotationXYThreshold, AnnotationCalloutCircle } from "react-annotation" 406 | 407 | const makeAnnotations = () => { 408 | const annotations = [{ 409 | note: { label: "Steve Jobs Returns", 410 | orientation: "top"}, 411 | subject: { 412 | y1: margin.top, 413 | y2: height - margin.bottom 414 | }, 415 | y: margin.top, 416 | data: { x: "7/9/1997"}, 417 | type: AnnotationXYThreshold 418 | }, 419 | { 420 | note: { label: "iPod Release", 421 | orientation: "top"}, 422 | subject: { 423 | y1: margin.top, 424 | y2: height - margin.bottom 425 | }, 426 | y: margin.top, 427 | data: { x: "10/23/2001"}, 428 | type: AnnotationXYThreshold 429 | }, 430 | { 431 | note: { label: "Stock Split 2:1", 432 | orientation: "leftRight"}, 433 | className: "anomaly", 434 | subject: { radius: 35 }, 435 | data: { x: "6/21/2000", y: 76}, 436 | dx: 40 437 | type: AnnotationCalloutCircle, 438 | }].map(a => { 439 | const Annotation = a.type 440 | const { note, subject, y, dx, data } = a 441 | note.wrap = 30 442 | note.lineType = null 443 | note.align = "middle" 444 | return <Annotation 445 | x={x(new Date(data.x))} 446 | y={data.y && y(data.y) || y} 447 | dx={dx} 448 | note={note} 449 | subject={subject} 450 | /> 451 | }) 452 | 453 | return <svg> 454 | <g className="annotation-group"> 455 | {annotations} 456 | </g> 457 | </svg> 458 | } 459 | `} 460 | </Code> 461 | </section> 462 | ) 463 | } 464 | 465 | export function Examples() { 466 | return ( 467 | <section> 468 | <Title text="Examples" id="examples" /> 469 | <Tooltip /> 470 | <hr /> 471 | <h3>Annotations for labels, axis, and emphasis</h3> 472 | 473 | <Emmys /> 474 | </section> 475 | ) 476 | } 477 | 478 | export function API() { 479 | const source1 = ` 480 | 481 | ### **_Composable Annotations_** 482 | When using composable annotations, you have an Annotation component. Then a subject, a connector, and a note can be its children. 483 | 484 | Examples are in the [Annotation Types UI](#types), and the [Composable Annotations](#composable) sections. 485 | 486 | ### Annotation 487 | **<Annotation />** 488 | 489 | - **x (number)**: X position of the subject and one end of the connector 490 | - **y (number)**: Y position of the subject and one end of the connector 491 | - **dx (number)**: X Position of the note and one end of the connector, as an offset from x,y 492 | - **dx (number)**: Y Position of the note and one end of the connector, as an offset from x,y 493 | - **nx (number)**: X Position of the note and one end of the connector, as the raw x,y position **not** an offset 494 | - **ny (number)**: Y Position of the note and one end of the connector, as the raw x,y position **not** an offset 495 | - **color(string)**: A color string that will be applied to the annotation. This color can be overridden via css or inline-styles 496 | - **events(object)**: An object with [react event hanlders](https://reactjs.org/docs/handling-events.html) as keys and functions to handle those events. Each handler is bound with the annotation context and has the prarameters: annotation props, annotation state, event 497 | 498 | **<EditableAnnotation />** 499 | 500 | All the same props as \`Annotation\`, with built in anchors for dragging the annotation 501 | 502 | - **onDragStart(function)**: Passes the current props of the annotation when dragging starts 503 | - **onDrag(function)**: Passes the current props of the annotation while dragging 504 | - **onDragEnd(function)**: Passes the current props of the annotation when dragging ends 505 | 506 | 507 | ` 508 | 509 | const source3 = ` 510 | ### Subjects 511 | 512 | **<SubjectCircle />** 513 | - **radius (number)**: Radius of circle 514 | - **radiusPadding (number)**: Padding outside of circle, affects spacing between the circle and the start of the connector 515 | - **innerRadius (number)**: Inner radius to make a ring annotation 516 | - **outerRadius (number)**: Outer radius to make a ring annotation 517 | - **fill (number)**: Accepts color string 518 | - **fillOpacity (number)**: Accepts opacity value from 0-1 519 | 520 | **<SubjectRect />** 521 | - **width (number)**: Accepts negative and positive values 522 | - **height (number)**: Accepts negative and positive values 523 | - **fill (number)**: Accepts color string 524 | - **fillOpacity (number)**: Accepts opacity value from 0-1 525 | 526 | 527 | **<SubjectThreshold />** 528 | - **x1, x2 or y1, y2 (number)**: x1, x2 for a horizontal line, y1, y2 for a vertical line 529 | 530 | **<SubjectCustom />** 531 | - **customID (string: Required)**: Needed for masking the connector by the subject, must be a unique DOM id for the entire page 532 | - **custom ([array of JSX SVG shapes])**: Array of JSX SVG shapes that are used to compose the custom element 533 | - **transform (SVG transform string)**: Convenience if you need to offset your custom shape 534 | 535 | **<SubjectBracket />** 536 | - **width or height (number)**: Using width creates a horizontal bracket, using a height creates a vertical bracket 537 | - **depth (number)**: How far the bracket pops out from the corners, defaults to 20 538 | - **type (string, "square" or "curly")**: Type of bracket 539 | 540 | **<SubjectBadge />** 541 | - **text (string)**: Text placed in the center of the badge 542 | - **radius (number)**: Defaults to 14 543 | - **topBottom (string, "top" or "bottom")**: Location, can be combined with leftRight to offset the badge into a corner such as the top right corner. Default places the badge in the center 544 | - **leftRight (string, "left" or "right")**: Location, can be combined with topBottom to offset the badge into a corner such as the top right corner. Default places the badge in the center 545 | 546 | ` 547 | 548 | const connectorText = `### Connectors 549 | 550 | All connectors are automatically passed the x, y and dx, dy positions from \`Annotation\`. 551 | 552 | - **x (number)**: X position of the subject and one end of the connector 553 | - **y (number)**: Y position of the subject and one end of the connector 554 | - **dx (number)**: X Position of the note and one end of the connector, as an offset from x,y 555 | - **dx (number)**: Y Position of the note and one end of the connector, as an offset from x,y 556 | 557 | **<ConnectorLine />** no additional props 558 | 559 | 560 | **<ConnectorElbow />** no additional props 561 | 562 | 563 | **<ConnectorCurve />** 564 | - **curve (function):** Made to use a curve function from [d3-shape](https://github.com/d3/d3-shape), defaults to \`curveCatmullRom\`. 565 | - **points (array[[x,y],[x,y]...])**: Anchor points for the curve function 566 | 567 | 568 | ### ConnectorEnds 569 | Passed as children to \`Connectors\` to add an arrow or dot to the end of a connector 570 | 571 | All connector ends have a \`scale\` property to adjust the size. 572 | 573 | - **scale (number)**: A multiplying factor for sizing the connector end 574 | 575 | **<ConnectorEndArrow />** no additional props 576 | 577 | **<ConnectorEndDot />** no additional props 578 | 579 | 580 | ` 581 | 582 | const noteText = ` 583 | ### Notes 584 | 585 | **<Note />** 586 | - **title (string)** 587 | - **label (string)** 588 | - **padding (number)** 589 | - **bgPadding (number or Object with one or more options of top, bottom, left, or right)**: this allows you to add more of a padding to the rectangle behind the text element, only available in version 1.3.0 and higher 590 | - **orientation (string, "leftRight" or "topBottom")**: Determines based on the dx, and dy, which direction to orient the \`Note\`. Default is set to \`"topBottom\` 591 | - **lineType (string, "vertical" or "horizontal")**: Creates a line along the edge of the note text. **Please Note** if you set this to \`"vertical"\` then \`orientation\` is fixed at \`"leftRight"\` and vice versa if it is \`"horizontal"\` then \`orientation\` is fixed at \`"topBottom"\` 592 | 593 | - **align (string, "left", "right", "middle", "top", "bottom", "dynamic")**: When the orientation is set to \`"topBottom"\` or lineType is set to \`"horiztonal"\` you can align the note with \`"top"\`, \`"bottom"\`, \`"middle"\`, or \`"dynamic"\`. When the orientation is set to \`"leftRight"\` or \`lineType\` is set to \`"vertical"\` you can align the note with \`"left"\`, \`"right"\`, \`"middle"\`, or \`"dynamic"\`. 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 594 | - **color (string)**: Color string, inherited from Annotation but can be customized by directly adding to Note as a prop 595 | - **titleColor (string)**: Color string, inherited from Annotation but can be customized by directly adding to Note as a prop, overrides color property 596 | - **labelColor (string)**: Color string, inherited from Annotation but can be customized by directly adding to Note as a prop, overrides color property 597 | - **wrapSplitter (string or regex)**: A string if you want to customize the way your text is split into new lines, such as manual breaks on new lines 598 | 599 | **<BracketNote />** use with <SubjectBracket /> 600 | - This Note has all of the same properties as the regular Note, except it has dynamic positioning of the dx, and dy depending on the settings given to \`SubjectBracket\` 601 | ` 602 | return ( 603 | <section> 604 | <Title text="API" id="composable-api" /> 605 | <ReactMarkdown source={source1} /> 606 | <Chip> 607 | <Avatar backgroundColor={theme.accent} icon={<AlertIcon />} /> 608 | Please Note 609 | </Chip> 610 | <p> 611 | Subjects are a special case because their attributes affect both the 612 | subject and the connector. The use the components below with the 613 | Annotation components, you should pass these props to{" "} 614 | <code>Annotation</code>, which the sends them down to the{" "} 615 | <code>Subject</code>. For Example:{" "} 616 | </p> 617 | <Code>{` 618 | <Annotation 619 | x={20} 620 | y={100} 621 | dx={20} 622 | 623 | //Subject props given to Annotation. 624 | //They are passed down to the Subject. 625 | radius={30} 626 | > 627 | <SubjectCircle /> 628 | </Annotation> 629 | `}</Code> 630 | <ReactMarkdown source={source3} /> 631 | <ReactMarkdown source={connectorText} /> 632 | <ReactMarkdown source={noteText} /> 633 | </section> 634 | ) 635 | } 636 | 637 | export function Notes() { 638 | const source = ` 639 | Inspired by all of the [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). 640 | 641 | Thumbs up to [Nunito](https://fonts.google.com/specimen/Nunito) and [Bungee](https://fonts.google.com/specimen/Bungee) via Google Fonts and [Material UI](http://www.material-ui.com/#/) for making the docs site building a breeze. 642 | 643 | ` 644 | 645 | return ( 646 | <section> 647 | <Title text="Notes" id="notes" /> 648 | <ReactMarkdown source={source} /> 649 | </section> 650 | ) 651 | } 652 | -------------------------------------------------------------------------------- /src/docs/Types.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Card, CardHeader, CardTitle, CardText } from "material-ui/Card" 3 | import Toggle from "material-ui/Toggle" 4 | import TextField from "material-ui/TextField" 5 | import FloatingActionButton from "material-ui/FloatingActionButton" 6 | 7 | import Annotations from "../components/index" 8 | import theme from "./theme" 9 | import { Code } from "./Sections" 10 | import { PuppersIcon } from "./Icons" 11 | 12 | const types = { 13 | AnnotationLabel: { 14 | typeSettings: { 15 | className: "show-bg", 16 | note: { 17 | align: "middle", 18 | orientation: "topBottom", 19 | bgPadding: 20, 20 | padding: 15, 21 | titleColor: "#59039c" 22 | }, 23 | connector: { type: "line" } 24 | }, 25 | summary: "A centered label annotation", 26 | img: "a-label.png" 27 | }, 28 | AnnotationCallout: { 29 | typeSettings: { 30 | className: "show-bg", 31 | note: { 32 | lineType: "horizontal", 33 | bgPadding: { top: 15, left: 10, right: 10, bottom: 10 }, 34 | padding: 15, 35 | titleColor: "#59039c" 36 | }, 37 | connector: { type: "line" } 38 | }, 39 | summary: "Adds a line along the note", 40 | img: "a-callout.png" 41 | }, 42 | AnnotationCalloutElbow: { 43 | typeSettings: { 44 | note: { lineType: "horizontal" }, 45 | connector: { type: "elbow" } 46 | }, 47 | summary: "Keeps connector at 45 and 90 degree angles", 48 | img: "a-elbow.png" 49 | }, 50 | AnnotationCalloutCircle: { 51 | typeSettings: { 52 | note: { lineType: "horizontal" }, 53 | connector: { type: "elbow" } 54 | }, 55 | summary: "Subject options: radius, innerRadius, outerRadius, ", 56 | summaryCont: "radiusPadding", 57 | subject: { 58 | radius: 50, 59 | radiusPadding: 5 60 | }, 61 | img: "a-circle.png" 62 | }, 63 | AnnotationCalloutRect: { 64 | typeSettings: { 65 | note: { lineType: "horizontal" }, 66 | connector: { type: "elbow" } 67 | }, 68 | summary: "Subject options: width, height", 69 | subject: { 70 | width: -50, 71 | height: 100 72 | }, 73 | img: "a-rect.png" 74 | }, 75 | AnnotationCalloutCurve: { 76 | typeSettings: { 77 | note: { lineType: "horizontal" }, 78 | connector: { type: "curve" } 79 | }, 80 | summary: "Connector options: curve, ", 81 | summaryCont: "points(array of [x,y]s or number)", 82 | img: "a-curve.png" 83 | }, 84 | AnnotationXYThreshold: { 85 | typeSettings: { 86 | note: { lineType: "horizontal" }, 87 | connector: { type: "elbow" } 88 | }, 89 | summary: "Subject options: x1, x2 or y1, y2", 90 | subject: { 91 | x1: 0, 92 | x2: 1000 93 | }, 94 | img: "a-threshold.png" 95 | }, 96 | AnnotationBadge: { 97 | typeSettings: { 98 | note: { lineType: "horizontal" }, 99 | connector: { type: "elbow" } 100 | }, 101 | summary: "Subject options: radius, text, x:left or right, y:top or bottom", 102 | subject: { 103 | radius: 14, 104 | text: "A" 105 | }, 106 | img: "a-badge.png" 107 | }, 108 | AnnotationBracket: { 109 | typeSettings: { 110 | note: {}, 111 | connector: { type: "elbow" } 112 | }, 113 | summary: "Subject options: height or width, depth, type (square or curly)", 114 | subject: { 115 | height: 100, 116 | type: "square" 117 | }, 118 | img: "a-bracket.png" 119 | }, 120 | AnnotationCalloutCustom: { 121 | typeSettings: { 122 | note: { lineType: "horizontal" }, 123 | connector: { type: "elbow" } 124 | }, 125 | summary: "Subject options: radius, text, x:left or right, y:top or bottom", 126 | subject: { 127 | radius: 40, 128 | custom: PuppersIcon(), 129 | customID: "puppers", 130 | transform: "translate(-32, -32)" 131 | }, 132 | img: "a-custom.png" 133 | } 134 | } 135 | 136 | const typesOrder = [ 137 | "AnnotationLabel", 138 | "AnnotationCallout", 139 | "AnnotationCalloutElbow", 140 | "AnnotationCalloutCurve", 141 | "AnnotationCalloutCircle", 142 | "AnnotationCalloutRect", 143 | "AnnotationXYThreshold", 144 | "AnnotationBracket", 145 | "AnnotationCalloutCustom", 146 | "AnnotationBadge" 147 | ] 148 | 149 | export default class Types extends React.Component { 150 | state = { 151 | name: "AnnotationLabel", 152 | description: types.AnnotationLabel.summary, 153 | editMode: true, 154 | connector: {}, 155 | note: {}, 156 | subject: {} 157 | } 158 | updateType(t) { 159 | this.setState({ 160 | name: t, 161 | description: types[t].summary, 162 | connector: {}, 163 | note: {}, 164 | subject: {} 165 | }) 166 | } 167 | 168 | updateNote(property, value) { 169 | const settings = Object.assign({}, this.state.note) 170 | settings[property] = value 171 | this.setState({ 172 | note: settings 173 | }) 174 | } 175 | 176 | updateConnector(property, value) { 177 | const settings = Object.assign({}, this.state.connector) 178 | settings[property] = value 179 | this.setState({ 180 | connector: settings 181 | }) 182 | } 183 | 184 | updateSubject(property, value) { 185 | const settings = Object.assign({}, this.state.subject) 186 | settings[property] = value 187 | this.setState({ 188 | subject: settings 189 | }) 190 | } 191 | 192 | render() { 193 | const name = this.state.name 194 | const imgs = typesOrder.map(i => { 195 | const t = types[i] 196 | return ( 197 | <img 198 | key={i} 199 | alt={t.img} 200 | className={`icon ${name === i ? "selected" : ""}`} 201 | onClick={this.updateType.bind(this, i)} 202 | src={`img/${t.img}`} 203 | /> 204 | ) 205 | }) 206 | 207 | const Annotation = Annotations[name] 208 | const t = types[name] 209 | const subject = this.state.subject 210 | const connector = this.state.connector 211 | 212 | const note = Object.assign( 213 | {}, 214 | { 215 | title: "Annotations :)", 216 | label: "Longer text to show text wrapping" 217 | }, 218 | t.typeSettings.note, 219 | this.state.note 220 | ) 221 | 222 | const subjectJoined = Object.assign({}, t.subject, subject) 223 | const connectorJoined = Object.assign( 224 | {}, 225 | t.typeSettings.connector, 226 | connector 227 | ) 228 | 229 | const noteJoined = Object.assign({}, t.typeSettings.note, this.state.note) 230 | 231 | const ConnectorCode = () => { 232 | const c = 233 | connectorJoined.type === "line" 234 | ? "ConnectorLine" 235 | : connectorJoined.type === "elbow" 236 | ? "ConnectorElbow" 237 | : "ConnectorCurve" 238 | 239 | const e = 240 | connectorJoined.end === "arrow" 241 | ? "ConnectorEndArrow" 242 | : connectorJoined.end === "dot" 243 | ? "ConnectorEndDot" 244 | : undefined 245 | 246 | if (e) { 247 | return ` 248 | <${c} > 249 | <${e} /> 250 | </${c} > 251 | ` 252 | } 253 | return `<${c} />` 254 | } 255 | 256 | const SubjectCode = () => { 257 | if (!t.subject) { 258 | return "" 259 | } 260 | 261 | const s = 262 | name === "AnnotationCalloutCircle" 263 | ? "SubjectCircle" 264 | : name === "AnnotationXYThreshold" 265 | ? "SubjectThreshold" 266 | : name === "AnnotationBadge" 267 | ? "SubjectBadge" 268 | : name === "AnnotationCalloutRect" 269 | ? "SubjectRect" 270 | : undefined 271 | 272 | if (s) { 273 | return `<${s} />` 274 | } 275 | } 276 | 277 | let orientation 278 | 279 | if (!noteJoined.lineType) { 280 | orientation = ( 281 | <div> 282 | <p>Orientation</p> 283 | <img 284 | className={`tiny-icon ${ 285 | note.orientation === "topBottom" ? "selected" : "" 286 | }`} 287 | onClick={this.updateNote.bind(this, "orientation", "topBottom")} 288 | src="img/topBottom.png" 289 | /> 290 | <img 291 | className={`tiny-icon ${ 292 | note.orientation === "leftRight" ? "selected" : "" 293 | }`} 294 | onClick={this.updateNote.bind(this, "orientation", "leftRight")} 295 | src="img/leftRight.png" 296 | /> 297 | </div> 298 | ) 299 | } 300 | 301 | let alignFirst = "left" 302 | let alignSecond = "right" 303 | 304 | if ( 305 | noteJoined.lineType === "vertical" || 306 | noteJoined.orientation === "leftRight" 307 | ) { 308 | alignFirst = "top" 309 | alignSecond = "bottom" 310 | } 311 | 312 | let bracketType 313 | 314 | if (this.state.name === "AnnotationBracket") { 315 | bracketType = ( 316 | <div style={{ position: "absolute", top: 20, right: 30 }}> 317 | <FloatingActionButton 318 | onTouchTap={this.updateSubject.bind(this, "type", "square")} 319 | mini={true} 320 | secondary={this.state.subject.type === "curly" ? true : false} 321 | iconStyle={{ 322 | color: "white", 323 | lineHeight: ".8em", 324 | fontSize: "1.4em" 325 | }} 326 | > 327 | {"]"} 328 | </FloatingActionButton> 329 | <FloatingActionButton 330 | onTouchTap={this.updateSubject.bind(this, "type", "curly")} 331 | mini={true} 332 | secondary={this.state.subject.type !== "curly" ? true : false} 333 | iconStyle={{ 334 | color: "white", 335 | lineHeight: ".8em", 336 | fontSize: "1.4em" 337 | }} 338 | > 339 | {"}"} 340 | </FloatingActionButton> 341 | </div> 342 | ) 343 | } 344 | 345 | return ( 346 | <div> 347 | <Card className="types-ui" initiallyExpanded={true}> 348 | <CardHeader 349 | title="Presets" 350 | style={{ fontWeight: "bold", borderBottom: "1px solid #d6daea" }} 351 | > 352 | <Toggle 353 | label="Edit Mode" 354 | toggled={this.state.editMode} 355 | onToggle={() => 356 | this.setState({ 357 | editMode: !this.state.editMode 358 | }) 359 | } 360 | style={{ 361 | float: "right", 362 | width: 30, 363 | fontSize: ".8em" 364 | }} 365 | /> 366 | </CardHeader> 367 | <CardTitle>{imgs}</CardTitle> 368 | <CardHeader 369 | title="Options" 370 | style={{ 371 | fontWeight: "bold", 372 | borderTop: "1px solid #d6daea", 373 | borderBottom: "1px solid #d6daea" 374 | }} 375 | showExpandableButton={true} 376 | actAsExpander={true} 377 | /> 378 | <CardText expandable={true} className="row"> 379 | <div className="col-xs-3"> 380 | <b>Note</b> 381 | <p>Line Type</p> 382 | <img 383 | className={`tiny-icon ${!note.lineType ? "selected" : ""}`} 384 | src="img/none.png" 385 | onClick={this.updateNote.bind(this, "lineType", null)} 386 | /> 387 | <img 388 | className={`tiny-icon ${ 389 | note.lineType === "vertical" ? "selected" : "" 390 | }`} 391 | src="img/vertical.png" 392 | onClick={this.updateNote.bind(this, "lineType", "vertical")} 393 | /> 394 | <img 395 | className={`tiny-icon ${ 396 | note.lineType === "horizontal" ? "selected" : "" 397 | }`} 398 | src="img/horizontal.png" 399 | onClick={this.updateNote.bind(this, "lineType", "horizontal")} 400 | /> 401 | {orientation} 402 | </div> 403 | <div className="col-xs-3"> 404 | <br /> 405 | <p>Align</p> 406 | <img 407 | className={`tiny-icon ${!note.align ? "selected" : ""}`} 408 | src="img/dynamic.png" 409 | onClick={this.updateNote.bind(this, "align", null)} 410 | /> 411 | <img 412 | className={`tiny-icon ${ 413 | note.align === "middle" ? "selected" : "" 414 | }`} 415 | src="img/middle.png" 416 | onClick={this.updateNote.bind(this, "align", "middle")} 417 | /> 418 | <img 419 | className={`tiny-icon ${ 420 | note.align === alignFirst ? "selected" : "" 421 | }`} 422 | src={`img/${alignFirst}.png`} 423 | onClick={this.updateNote.bind(this, "align", alignFirst)} 424 | /> 425 | <img 426 | className={`tiny-icon ${ 427 | note.align === alignSecond ? "selected" : "" 428 | }`} 429 | src={`img/${alignSecond}.png`} 430 | onClick={this.updateNote.bind(this, "align", alignSecond)} 431 | /> 432 | </div> 433 | <div className="col-xs-3"> 434 | <TextField 435 | hintText="120" 436 | floatingLabelFixed={true} 437 | floatingLabelShrinkStyle={{ 438 | height: 80 439 | }} 440 | fullWidth={true} 441 | type="number" 442 | onChange={(e, v) => { 443 | this.setState({ 444 | note: Object.assign({}, this.state.note, { 445 | wrap: parseInt(v) || 0 446 | }) 447 | }) 448 | }} 449 | floatingLabelText="Text Wrap (120)" 450 | /> 451 | <TextField 452 | hintText="5" 453 | floatingLabelFixed={true} 454 | fullWidth={true} 455 | type="number" 456 | onChange={(e, v) => { 457 | this.setState({ 458 | note: Object.assign({}, this.state.note, { 459 | padding: parseInt(v) || 0 460 | }) 461 | }) 462 | }} 463 | floatingLabelText="Text Padding (5)" 464 | /> 465 | </div> 466 | <div className="col-xs-3"> 467 | <b>Connector</b> 468 | <p>Type</p> 469 | <img 470 | className={`tiny-icon ${ 471 | connectorJoined.type === "line" ? "selected" : "" 472 | }`} 473 | onClick={this.updateConnector.bind(this, "type", "line")} 474 | src="img/line.png" 475 | /> 476 | <img 477 | className={`tiny-icon ${ 478 | connectorJoined.type === "elbow" ? "selected" : "" 479 | }`} 480 | onClick={this.updateConnector.bind(this, "type", "elbow")} 481 | src="img/elbow.png" 482 | /> 483 | <img 484 | className={`tiny-icon ${ 485 | connectorJoined.type === "curve" ? "selected" : "" 486 | }`} 487 | onClick={this.updateConnector.bind(this, "type", "curve")} 488 | src="img/curve.png" 489 | /> 490 | <p>End</p> 491 | <img 492 | className={`tiny-icon ${ 493 | !connectorJoined.end ? "selected" : "" 494 | }`} 495 | onClick={this.updateConnector.bind(this, "end", null)} 496 | src="img/none.png" 497 | /> 498 | <img 499 | className={`tiny-icon ${ 500 | connectorJoined.end === "arrow" ? "selected" : "" 501 | }`} 502 | onClick={this.updateConnector.bind(this, "end", "arrow")} 503 | src="img/arrow.png" 504 | /> 505 | <img 506 | className={`tiny-icon ${ 507 | connector.end === "dot" ? "selected" : "" 508 | }`} 509 | onClick={this.updateConnector.bind(this, "end", "dot")} 510 | src="img/dot.png" 511 | /> 512 | </div> 513 | </CardText> 514 | </Card> 515 | <h3>Use {name}</h3> 516 | <div style={{ position: "relative" }}> 517 | {bracketType} 518 | <svg className="types viz"> 519 | <g transform="translate(30,60)"> 520 | <text className="title">{name}</text> 521 | <text className="summary" y={30}> 522 | {this.state.description} 523 | </text> 524 | </g> 525 | <Annotation 526 | x={150} 527 | y={170} 528 | dy={name === "AnnotationBracket" ? undefined : 117} 529 | dx={name === "AnnotationBracket" ? undefined : 162} 530 | editMode={this.state.editMode} 531 | subject={subjectJoined} 532 | connector={connector} 533 | className={t.typeSettings.className} 534 | color={theme.accent} 535 | note={note} 536 | /> 537 | <text x="30" y="415" className="summary"> 538 | Code below is ready to use with these setttings 539 | </text> 540 | </svg> 541 | </div> 542 | <Code> 543 | {` 544 | /* This code is UPDATING based on the UI selections above */ 545 | /* With built-in defaults */ 546 | <${name} 547 | x={150} 548 | y={170} 549 | dy={117} 550 | dx={162} 551 | color={"${theme.accent}"} 552 | ${(t.typeSettings.className && 553 | `className="${t.typeSettings.className}" `) || 554 | ""} 555 | ${this.state.editMode ? "editMode={true}" : ""} 556 | ${`note={${JSON.stringify( 557 | Object.assign( 558 | {}, 559 | { 560 | title: "Annotations :)", 561 | label: "Longer text to show text wrapping" 562 | }, 563 | note 564 | ) 565 | ).replace(/,/g, ",\n ")}}`} 566 | ${ 567 | connector.type || connector.end 568 | ? `connector={${JSON.stringify(connector)}}` 569 | : "" 570 | } 571 | ${t.subject ? `subject={${JSON.stringify(t.subject)}}` : ""} 572 | /> 573 | // 574 | /* Or with composable annotation parts */ 575 | 576 | <${this.state.editMode ? `EditableAnnotation` : `Annotation`} 577 | x={150} 578 | y={170} 579 | dy={117} 580 | dx={162} 581 | color={"${theme.accent}"} 582 | 583 | title={"${note.title}"} 584 | label={"${note.label}"} 585 | ${(t.typeSettings.className && 586 | `className="${t.typeSettings.className}"`) || 587 | ""} 588 | ${(t.subject 589 | ? Object.keys(t.subject).map( 590 | k => 591 | `\n ${k}=${ 592 | k !== "text" ? `{${t.subject[k]}}` : `"${t.subject[k]}"` 593 | }` 594 | ) 595 | : [] 596 | ).join("")} 597 | > 598 | ${SubjectCode()} 599 | ${ConnectorCode()} 600 | <Note ${Object.keys(noteJoined) 601 | .map(k => `\n ${k}={${JSON.stringify(noteJoined[k])}}`) 602 | .join("")} /> 603 | </${this.state.editMode ? `EditableAnnotation` : `Annotation`}> 604 | ` 605 | .replace(/^\s*\n/gm, "") 606 | .replace(/\/\//g, "\n")} 607 | </Code> 608 | </div> 609 | ) 610 | } 611 | } 612 | -------------------------------------------------------------------------------- /src/docs/data/yearNetwork.json: -------------------------------------------------------------------------------- 1 | { 2 | "networkLines": [ 3 | { 4 | "network": "AMC", 5 | "line": [ 6 | { "year": "2013", "value": 26 }, 7 | { "year": "2014", "value": 26 }, 8 | { "year": "2015", "value": 24 }, 9 | { "year": "2016", "value": 24 }, 10 | { "year": "2017", "value": 13 } 11 | ], 12 | "total": 113, 13 | "max": 26 14 | }, 15 | 16 | { 17 | "network": "Showtime", 18 | "line": [ 19 | { "year": "2013", "value": 32 }, 20 | { "year": "2014", "value": 24 }, 21 | { "year": "2015", "value": 18 }, 22 | { "year": "2016", "value": 22 }, 23 | { "year": "2017", "value": 15 } 24 | ], 25 | "total": 111, 26 | "max": 32 27 | }, 28 | 29 | { 30 | "network": "ABC", 31 | "line": [ 32 | { "year": "2013", "value": 45 }, 33 | { "year": "2014", "value": 37 }, 34 | { "year": "2015", "value": 42 }, 35 | { "year": "2016", "value": 35 }, 36 | { "year": "2017", "value": 33 } 37 | ], 38 | "total": 192, 39 | "max": 45 40 | }, 41 | { 42 | "network": "Netflix", 43 | "line": [ 44 | { "year": "2013", "value": 14 }, 45 | { "year": "2014", "value": 31 }, 46 | { "year": "2015", "value": 34 }, 47 | { "year": "2016", "value": 54 }, 48 | { "year": "2017", "value": 91 } 49 | ], 50 | "total": 224, 51 | "max": 91 52 | }, 53 | { 54 | "network": "HBO", 55 | "line": [ 56 | { "year": "2013", "value": 109 }, 57 | { "year": "2014", "value": 99 }, 58 | { "year": "2015", "value": 126 }, 59 | { "year": "2016", "value": 94 }, 60 | { "year": "2017", "value": 111 } 61 | ], 62 | "total": 539, 63 | "max": 126 64 | }, 65 | { 66 | "network": "CBS", 67 | "line": [ 68 | { "year": "2013", "value": 54 }, 69 | { "year": "2014", "value": 47 }, 70 | { "year": "2015", "value": 41 }, 71 | { "year": "2016", "value": 35 }, 72 | { "year": "2017", "value": 29 } 73 | ], 74 | "total": 206, 75 | "max": 54 76 | }, 77 | { 78 | "network": "FX Networks", 79 | "line": [ 80 | { "year": "2013", "value": 26 }, 81 | { "year": "2014", "value": 45 }, 82 | { "year": "2015", "value": 39 }, 83 | { "year": "2016", "value": 57 }, 84 | { "year": "2017", "value": 55 } 85 | ], 86 | "total": 222, 87 | "max": 57 88 | }, 89 | { 90 | "network": "NBC", 91 | "line": [ 92 | { "year": "2013", "value": 53 }, 93 | { "year": "2014", "value": 47 }, 94 | { "year": "2015", "value": 43 }, 95 | { "year": "2016", "value": 41 }, 96 | { "year": "2017", "value": 64 } 97 | ], 98 | "total": 248, 99 | "max": 64 100 | }, 101 | { 102 | "network": "FOX", 103 | "line": [ 104 | { "year": "2013", "value": 20 }, 105 | { "year": "2014", "value": 21 }, 106 | { "year": "2015", "value": 36 }, 107 | { "year": "2016", "value": 30 }, 108 | { "year": "2017", "value": 20 } 109 | ], 110 | "total": 127, 111 | "max": 36 112 | }, 113 | 114 | { 115 | "network": "PBS", 116 | "line": [ 117 | { "year": "2013", "value": 25 }, 118 | { "year": "2014", "value": 34 }, 119 | { "year": "2015", "value": 30 }, 120 | { "year": "2016", "value": 26 }, 121 | { "year": "2017", "value": 11 } 122 | ], 123 | "total": 126, 124 | "max": 34 125 | }, 126 | 127 | { 128 | "network": "Nat Geo", 129 | "line": [ 130 | { "year": "2014", "value": 4 }, 131 | { "year": "2015", "value": 4 }, 132 | { "year": "2016", "value": 10 }, 133 | { "year": "2017", "value": 15 } 134 | ], 135 | "total": 33, 136 | "max": 10 137 | }, 138 | { 139 | "network": "Amazon", 140 | "line": [ 141 | { "year": "2015", "value": 12 }, 142 | { "year": "2016", "value": 16 }, 143 | { "year": "2017", "value": 16 } 144 | ], 145 | "total": 44, 146 | "max": 16 147 | }, 148 | 149 | { 150 | "network": "Hulu", 151 | "line": [{ "year": "2016", "value": 2 }, { "year": "2017", "value": 18 }], 152 | "total": 20, 153 | "max": 18 154 | } 155 | ] 156 | } 157 | -------------------------------------------------------------------------------- /src/docs/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --font-color: #668389; 3 | --accent-color: #9610ff; 4 | } 5 | html { 6 | font-size: 14px; 7 | } 8 | 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6 { 15 | font-weight: 300; 16 | font-family: "Bungee", Helvetica, Arial, sans-serif; 17 | } 18 | 19 | h1, 20 | h2, 21 | h3 { 22 | margin-bottom: 1rem; 23 | } 24 | 25 | h1 { 26 | font-size: 3.6em; 27 | line-height: 110%; 28 | } 29 | 30 | h2 { 31 | font-size: 2em; 32 | } 33 | 34 | h3 { 35 | font-size: 1em; 36 | } 37 | 38 | strong, 39 | b { 40 | font-weight: 900; 41 | } 42 | 43 | hr { 44 | border-top: 1px solid #d8d8d8; 45 | border-bottom: none; 46 | } 47 | 48 | body { 49 | font-family: "Nunito", -apple-system, system-ui, BlinkMacSystemFont, Helvetica, 50 | Arial, sans-serif; 51 | line-height: 1.8em; 52 | font-size: 1.2em; 53 | color: var(--font-color); 54 | } 55 | 56 | a { 57 | text-decoration: none; 58 | font-weight: bold; 59 | color: var(--accent-color); 60 | } 61 | 62 | section { 63 | border-bottom: 1px solid var(--accent-color); 64 | } 65 | 66 | section { 67 | padding-top: 1rem; 68 | padding-bottom: 1rem; 69 | } 70 | 71 | svg.viz { 72 | width: 100%; 73 | background: #f6f7fc; 74 | border: 1px solid #d6daea; 75 | } 76 | 77 | svg.types { 78 | height: 440px; 79 | } 80 | 81 | svg.types text.title { 82 | font-weight: 900; 83 | fill: var(--font-color); 84 | } 85 | svg.types text.summary { 86 | font-size: 0.8em; 87 | fill: var(--font-color); 88 | } 89 | 90 | .center { 91 | text-align: center; 92 | } 93 | 94 | .types-ui .icon { 95 | width: 50px; 96 | cursor: pointer; 97 | } 98 | 99 | .types-ui .icon.selected, 100 | .types-ui img.tiny-icon.selected { 101 | border: 1px dotted var(--accent-color); 102 | } 103 | 104 | img.tiny-icon { 105 | /* width: 24px; */ 106 | vertical-align: middle; 107 | padding: 4px; 108 | } 109 | 110 | p code, 111 | li code { 112 | font-size: 0.9em; 113 | /* color: var(--accent-color); */ 114 | opacity: 0.6; 115 | } 116 | 117 | img { 118 | margin-left: auto; 119 | margin-right: auto; 120 | max-width: 100%; 121 | } 122 | 123 | .hide-sm { 124 | position: fixed; 125 | top: 0; 126 | height: 61px; 127 | width: 100%; 128 | z-index: 1000; 129 | background: white; 130 | text-align: center; 131 | } 132 | 133 | .hide-sm img { 134 | float: left; 135 | margin-left: 5px; 136 | margin-top: 0px; 137 | cursor: pointer; 138 | } 139 | 140 | .hide-sm h1 { 141 | font-size: 1.5em; 142 | padding-top: 15px; 143 | padding-bottom: 15px; 144 | margin: 0px; 145 | border-bottom: 1px solid var(--font-color); 146 | } 147 | 148 | .hidden { 149 | display: none; 150 | } 151 | 152 | @media only screen and (max-width: 48em) { 153 | .hide-xs { 154 | display: none; 155 | } 156 | .main { 157 | margin-top: 50px; 158 | } 159 | } 160 | 161 | @media only screen and (min-width: 48em) { 162 | .hide-sm { 163 | display: none; 164 | } 165 | } 166 | 167 | #emmys .subject-ring { 168 | display: none; 169 | } 170 | 171 | div.title { 172 | font-size: 2em; 173 | width: 200px; 174 | position: absolute; 175 | left: 70px; 176 | top: 100px; 177 | line-height: 1.5em; 178 | color: black; 179 | font-family: sans-serif; 180 | } 181 | 182 | #emmys .annotation.badge text { 183 | font-weight: normal; 184 | font-size: 10px; 185 | } 186 | 187 | #emmys .annotation text { 188 | font-size: 0.7em; 189 | text-transform: uppercase; 190 | font-weight: bold; 191 | } 192 | 193 | .show-bg .annotation-note-bg { 194 | fill: blue; 195 | fill-opacity: 0.05; 196 | } 197 | 198 | .annotation.axis .annotation-note-bg { 199 | fill: white; 200 | stroke: white; 201 | stroke-width: 10; 202 | } 203 | 204 | .annotation.axis path { 205 | stroke: lightgrey; 206 | } 207 | 208 | .annotation.axis text { 209 | fill: lightgrey; 210 | } 211 | 212 | .sticky-menu { 213 | position: sticky; 214 | top: 0; 215 | align-self: flex-start; 216 | } 217 | -------------------------------------------------------------------------------- /src/docs/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import injectTapEventPlugin from "react-tap-event-plugin"; 3 | injectTapEventPlugin(); 4 | import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; 5 | import getMuiTheme from "material-ui/styles/getMuiTheme"; 6 | import { muiTheme } from "./theme"; 7 | // import { goToAnchor } from "react-scrollable-anchor" "material-ui/BottomNavigation" 8 | import Drawer from "material-ui/Drawer"; 9 | import MenuItem from "material-ui/MenuItem"; 10 | 11 | import "./prism.css"; 12 | import "./index.css"; 13 | import { 14 | Introduction, 15 | Setup, 16 | Types, 17 | AnnotationTypesAPI, 18 | ExtendingTypes, 19 | InPractice, 20 | Examples, 21 | API, 22 | Notes 23 | } from "./Sections"; 24 | 25 | class Docs extends React.Component { 26 | state = { 27 | open: false 28 | }; 29 | render() { 30 | const sections = [ 31 | { id: "introduction", name: "Introduction" }, 32 | { id: "setup", name: "Setup" }, 33 | { id: "types", name: "Types" }, 34 | { id: "types-api", name: "Types API" }, 35 | { id: "composable", name: "Composable" }, 36 | { id: "composable-api", name: "Composable API" }, 37 | { id: "in-practice", name: "In Practice" }, 38 | { id: "examples", name: "Examples" }, 39 | { id: "notes", name: "Notes" } 40 | ]; 41 | 42 | const list = sections.map(d => ( 43 | <p key={d.id}> 44 | <a href={"#" + d.id}>{d.name}</a> 45 | </p> 46 | )); 47 | 48 | const drawerSections = sections.map(d => { 49 | return ( 50 | <MenuItem 51 | key={d.id} 52 | onTouchTap={() => { 53 | this.setState({ open: false }); 54 | window.location = `#${d.id}`; 55 | }} 56 | > 57 | {d.name} 58 | </MenuItem> 59 | ); 60 | }); 61 | 62 | return ( 63 | <MuiThemeProvider muiTheme={getMuiTheme(muiTheme)}> 64 | <div> 65 | <div className="hide-sm"> 66 | <h1> 67 | <img 68 | src="img/menu.png" 69 | role='presentation' 70 | onClick={() => { 71 | this.setState({ open: !this.state.open }); 72 | }} 73 | />react-annotation 74 | </h1> 75 | </div> 76 | <Drawer 77 | open={this.state.open} 78 | docked={false} 79 | onRequestChange={open => this.setState({ open })} 80 | > 81 | {drawerSections} 82 | </Drawer> 83 | <div className="container-fluid main"> 84 | <div className="row"> 85 | <div className="col-sm-10 col-sm-offset-1 col-xs-12 smallHeader"> 86 | <header className="box-row"> 87 | <h1 className="hide-xs">react-annotation</h1> 88 | 89 | <p> 90 | Made with <img alt="A picture of a heart" src="img/heart.png" /> by 91 | <a href="http://www.susielu.com"> Susie Lu</a> 92 | </p> 93 | </header> 94 | 95 | <div className="row"> 96 | <div className="col-sm-3 col-xs-12 hide-xs sticky-menu"> 97 | {list} 98 | </div> 99 | <div className="col-sm-9 col-xs-12"> 100 | <Introduction /> 101 | <Setup /> 102 | <Types /> 103 | <AnnotationTypesAPI /> 104 | <ExtendingTypes /> 105 | <API /> 106 | <InPractice /> 107 | <Examples /> 108 | <Notes /> 109 | </div> 110 | </div> 111 | </div> 112 | <div className="col-xs-10 col-xs-offset-1"> 113 | <div className="row"> 114 | <footer className="col-xs-9 col-xs-offset-3"> 115 | <small> 116 | Made by <a href="http://www.susielu.com">Susie Lu</a> 117 | </small> 118 | </footer> 119 | </div> 120 | </div> 121 | </div> 122 | </div> 123 | </div> 124 | </MuiThemeProvider> 125 | ); 126 | } 127 | } 128 | 129 | export default Docs; 130 | -------------------------------------------------------------------------------- /src/docs/prism.css: -------------------------------------------------------------------------------- 1 | /* 2 | Name: Base16 Atelier Sulphurpool Light 3 | Author: Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) 4 | Prism template by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/prism/) 5 | Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) 6 | */ 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | font-family: Consolas, Menlo, Monaco, "Andale Mono WT", "Andale Mono", 10 | "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", 11 | "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", 12 | "Courier New", Courier, monospace; 13 | font-size: 14px; 14 | line-height: 1.375; 15 | direction: ltr; 16 | text-align: left; 17 | white-space: pre; 18 | word-spacing: normal; 19 | word-break: normal; 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | -webkit-hyphens: none; 24 | -moz-hyphens: none; 25 | -ms-hyphens: none; 26 | hyphens: none; 27 | background: #f6f7fc; 28 | color: #5e6687; 29 | } 30 | 31 | pre { 32 | border-top: 1px dashed #d6daea; 33 | border-bottom: 1px dashed #d6daea; 34 | } 35 | 36 | pre[class*="language-"]::-moz-selection, 37 | pre[class*="language-"] ::-moz-selection, 38 | code[class*="language-"]::-moz-selection, 39 | code[class*="language-"] ::-moz-selection { 40 | text-shadow: none; 41 | background: #dfe2f1; 42 | } 43 | 44 | pre[class*="language-"]::selection, 45 | pre[class*="language-"] ::selection, 46 | code[class*="language-"]::selection, 47 | code[class*="language-"] ::selection { 48 | text-shadow: none; 49 | background: #dfe2f1; 50 | } 51 | 52 | /* Code blocks */ 53 | pre[class*="language-"] { 54 | padding: 1em; 55 | margin: 0.5em 0; 56 | overflow: auto; 57 | } 58 | 59 | /* Inline code */ 60 | :not(pre) > code[class*="language-"] { 61 | padding: 0.1em; 62 | border-radius: 0.3em; 63 | } 64 | /* 65 | .token.comment, 66 | .token.prolog, 67 | .token.doctype, 68 | .token.cdata { 69 | color: #898ea4; 70 | } 71 | 72 | .token.punctuation { 73 | color: #5e6687; 74 | } 75 | 76 | .token.namespace { 77 | opacity: 0.7; 78 | } 79 | 80 | .token.operator, 81 | .token.boolean, 82 | .token.number { 83 | color: #c76b29; 84 | } 85 | 86 | .token.property { 87 | color: #c08b30; 88 | } 89 | 90 | .token.tag { 91 | color: #3d8fd1; 92 | } 93 | 94 | .token.string { 95 | color: #22a2c9; 96 | } 97 | 98 | .token.selector { 99 | color: #6679cc; 100 | } 101 | 102 | .token.attr-name { 103 | color: #c76b29; 104 | } 105 | 106 | .token.entity, 107 | .token.url, 108 | .language-css .token.string, 109 | .style .token.string { 110 | color: #22a2c9; 111 | } 112 | 113 | .token.attr-value, 114 | .token.keyword, 115 | .token.control, 116 | .token.directive, 117 | .token.unit { 118 | color: #ac9739; 119 | } 120 | 121 | .token.statement, 122 | .token.regex, 123 | .token.atrule { 124 | color: #22a2c9; 125 | } 126 | 127 | .token.placeholder, 128 | .token.variable { 129 | color: #3d8fd1; 130 | } 131 | 132 | .token.deleted { 133 | text-decoration: line-through; 134 | } 135 | 136 | .token.inserted { 137 | border-bottom: 1px dotted #202746; 138 | text-decoration: none; 139 | } 140 | 141 | .token.italic { 142 | font-style: italic; 143 | } 144 | 145 | .token.important, 146 | .token.bold { 147 | font-weight: bold; 148 | } 149 | 150 | .token.important { 151 | color: #c94922; 152 | } 153 | 154 | .token.entity { 155 | cursor: help; 156 | } */ 157 | 158 | .token.comment, 159 | .token.prolog, 160 | .token.doctype, 161 | .token.cdata { 162 | color: #668389; 163 | font-style: italic; 164 | } 165 | 166 | .token.namespace { 167 | opacity: 0.7; 168 | } 169 | 170 | .token.string { 171 | color: #e91e63; 172 | } 173 | 174 | .token.punctuation, 175 | .token.operator { 176 | color: #393a34; /* no highlight */ 177 | } 178 | 179 | .token.url, 180 | .token.symbol, 181 | .token.number, 182 | .token.boolean, 183 | .token.variable, 184 | .token.constant, 185 | .token.inserted { 186 | color: #36acaa; 187 | } 188 | 189 | .token.atrule, 190 | .token.keyword, 191 | .token.attr-value, 192 | .language-autohotkey .token.selector, 193 | .language-json .token.boolean, 194 | .language-json .token.number, 195 | code[class*="language-css"] { 196 | color: #2196f3; 197 | } 198 | 199 | .token.function { 200 | color: #393a34; 201 | } 202 | .token.deleted, 203 | .language-autohotkey .token.tag { 204 | color: #9a050f; 205 | } 206 | 207 | .token.selector, 208 | .language-autohotkey .token.keyword { 209 | color: #00009f; 210 | } 211 | 212 | .token.important, 213 | .token.bold { 214 | font-weight: bold; 215 | } 216 | 217 | .token.italic { 218 | font-style: italic; 219 | } 220 | 221 | .token.class-name, 222 | .language-json .token.property { 223 | color: #2b91af; 224 | } 225 | 226 | .token.tag, 227 | .token.selector { 228 | color: #9610ff; 229 | } 230 | 231 | .token.attr-name, 232 | .token.property, 233 | .token.regex, 234 | .token.entity { 235 | color: #668389; 236 | } 237 | 238 | .token.directive.tag .tag { 239 | background: #ffff00; 240 | color: #393a34; 241 | } 242 | 243 | pre > code.highlight { 244 | outline: 0.4em solid #c94922; 245 | outline-offset: 0.4em; 246 | } 247 | 248 | /* overrides color-values for the Line Numbers plugin 249 | * http://prismjs.com/plugins/line-numbers/ 250 | */ 251 | /* .line-numbers .line-numbers-rows { 252 | border-right-color: #dfe2f1; 253 | } 254 | 255 | .line-numbers-rows > span:before { 256 | color: #979db4; 257 | } */ 258 | 259 | .line-numbers .line-numbers-rows { 260 | border-right-color: #a5a5a5; 261 | } 262 | 263 | .line-numbers-rows > span:before { 264 | color: #2b91af; 265 | } 266 | /* overrides color-values for the Line Highlight plugin 267 | * http://prismjs.com/plugins/line-highlight/ 268 | */ 269 | .line-highlight { 270 | background: rgba(107, 115, 148, 0.2); 271 | background: -webkit-linear-gradient( 272 | left, 273 | rgba(107, 115, 148, 0.2) 70%, 274 | rgba(107, 115, 148, 0) 275 | ); 276 | background: linear-gradient( 277 | to right, 278 | rgba(107, 115, 148, 0.2) 70%, 279 | rgba(107, 115, 148, 0) 280 | ); 281 | } 282 | -------------------------------------------------------------------------------- /src/docs/theme.js: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: "#668389", 3 | accent: "#9610ff" 4 | } 5 | 6 | export const muiTheme = { 7 | // spacing: spacing, 8 | fontFamily: '"Nunito", "Open-Sans", Arial, sans-serif', 9 | // // borderRadius: 2, 10 | palette: { 11 | primary1Color: "#9610ff", 12 | // // primary2Color: cyan700, 13 | // // primary3Color: grey400, 14 | accent1Color: "#bdbdbd", 15 | // accent1ColorDark: "#82ada9" 16 | // // accent2Color: grey100, 17 | // // accent3Color: grey500, 18 | textColor: "#668389" 19 | // // secondaryTextColor: fade(darkBlack, 0.54), 20 | // // alternateTextColor: white, 21 | // // canvasColor: "#f2f2f2" 22 | // // borderColor: grey300, 23 | // // disabledColor: fade(darkBlack, 0.3), 24 | // // pickerHeaderColor: cyan500, 25 | // // clockCircleColor: fade(darkBlack, 0.07), 26 | // // shadowColor: fullBlack 27 | } 28 | // optionColors: colors, 29 | // colorPicker 30 | } 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React from "react" 3 | /* eslint-enable no-unused-vars */ 4 | import ReactDOM from "react-dom" 5 | import Docs from "./docs" 6 | 7 | ReactDOM.render(<Docs />, document.getElementById("root")) 8 | -------------------------------------------------------------------------------- /tests.md: -------------------------------------------------------------------------------- 1 | List of tests 2 | 3 | // if OnReset isn't passed don't show reset button -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | 3 | module.exports = { 4 | mode: "production", 5 | entry: "./src/components/index.js", 6 | output: { 7 | path: path.resolve(__dirname), 8 | filename: "bundle.js", 9 | library: "ReactAnnotation", 10 | libraryTarget: "window" 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js?/, 16 | exclude: /build|node_modules|styles/, 17 | loader: "babel-loader", 18 | include: path.join(__dirname, "src") 19 | } 20 | ] 21 | }, 22 | externals: { 23 | react: "React", 24 | "react-dom": "ReactDOM" 25 | } 26 | } 27 | --------------------------------------------------------------------------------