├── .babelrc.js ├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ └── npm-publish.yml ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── data ├── animals.tsv ├── cities.csv ├── countriesGDP.csv ├── dispersions.csv ├── letters.tsv ├── lineup.tsv ├── multivariate.csv ├── music.csv ├── orchestra.csv └── titanic.tsv ├── doc.hbs ├── jsdoc.json ├── package-lock.json ├── package.json ├── rollup.config.js ├── sandbox ├── node │ ├── cli.js │ └── index.js └── web │ ├── index.html │ ├── index.js │ ├── testfetch.html │ └── testfetch.js ├── src ├── __tests__ │ ├── colors.test.js │ ├── dataset.test.js │ ├── mapper.test.js │ ├── options.test.js │ └── rawchart.test.js ├── colors.js ├── dataset.js ├── dateFormats.js ├── expressionRegister.js ├── groupBy.js ├── importExport │ ├── importExportV1.1.js │ ├── importExportV1.2.js │ ├── importExportV1.js │ ├── index.js │ └── utils.js ├── index.js ├── labels.js ├── legend.js ├── mapping.js ├── options.js ├── rawGraphs.js ├── testSupport │ ├── chart.js │ └── titanic.tsv ├── typeDefs.js └── utils.js ├── webpack.config.js └── website ├── .gitignore ├── README.md ├── babel.config.js ├── blog ├── 2019-05-28-hola.md ├── 2019-05-29-hello-world.md └── 2019-05-30-welcome.md ├── docs ├── api.md ├── chart-interface.md ├── chart-utilities.md ├── core.md ├── data-parsing.md ├── declarative-mapping.md ├── example-npm.md ├── example-script.md ├── glossary.md ├── import-export.md ├── install.md ├── rendering.md └── workflow.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src ├── css │ └── custom.css └── pages │ ├── index.js │ ├── index_.js │ └── styles.module.css └── static ├── .nojekyll └── img ├── beeswarm-occlusion.svg ├── bubble-legend.svg ├── favicon.ico ├── favicon.png ├── icon-512x512.png ├── logo.svg ├── undraw_docusaurus_mountain.svg ├── undraw_docusaurus_react.svg └── undraw_docusaurus_tree.svg /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | loose: true, 7 | modules: false, 8 | ...(process.env.NODE_ENV === 'test' && { 9 | targets: { 10 | node: 'current' 11 | } 12 | }) 13 | }, 14 | ], 15 | ], 16 | plugins: [ 17 | process.env.NODE_ENV === 'test' && 18 | '@babel/plugin-transform-modules-commonjs', 19 | ].filter(Boolean), 20 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true, 5 | es2021: true, 6 | }, 7 | extends: "eslint:recommended", 8 | parserOptions: { 9 | ecmaVersion: 12, 10 | sourceType: "module", 11 | }, 12 | rules: { 13 | semi: ["warn", "never"], 14 | quotes: ["warn", "double"], 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [rawgraphs] 4 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Publish on private NPM registry 5 | 6 | on: 7 | push: 8 | tags: 9 | - v* 10 | 11 | jobs: 12 | publish-npm: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: '14.x' 19 | registry-url: 'https://registry.npmjs.org' 20 | - run: npm install 21 | - run: npm run build 22 | - run: npm publish 23 | env: 24 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | /docs 3 | 4 | # dependencies 5 | /**/node_modules 6 | 7 | # testing 8 | /coverage 9 | 10 | # misc 11 | .DS_Store 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | .vscode 22 | 23 | lib 24 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": false 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v1.0.0-beta.17 2 | #### 18 Mar 2022 3 | - project serialization: new `customChart` property to handle custom charts loading 4 | 5 | 6 | ## v1.0.0-beta.16 7 | #### 24 May 2021 8 | - fixed `simplifyDataType` implementation (handling undefined datatype) 9 | - logging validations errors in dev mode 10 | ## v1.0.0-beta.15 11 | #### 20 May 2021 12 | - removed wrong import from importExport 13 | - added new `overrideBaseOptions` function 14 | ## v1.0.0-beta.14 15 | #### 03 Mar 2021 16 | - numberparser: fixed regexp for group separator 17 | - Add import and export (multiversion) 18 | - working on docs 19 | ## v1.0.0-beta.13 20 | #### 28 Feb 2021 21 | - fixed number parsing 22 | ## v1.0.0-beta.12 23 | #### 25 Feb 2021 24 | - removed `iso` date format specialization, added `YYYY-MM-DDTHH:mm:ss` format 25 | ## v1.0.0-beta.11 26 | #### 25 Feb 2021 27 | - fixed data parsing (everything was parsed as iso date) 28 | ## v1.0.0-beta.10 29 | #### 25 Feb 2021 30 | - added explicit iso (with undefined formatter) to exported date formats 31 | - updated publishing action 32 | ## v1.0.0-beta.9 33 | #### 24 Feb 2021 34 | - color scale generation: if no scaletype or interpolator, default color scale is generated 35 | - color scales: allow automatic scale values; explicit scaletype/interpolator check. 36 | - multiple web sandbox. 37 | - better date parsing (parsing iso dates by default) 38 | 39 | ## v1.0.0-beta.8 40 | #### 23 Feb 2021 41 | - switched publishing to npm 42 | ## v1.0.0-beta.7 43 | #### 19 Feb 2021 44 | - fixed condition for getting default color scale 45 | - dataset/inferTypes: default value for parsingOptions 46 | - added median aggregation function from d3 array 47 | - working on docs 48 | 49 | ## v1.0.0-beta.6 50 | #### 16 Feb 2021 51 | - feature: support for custom domain in color scale via "domain" property in visual options 52 | - fix: support for using "dimension" property in color scale with non-mapped dimensions 53 | 54 | ## v1.0.0-beta.5 55 | #### 09 Feb 2021 56 | 57 | - Support for default color scale 58 | - Support for new color scale property: locked 59 | - Support for disabing visual options with "requiredDimensions" property 60 | - Added new date format to defaults (date with time) 61 | - Skip parsing empty rows 62 | 63 | ## v1.0.0-beta.4 64 | #### 01 Feb 2021 65 | 66 | Features: 67 | - Support for styles override in charts 68 | 69 | ## v1.0.0-beta.3 70 | #### 01 Dec 2020 71 | 72 | Features: 73 | - Support for repeated options 74 | - Added labels occlusion utility 75 | 76 | ## v1.0.0beta2 77 | #### 26 Nov 2020 78 | 79 | Fixes: 80 | - Testing github action 81 | 82 | 83 | ## v1.0.0beta1 84 | #### 26 Nov 2020 85 | 86 | Features: 87 | - Better support for color scales based on dates 88 | - Integration for github actions (still publishing on inmagik registry) 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c), 2013-2021 DensityDesign Lab, Calibro, INMAGIK 190 | 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rawgraphs-core 2 | 3 | This javascript library was born to simplify and modularize development of the [RawGraphs web app](https://app.rawgraphs.io), but it can be used to implement the RAWGraphs workflow and rendering charts from javascript code. 4 | 5 | ## Docs 6 | 7 | Browse the docs on the [documentation website](https://rawgraphs.github.io/rawgraphs-core/) 8 | 9 | ## License 10 | 11 | RAWGraphs is provided under the [Apache License 2.0](https://github.com/rawgraphs/rawgraphs-core/blob/master/LICENSE): 12 | 13 | Copyright (c), 2013-2021 DensityDesign Lab, Calibro, INMAGIK 14 | 15 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 16 | You may obtain a copy of the License at 17 | 18 | http://www.apache.org/licenses/LICENSE-2.0 19 | 20 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | See the License for the specific language governing permissions and limitations under the License. 22 | 23 | -------------------------------------------------------------------------------- /data/animals.tsv: -------------------------------------------------------------------------------- 1 | Name Kingdom Phylum Class Order Family 2 | Spotted Hyena Animalia Chordata Mammalia Carnivora Hyaenidae 3 | Woolly mammoth Animalia Chordata Mammalia Proboscidea Elephantidae 4 | Nine-banded armadillo Animalia Chordata Mammalia Cingulata Dasypodidae 5 | Donkey Animalia Chordata Mammalia Perissodactyla Equidae 6 | American bison Animalia Chordata Mammalia Artiodactyla Bovidae 7 | Brown-throated sloth Animalia Chordata Mammalia Pilosa Bradypodidae 8 | Dog Animalia Chordata Mammalia Carnivora Canidae 9 | Eastern grey kangaroo Animalia Chordata Mammalia Diprotodontia Macropodidae 10 | Goat Animalia Chordata Mammalia Artiodactyla Bovidae 11 | Little Owl Animalia Chordata Aves Strigiformes Strigidae 12 | Arctic hare Animalia Chordata Mammalia Lagomorpha Leporidae 13 | European rabbit Animalia Chordata Mammalia Lagomorpha Leporidae 14 | Roborovski hamster Animalia Chordata Mammalia Rodentia Cricetidae 15 | Common bottlenose dolphin Animalia Chordata Mammalia Cetacea Delphinidae 16 | Dodo Animalia Chordata Aves Columbiformes Columbidae 17 | Dugong Animalia Chordata Mammalia Sirenia Dugongidae 18 | Harbor seal Animalia Chordata Mammalia Carnivora Phocidae 19 | Weaver ant Animalia Arthropoda Insecta Hymenoptera Formicidae 20 | Giraffe Animalia Chordata Mammalia Artiodactyla Giraffidae 21 | Koala Animalia Chordata Mammalia Diprotodontia Phascolarctidae 22 | Llama Animalia Chordata Mammalia Artiodactyla Camelidae 23 | Lion Animalia Chordata Mammalia Carnivora Felidae 24 | Narwhal Animalia Chordata Mammalia Cetacea Monodontidae 25 | Platypus Animalia Chordata Mammalia Monotremata Ornithorhynchidae 26 | Brown Bear Animalia Chordata Mammalia Carnivora Ursidae 27 | Hairy Hermit Crab Animalia Arthropoda Malacostraca Decapoda Paguridae 28 | Giant Panda Animalia Chordata Mammalia Carnivora Ursidae 29 | Black Rat Animalia Chordata Mammalia Rodentia Muridae 30 | Alligator Animalia Chordata Reptilia Crocodylia Alligatoridae 31 | Alligator snapping turtle Animalia Chordata Reptilia Testudines Chelydridae 32 | House Sparrow Animalia Chordata Aves Passeriformes Passeridae 33 | Mediterranean mussel Animalia Mollusca Bivalvia Mytiloida Mytilidae 34 | Pacific Gull Animalia Chordata Aves Charadriiformes Laridae 35 | Common Magpie Animalia Chordata Aves Passeriformes Corvidae 36 | Black Woodpecker Animalia Chordata Aves Piciformes Picidae 37 | Great White Pelican Animalia Chordata Aves Pelecaniformes Pelecanidae 38 | Boa constrictor Animalia Chordata Reptilia Squamata Boidae 39 | Chans megastick Animalia Arthropoda Insecta Phasmatodea Phasmatidae 40 | Golden Stag Beetle Animalia Arthropoda Insecta Coleoptera Lucanidae 41 | russet mite Animalia Arthropoda Arachnida Prostigmata Eriophyidae 42 | Firefly Animalia Arthropoda Insecta Coleoptera Lampyridae 43 | Ringlet Butterfly Animalia Arthropoda Insecta Lepidoptera Nymphalidae 44 | European wasp Animalia Arthropoda Insecta Hymenoptera Vespidae 45 | Ostrich Animalia Chordata Aves Struthioniformes Struthionidae 46 | Tarantula Animalia Arthropoda Arachnida Araneae Lycosidae 47 | giant forest scorpions Animalia Arthropoda Chelicerata Scorpiones Scorpionidae 48 | Pterodactylus Animalia Chordata Reptilia †Pterosauria †Pterodactylidae 49 | Zebra Animalia Chordata Mammalia Perissodactyla Equidae 50 | African civet Animalia Chordata Mammalia Carnivora Viverridae 51 | Mink Animalia Chordata Mammalia Carnivora Mustelidae 52 | Cynoscion nebulosus Animalia Chordata Actinopterygii Perciformes Sciaenidae 53 | Walrus Animalia Chordata Mammalia Carnivora Odobenidae 54 | Tiger Animalia Chordata Mammalia Carnivora Felidae 55 | Tortoise Animalia Chordata Reptilia Chelonii Testudinidae 56 | Scorpionfish Animalia Chordata Actinopterygii Scorpaeniformes Scorpaenidae 57 | Hedgehog Animalia Chordata Mammalia Erinaceomorpha Erinaceidae 58 | Reindeer Animalia Chordata Mammalia Artiodactyla Cervidae 59 | Lophius piscatorius Animalia Chordata Actinopterygii Lophiiformes Lophiidae 60 | Guinea pig Animalia Chordata Mammalia Rodentia Caviidae 61 | Penguin Animalia Chordata Aves Sphenisciformes Spheniscidae 62 | Blowfish Animalia Chordata Actinopterygii Tetraodontiformes Tetraodontidae 63 | Cockatoo Animalia Chordata Aves Psittaciformes Cacatuidae 64 | Leopard Animalia Chordata Mammalia Carnivora Felidae 65 | Killer whale Animalia Chordata Mammalia Cetacea Delphinidae 66 | Bowhead Whale Animalia Chordata Mammalia Cetacea Balaenidae -------------------------------------------------------------------------------- /data/cities.csv: -------------------------------------------------------------------------------- 1 | Continent,Country,City,Population 2 | Asia,China,Shanghai,24256800 3 | Asia,Pakistan,Karachi,23500000 4 | Asia,China,Beijing,21516000 5 | Asia,India,Delhi,16787941 6 | Asia,China,Tianjin,15200000 7 | Asia,Japan,Tokyo,13513734 8 | Asia,China,Guangzhou,13080500 9 | Asia,India,Mumbai,12442373 10 | Asia,China,Shenzhen,10467400 11 | Asia,Indonesia,Jakarta,10075310 12 | Africa,Nigeria,Lagos,17578000 13 | Africa,Egypt,Cairo,11001000 14 | Africa,Democratic Republic of the Congo,Kinshasa-Brazzaville,8754000 15 | Africa,Somalia,Mogadishu,6346000 16 | Africa,Sudan,Khartoum-Omdurman,5172000 17 | Africa,Angola,Luanda,4772000 18 | Africa,Egypt,Alexandria,4387000 19 | Africa,Tanzania,Dar es Salaam,4364541 20 | Africa,Ivory Coast,Abidjan,4125000 21 | Africa,South Africa,Greater Johannesburg,3670000 22 | Europe,Turkey,Istanbul,14025646 23 | Europe,Russia,Moscow,12330126 24 | Europe,United Kingdom,London,8673713 25 | Europe,Russia,Saint Petersburg,5225690 26 | Europe,Germany,Berlin,3562166 27 | Europe,Spain,Madrid,3165235 28 | Europe,Ukraine,Kiev,2909491 29 | Europe,Italy,Rome,2874038 30 | Europe,France,Paris,2241346 31 | Europe,Belarus,Minsk,1949400 32 | North America,Mexico,Mexico City,8918653 33 | North America,United States,New York City,8550405 34 | North America,United States,Los Angeles,3971883 35 | North America,Canada,Toronto,2826498 36 | North America,United States,Chicago,2720546 37 | North America,United States,Houston,2296224 38 | North America,Cuba,Havana,2117625 39 | North America,Canada,Montreal,1753034 40 | North America,Mexico,Ecatepec de Morelos,1677678 41 | North America,United States,Philadelphia,1567442 42 | South America,Brazil,São Paulo,11967825 43 | South America,Peru,Lima,8894412 44 | South America,Colombia,Bogotá,7862277 45 | South America,Brazil,Rio de Janeiro,6476631 46 | South America,Chile,Santiago,5507282 47 | South America,Venezuela,Caracas,3289886 48 | South America,Argentina,Buenos Aires,3054267 49 | South America,Brazil,Salvador,2921087 50 | South America,Brazil,Brasília,2914830 51 | South America,Brazil,Fortaleza,2591188 -------------------------------------------------------------------------------- /data/countriesGDP.csv: -------------------------------------------------------------------------------- 1 | Country,Agricolture,Industry,Services 2 | United States,209027.00,3327015.00,13882883.00 3 | China,944615.00,4422042.00,5013724.00 4 | Japan,55396.00,1269492.00,3296063.00 5 | Germany,30876.00,1084533.00,2744138.00 6 | United Kingdom,20616.00,618481.00,2306049.00 7 | France,54091.00,520981.00,2271817.00 8 | Brazil,127063.00,644729.00,1581233.00 9 | Italy,42959.00,519804.00,1585189.00 10 | India,356319.00,528335.00,1165204.00 11 | Russia,72441.00,668686.00,1116334.00 12 | Canada,32197.00,511573.00,1244947.00 13 | Australia,57768.00,384154.00,1002267.00 14 | South Korea,38258.00,563946.00,814746.00 15 | Spain,46426.00,340459.00,1021377.00 16 | Mexico,47461.00,438692.00,796572.00 17 | Indonesia,127077.00,416776.00,344795.00 18 | Netherlands,24258.00,208791.00,634171.00 19 | Turkey,71744.00,226516.00,507848.00 20 | Saudi Arabia,15049.00,503395.00,234015.00 21 | Switzerland,9257.00,197238.00,505556.00 22 | Nigeria,102110.00,147429.00,313214.00 23 | Sweden,10064.00,150401.00,398648.00 24 | Poland,18776.00,185549.00,347905.00 25 | Belgium,3695.00,114007.00,410108.00 26 | Norway,13813.00,195944.00,301845.00 27 | Taiwan,6571.00,161745.00,338147.00 28 | Argentina,44764.00,137427.00,265005.00 29 | Austria,6541.00,128640.00,300888.00 30 | United Arab Emirates,2915.00,247368.00,165745.00 31 | Iran,45102.00,163496.00,194101.00 32 | Colombia,35610.00,152044.00,212462.00 33 | Thailand,50605.00,129367.00,200519.00 34 | Denmark,15624.00,66314.00,265258.00 35 | South Africa,8530.00,107824.00,224861.00 36 | Greece,8131.00,44105.00,194407.00 37 | Venezuela,9834.00,73020.00,126373 -------------------------------------------------------------------------------- /data/dispersions.csv: -------------------------------------------------------------------------------- 1 | Movie,Genre,Production Budget (millions),Box Office (millions),ROI,Rating IMDB 2 | Avatar,Action,237,2784,11.7,8.0 3 | The Blind Side,Drama,29,309,10.7,7.6 4 | "The Chronicles of Narnia: The Lion, the Witch and the Wardrobe",Adventure,180,745,4.1,6.9 5 | The Dark Knight,Action,185,1005,5.4,9.0 6 | ET: The Extra-Terrestrial,Drama,11,793,75.5,7.9 7 | Finding Nemo,Adventure,94,940,10.0,8.1 8 | Ghostbusters,Comedy,144,229,1.6,7.8 9 | The Hunger Games,Thriller/Suspense,78,649,8.3,7.2 10 | Iron Man 3,Action,178,1215,6.8,7.6 11 | Jurassic Park,Action,53,1030,19.4,8.0 12 | King Kong,Adventure,207,551,2.7,7.3 13 | The Lion King,Adventure,45,968,21.5,8.4 14 | "Monsters, Inc.",Adventure,115,577,5.0,8.0 15 | The Twilight Saga: New Moon,Drama,50,710,14.2,4.5 16 | Oz the Great and Powerful,Adventure,160,493,3.1,6.6 17 | Pirates of the Caribbean: Dead Man's Chest,Adventure,225,1066,4.7,7.3 18 | Quantum of Solace,Action,200,586,2.9,6.7 19 | Raiders of the Lost Ark,Adventure,18,390,21.7,8.7 20 | Star Wars Ep. I: The Phantom Menace,Adventure,115,1027,8.9,6.5 21 | Titanic,Thriller/Suspense,200,2187,10.9,7.6 22 | Up,Adventure,175,735,4.2,8.3 23 | The Vow,Drama,30,196,6.5,6.7 24 | The War of the Worlds,Action,132,704,5.3,6.5 25 | X-Men: The Last Stand,Action,210,459,2.2,6.8 26 | You've Got Mail,Drama,65,251,3.9,6.3 27 | Zookeeper,Romantic Comedy,80,170,2.1,5.0 -------------------------------------------------------------------------------- /data/letters.tsv: -------------------------------------------------------------------------------- 1 | Letter Language Frequency Rank 2 | a English 0.08 3 3 | b English 0.01 other 4 | c English 0.03 other 5 | d English 0.04 other 6 | e English 0.13 1 7 | f English 0.02 other 8 | g English 0.02 other 9 | h English 0.06 other 10 | i English 0.07 other 11 | j English 0.00 other 12 | k English 0.01 other 13 | l English 0.04 other 14 | m English 0.02 other 15 | n English 0.07 other 16 | o English 0.08 other 17 | p English 0.02 other 18 | q English 0.00 other 19 | r English 0.06 other 20 | s English 0.06 other 21 | t English 0.09 2 22 | u English 0.03 other 23 | v English 0.01 other 24 | w English 0.02 other 25 | x English 0.00 other 26 | y English 0.02 other 27 | z English 0.00 other 28 | a German 0.07 other 29 | b German 0.02 other 30 | c German 0.03 other 31 | d German 0.05 other 32 | e German 0.16 1 33 | f German 0.02 other 34 | g German 0.03 other 35 | h German 0.05 other 36 | i German 0.07 other 37 | j German 0.00 other 38 | k German 0.01 other 39 | l German 0.03 other 40 | m German 0.03 other 41 | n German 0.10 2 42 | o German 0.03 other 43 | p German 0.01 other 44 | q German 0.00 other 45 | r German 0.07 other 46 | s German 0.07 3 47 | t German 0.06 other 48 | u German 0.04 other 49 | v German 0.01 other 50 | w German 0.02 other 51 | x German 0.00 other 52 | y German 0.00 other 53 | z German 0.01 other 54 | a Italian 0.12 2 55 | b Italian 0.01 other 56 | c Italian 0.05 other 57 | d Italian 0.04 other 58 | e Italian 0.12 1 59 | f Italian 0.01 other 60 | g Italian 0.02 other 61 | h Italian 0.01 other 62 | i Italian 0.10 3 63 | j Italian 0.00 other 64 | k Italian 0.00 other 65 | l Italian 0.07 other 66 | m Italian 0.03 other 67 | n Italian 0.07 other 68 | o Italian 0.10 other 69 | p Italian 0.03 other 70 | q Italian 0.01 other 71 | r Italian 0.06 other 72 | s Italian 0.05 other 73 | t Italian 0.06 other 74 | u Italian 0.03 other 75 | v Italian 0.02 other 76 | w Italian 0.00 other 77 | x Italian 0.00 other 78 | y Italian 0.00 other 79 | z Italian 0.01 other -------------------------------------------------------------------------------- /data/lineup.tsv: -------------------------------------------------------------------------------- 1 | Name Begin End Role 2 | Steve Harris 12/25/1975 1/1/2015 Bass 3 | Paul Day 12/25/1975 7/1/1976 Vocals 4 | Dennis Wilcock 7/2/1976 3/1/1978 Vocals 5 | Paul Di'Anno 11/1/1978 10/1/1981 Vocals 6 | Bruce Dickinson 10/1/1981 8/28/1993 Vocals 7 | Blaze Bayley 1/1/1994 1/2/1999 Vocals 8 | Bruce Dickinson 1/2/1999 1/1/2015 Vocals 9 | Dave Sullivan 12/25/1975 12/1/1976 Guitars 10 | Terry Rance 12/25/1975 12/1/1976 Guitars 11 | Dave Murray 12/1/1976 6/1/1977 Guitars 12 | Dave Murray 3/1/1978 1/1/2015 Guitars 13 | Bob Sawyer 1/1/1977 6/1/1977 Guitars 14 | Terry Wapram 7/1/1977 3/1/1978 Guitars 15 | Paul Cairns 12/1/1978 3/1/1979 Guitars 16 | Paul Todd 6/23/1979 6/30/1979 Guitars 17 | Tony Parsons 9/1/1979 11/15/1979 Guitars 18 | Dennis Stratton 12/20/1979 11/1/1980 Guitars 19 | Adrian Smith 11/5/1980 1/1/1990 Guitars 20 | Adrian Smith 2/1/1999 1/1/2015 Guitars 21 | Janick Gers 2/1/1990 1/1/2015 Guitars 22 | Ron Matthews 12/25/1975 6/1/1977 Drums 23 | Thunderstick 9/1/1977 11/1/1977 Drums 24 | Doug Sampson 12/1/1977 12/23/1979 Drums 25 | Clive Burr 12/26/1979 11/1/1982 Drums 26 | Nicko McBrain 12/1/1982 1/1/2015 Drums 27 | Tony Moore 9/1/1977 11/1/1977 Keys -------------------------------------------------------------------------------- /data/music.csv: -------------------------------------------------------------------------------- 1 | Media Year Market Share 2 | 8-track 1980-01 14.30 3 | 8-track 1981-01 7.90 4 | 8-track 1982-01 1.00 5 | 8-track 1983-01 0.00 6 | 8-track 1984-01 0.00 7 | 8-track 1985-01 0.00 8 | 8-track 1986-01 0.00 9 | 8-track 1987-01 0.00 10 | 8-track 1988-01 0.00 11 | 8-track 1989-01 0.00 12 | 8-track 1990-01 0.00 13 | 8-track 1991-01 0.00 14 | 8-track 1992-01 0.00 15 | 8-track 1993-01 0.00 16 | 8-track 1994-01 0.00 17 | 8-track 1995-01 0.00 18 | 8-track 1996-01 0.00 19 | 8-track 1997-01 0.00 20 | 8-track 1998-01 0.00 21 | 8-track 1999-01 0.00 22 | 8-track 2000-01 0.00 23 | 8-track 2001-01 0.00 24 | 8-track 2002-01 0.00 25 | 8-track 2003-01 0.00 26 | 8-track 2004-01 0.00 27 | 8-track 2005-01 0.00 28 | 8-track 2006-01 0.00 29 | 8-track 2007-01 0.00 30 | 8-track 2008-01 0.00 31 | 8-track 2009-01 0.00 32 | 8-track 2010-01 0.00 33 | Cassete 1980-01 19.10 34 | Cassete 1981-01 26.70 35 | Cassete 1982-01 38.20 36 | Cassete 1983-01 47.80 37 | Cassete 1984-01 55.00 38 | Cassete 1985-01 55.30 39 | Cassete 1986-01 53.90 40 | Cassete 1987-01 53.20 41 | Cassete 1988-01 54.10 42 | Cassete 1989-01 50.80 43 | Cassete 1990-01 46.00 44 | Cassete 1991-01 38.50 45 | Cassete 1992-01 34.50 46 | Cassete 1993-01 29.00 47 | Cassete 1994-01 24.70 48 | Cassete 1995-01 18.70 49 | Cassete 1996-01 15.20 50 | Cassete 1997-01 12.40 51 | Cassete 1998-01 7.30 52 | Cassete 1999-01 7.30 53 | Cassete 2000-01 4.40 54 | Cassete 2001-01 2.60 55 | Cassete 2002-01 1.70 56 | Cassete 2003-01 0.90 57 | Cassete 2004-01 0.20 58 | Cassete 2005-01 0.10 59 | Cassete 2006-01 0.00 60 | Cassete 2007-01 0.00 61 | Cassete 2008-01 0.00 62 | Cassete 2009-01 0.00 63 | Cassete 2010-01 0.00 64 | Cassete single 1980-01 0.00 65 | Cassete single 1981-01 0.00 66 | Cassete single 1982-01 0.00 67 | Cassete single 1983-01 0.00 68 | Cassete single 1984-01 0.00 69 | Cassete single 1985-01 0.00 70 | Cassete single 1986-01 0.00 71 | Cassete single 1987-01 0.30 72 | Cassete single 1988-01 0.90 73 | Cassete single 1989-01 3.00 74 | Cassete single 1990-01 3.40 75 | Cassete single 1991-01 2.90 76 | Cassete single 1992-01 3.30 77 | Cassete single 1993-01 3.00 78 | Cassete single 1994-01 2.30 79 | Cassete single 1995-01 1.90 80 | Cassete single 1996-01 1.50 81 | Cassete single 1997-01 1.10 82 | Cassete single 1998-01 0.30 83 | Cassete single 1999-01 0.30 84 | Cassete single 2000-01 0.00 85 | Cassete single 2001-01 0.00 86 | Cassete single 2002-01 0.00 87 | Cassete single 2003-01 0.00 88 | Cassete single 2004-01 0.00 89 | Cassete single 2005-01 0.00 90 | Cassete single 2006-01 0.00 91 | Cassete single 2007-01 0.00 92 | Cassete single 2008-01 0.00 93 | Cassete single 2009-01 0.00 94 | Cassete single 2010-01 0.00 95 | CD 1980-01 0.00 96 | CD 1981-01 0.00 97 | CD 1982-01 0.00 98 | CD 1983-01 0.50 99 | CD 1984-01 2.40 100 | CD 1985-01 8.90 101 | CD 1986-01 20.00 102 | CD 1987-01 28.50 103 | CD 1988-01 33.40 104 | CD 1989-01 39.30 105 | CD 1990-01 45.80 106 | CD 1991-01 55.50 107 | CD 1992-01 59.20 108 | CD 1993-01 64.80 109 | CD 1994-01 70.10 110 | CD 1995-01 76.10 111 | CD 1996-01 79.20 112 | CD 1997-01 81.10 113 | CD 1998-01 87.90 114 | CD 1999-01 87.90 115 | CD 2000-01 92.30 116 | CD 2001-01 94.00 117 | CD 2002-01 95.30 118 | CD 2003-01 94.70 119 | CD 2004-01 92.70 120 | CD 2005-01 85.60 121 | CD 2006-01 79.70 122 | CD 2007-01 70.00 123 | CD 2008-01 62.40 124 | CD 2009-01 55.60 125 | CD 2010-01 49.10 126 | CD single 1980-01 0.00 127 | CD single 1981-01 0.00 128 | CD single 1982-01 0.00 129 | CD single 1983-01 0.00 130 | CD single 1984-01 0.00 131 | CD single 1985-01 0.00 132 | CD single 1986-01 0.00 133 | CD single 1987-01 0.00 134 | CD single 1988-01 0.20 135 | CD single 1989-01 0.00 136 | CD single 1990-01 0.10 137 | CD single 1991-01 0.40 138 | CD single 1992-01 0.50 139 | CD single 1993-01 0.50 140 | CD single 1994-01 0.50 141 | CD single 1995-01 0.90 142 | CD single 1996-01 1.50 143 | CD single 1997-01 2.20 144 | CD single 1998-01 1.50 145 | CD single 1999-01 1.50 146 | CD single 2000-01 1.00 147 | CD single 2001-01 0.60 148 | CD single 2002-01 0.20 149 | CD single 2003-01 0.30 150 | CD single 2004-01 0.10 151 | CD single 2005-01 0.10 152 | CD single 2006-01 0.10 153 | CD single 2007-01 0.10 154 | CD single 2008-01 0.00 155 | CD single 2009-01 0.00 156 | CD single 2010-01 0.00 157 | Download Album 1980-01 0.00 158 | Download Album 1981-01 0.00 159 | Download Album 1982-01 0.00 160 | Download Album 1983-01 0.00 161 | Download Album 1984-01 0.00 162 | Download Album 1985-01 0.00 163 | Download Album 1986-01 0.00 164 | Download Album 1987-01 0.00 165 | Download Album 1988-01 0.00 166 | Download Album 1989-01 0.00 167 | Download Album 1990-01 0.00 168 | Download Album 1991-01 0.00 169 | Download Album 1992-01 0.00 170 | Download Album 1993-01 0.00 171 | Download Album 1994-01 0.00 172 | Download Album 1995-01 0.00 173 | Download Album 1996-01 0.00 174 | Download Album 1997-01 0.00 175 | Download Album 1998-01 0.00 176 | Download Album 1999-01 0.00 177 | Download Album 2000-01 0.00 178 | Download Album 2001-01 0.00 179 | Download Album 2002-01 0.00 180 | Download Album 2003-01 0.00 181 | Download Album 2004-01 0.40 182 | Download Album 2005-01 1.20 183 | Download Album 2006-01 2.50 184 | Download Album 2007-01 4.90 185 | Download Album 2008-01 7.90 186 | Download Album 2009-01 11.00 187 | Download Album 2010-01 14.80 188 | Download Music Video 1980-01 0.00 189 | Download Music Video 1981-01 0.00 190 | Download Music Video 1982-01 0.00 191 | Download Music Video 1983-01 0.00 192 | Download Music Video 1984-01 0.00 193 | Download Music Video 1985-01 0.00 194 | Download Music Video 1986-01 0.00 195 | Download Music Video 1987-01 0.00 196 | Download Music Video 1988-01 0.00 197 | Download Music Video 1989-01 0.00 198 | Download Music Video 1990-01 0.00 199 | Download Music Video 1991-01 0.00 200 | Download Music Video 1992-01 0.00 201 | Download Music Video 1993-01 0.00 202 | Download Music Video 1994-01 0.00 203 | Download Music Video 1995-01 0.00 204 | Download Music Video 1996-01 0.00 205 | Download Music Video 1997-01 0.00 206 | Download Music Video 1998-01 0.00 207 | Download Music Video 1999-01 0.00 208 | Download Music Video 2000-01 0.00 209 | Download Music Video 2001-01 0.00 210 | Download Music Video 2002-01 0.00 211 | Download Music Video 2003-01 0.00 212 | Download Music Video 2004-01 0.00 213 | Download Music Video 2005-01 0.00 214 | Download Music Video 2006-01 0.20 215 | Download Music Video 2007-01 0.30 216 | Download Music Video 2008-01 0.50 217 | Download Music Video 2009-01 0.50 218 | Download Music Video 2010-01 0.50 219 | Download Single 1980-01 0.00 220 | Download Single 1981-01 0.00 221 | Download Single 1982-01 0.00 222 | Download Single 1983-01 0.00 223 | Download Single 1984-01 0.00 224 | Download Single 1985-01 0.00 225 | Download Single 1986-01 0.00 226 | Download Single 1987-01 0.00 227 | Download Single 1988-01 0.00 228 | Download Single 1989-01 0.00 229 | Download Single 1990-01 0.00 230 | Download Single 1991-01 0.00 231 | Download Single 1992-01 0.00 232 | Download Single 1993-01 0.00 233 | Download Single 1994-01 0.00 234 | Download Single 1995-01 0.00 235 | Download Single 1996-01 0.00 236 | Download Single 1997-01 0.00 237 | Download Single 1998-01 0.00 238 | Download Single 1999-01 0.00 239 | Download Single 2000-01 0.00 240 | Download Single 2001-01 0.00 241 | Download Single 2002-01 0.00 242 | Download Single 2003-01 0.00 243 | Download Single 2004-01 1.10 244 | Download Single 2005-01 3.10 245 | Download Single 2006-01 5.10 246 | Download Single 2007-01 7.80 247 | Download Single 2008-01 12.50 248 | Download Single 2009-01 16.90 249 | Download Single 2010-01 21.00 250 | DVD Audio 1980-01 0.00 251 | DVD Audio 1981-01 0.00 252 | DVD Audio 1982-01 0.00 253 | DVD Audio 1983-01 0.00 254 | DVD Audio 1984-01 0.00 255 | DVD Audio 1985-01 0.00 256 | DVD Audio 1986-01 0.00 257 | DVD Audio 1987-01 0.00 258 | DVD Audio 1988-01 0.00 259 | DVD Audio 1989-01 0.00 260 | DVD Audio 1990-01 0.00 261 | DVD Audio 1991-01 0.00 262 | DVD Audio 1992-01 0.00 263 | DVD Audio 1993-01 0.00 264 | DVD Audio 1994-01 0.00 265 | DVD Audio 1995-01 0.00 266 | DVD Audio 1996-01 0.00 267 | DVD Audio 1997-01 0.00 268 | DVD Audio 1998-01 0.00 269 | DVD Audio 1999-01 0.00 270 | DVD Audio 2000-01 0.00 271 | DVD Audio 2001-01 0.00 272 | DVD Audio 2002-01 0.10 273 | DVD Audio 2003-01 0.10 274 | DVD Audio 2004-01 0.10 275 | DVD Audio 2005-01 0.10 276 | DVD Audio 2006-01 0.00 277 | DVD Audio 2007-01 0.00 278 | DVD Audio 2008-01 0.00 279 | DVD Audio 2009-01 0.00 280 | DVD Audio 2010-01 0.00 281 | Kiosk 1980-01 0.00 282 | Kiosk 1981-01 0.00 283 | Kiosk 1982-01 0.00 284 | Kiosk 1983-01 0.00 285 | Kiosk 1984-01 0.00 286 | Kiosk 1985-01 0.00 287 | Kiosk 1986-01 0.00 288 | Kiosk 1987-01 0.00 289 | Kiosk 1988-01 0.00 290 | Kiosk 1989-01 0.00 291 | Kiosk 1990-01 0.00 292 | Kiosk 1991-01 0.00 293 | Kiosk 1992-01 0.00 294 | Kiosk 1993-01 0.00 295 | Kiosk 1994-01 0.00 296 | Kiosk 1995-01 0.00 297 | Kiosk 1996-01 0.00 298 | Kiosk 1997-01 0.00 299 | Kiosk 1998-01 0.00 300 | Kiosk 1999-01 0.00 301 | Kiosk 2000-01 0.00 302 | Kiosk 2001-01 0.00 303 | Kiosk 2002-01 0.00 304 | Kiosk 2003-01 0.00 305 | Kiosk 2004-01 0.00 306 | Kiosk 2005-01 0.00 307 | Kiosk 2006-01 0.00 308 | Kiosk 2007-01 0.00 309 | Kiosk 2008-01 0.00 310 | Kiosk 2009-01 0.10 311 | Kiosk 2010-01 0.10 312 | LP/EP 1980-01 59.80 313 | LP/EP 1981-01 58.90 314 | LP/EP 1982-01 53.10 315 | LP/EP 1983-01 44.60 316 | LP/EP 1984-01 35.70 317 | LP/EP 1985-01 29.40 318 | LP/EP 1986-01 21.20 319 | LP/EP 1987-01 14.30 320 | LP/EP 1988-01 8.50 321 | LP/EP 1989-01 3.30 322 | LP/EP 1990-01 1.10 323 | LP/EP 1991-01 0.40 324 | LP/EP 1992-01 0.10 325 | LP/EP 1993-01 0.10 326 | LP/EP 1994-01 0.10 327 | LP/EP 1995-01 0.20 328 | LP/EP 1996-01 0.30 329 | LP/EP 1997-01 0.30 330 | LP/EP 1998-01 0.20 331 | LP/EP 1999-01 0.20 332 | LP/EP 2000-01 0.20 333 | LP/EP 2001-01 0.20 334 | LP/EP 2002-01 0.20 335 | LP/EP 2003-01 0.20 336 | LP/EP 2004-01 0.20 337 | LP/EP 2005-01 0.10 338 | LP/EP 2006-01 0.10 339 | LP/EP 2007-01 0.20 340 | LP/EP 2008-01 0.60 341 | LP/EP 2009-01 0.80 342 | LP/EP 2010-01 1.30 343 | Mobile 1980-01 0.00 344 | Mobile 1981-01 0.00 345 | Mobile 1982-01 0.00 346 | Mobile 1983-01 0.00 347 | Mobile 1984-01 0.00 348 | Mobile 1985-01 0.00 349 | Mobile 1986-01 0.00 350 | Mobile 1987-01 0.00 351 | Mobile 1988-01 0.00 352 | Mobile 1989-01 0.00 353 | Mobile 1990-01 0.00 354 | Mobile 1991-01 0.00 355 | Mobile 1992-01 0.00 356 | Mobile 1993-01 0.00 357 | Mobile 1994-01 0.00 358 | Mobile 1995-01 0.00 359 | Mobile 1996-01 0.00 360 | Mobile 1997-01 0.00 361 | Mobile 1998-01 0.00 362 | Mobile 1999-01 0.00 363 | Mobile 2000-01 0.00 364 | Mobile 2001-01 0.00 365 | Mobile 2002-01 0.00 366 | Mobile 2003-01 0.00 367 | Mobile 2004-01 0.00 368 | Mobile 2005-01 3.40 369 | Mobile 2006-01 6.60 370 | Mobile 2007-01 9.90 371 | Mobile 2008-01 11.10 372 | Mobile 2009-01 9.50 373 | Mobile 2010-01 7.70 374 | Music video 1980-01 0.00 375 | Music video 1981-01 0.00 376 | Music video 1982-01 0.00 377 | Music video 1983-01 0.00 378 | Music video 1984-01 0.00 379 | Music video 1985-01 0.00 380 | Music video 1986-01 0.00 381 | Music video 1987-01 0.00 382 | Music video 1988-01 0.00 383 | Music video 1989-01 1.80 384 | Music video 1990-01 2.30 385 | Music video 1991-01 1.50 386 | Music video 1992-01 1.70 387 | Music video 1993-01 2.10 388 | Music video 1994-01 1.90 389 | Music video 1995-01 1.80 390 | Music video 1996-01 1.90 391 | Music video 1997-01 2.60 392 | Music video 1998-01 2.60 393 | Music video 1999-01 2.60 394 | Music video 2000-01 2.00 395 | Music video 2001-01 2.40 396 | Music video 2002-01 2.30 397 | Music video 2003-01 3.40 398 | Music video 2004-01 4.90 399 | Music video 2005-01 4.90 400 | Music video 2006-01 3.80 401 | Music video 2007-01 4.60 402 | Music video 2008-01 2.50 403 | Music video 2009-01 2.80 404 | Music video 2010-01 2.60 405 | SACD 1980-01 0.00 406 | SACD 1981-01 0.00 407 | SACD 1982-01 0.00 408 | SACD 1983-01 0.00 409 | SACD 1984-01 0.00 410 | SACD 1985-01 0.00 411 | SACD 1986-01 0.00 412 | SACD 1987-01 0.00 413 | SACD 1988-01 0.00 414 | SACD 1989-01 0.00 415 | SACD 1990-01 0.00 416 | SACD 1991-01 0.00 417 | SACD 1992-01 0.00 418 | SACD 1993-01 0.00 419 | SACD 1994-01 0.00 420 | SACD 1995-01 0.00 421 | SACD 1996-01 0.00 422 | SACD 1997-01 0.00 423 | SACD 1998-01 0.00 424 | SACD 1999-01 0.00 425 | SACD 2000-01 0.00 426 | SACD 2001-01 0.00 427 | SACD 2002-01 0.00 428 | SACD 2003-01 0.20 429 | SACD 2004-01 0.10 430 | SACD 2005-01 0.10 431 | SACD 2006-01 0.00 432 | SACD 2007-01 0.00 433 | SACD 2008-01 0.00 434 | SACD 2009-01 0.00 435 | SACD 2010-01 0.00 436 | Subscription 1980-01 0.00 437 | Subscription 1981-01 0.00 438 | Subscription 1982-01 0.00 439 | Subscription 1983-01 0.00 440 | Subscription 1984-01 0.00 441 | Subscription 1985-01 0.00 442 | Subscription 1986-01 0.00 443 | Subscription 1987-01 0.00 444 | Subscription 1988-01 0.00 445 | Subscription 1989-01 0.00 446 | Subscription 1990-01 0.00 447 | Subscription 1991-01 0.00 448 | Subscription 1992-01 0.00 449 | Subscription 1993-01 0.00 450 | Subscription 1994-01 0.00 451 | Subscription 1995-01 0.00 452 | Subscription 1996-01 0.00 453 | Subscription 1997-01 0.00 454 | Subscription 1998-01 0.00 455 | Subscription 1999-01 0.00 456 | Subscription 2000-01 0.00 457 | Subscription 2001-01 0.00 458 | Subscription 2002-01 0.00 459 | Subscription 2003-01 0.00 460 | Subscription 2004-01 0.00 461 | Subscription 2005-01 1.20 462 | Subscription 2006-01 1.80 463 | Subscription 2007-01 2.20 464 | Subscription 2008-01 2.50 465 | Subscription 2009-01 2.80 466 | Subscription 2010-01 2.90 467 | vinyl single 1980-01 6.80 468 | vinyl single 1981-01 6.50 469 | vinyl single 1982-01 7.70 470 | vinyl single 1983-01 7.10 471 | vinyl single 1984-01 6.90 472 | vinyl single 1985-01 6.40 473 | vinyl single 1986-01 4.90 474 | vinyl single 1987-01 3.70 475 | vinyl single 1988-01 2.90 476 | vinyl single 1989-01 1.80 477 | vinyl single 1990-01 1.30 478 | vinyl single 1991-01 0.80 479 | vinyl single 1992-01 0.70 480 | vinyl single 1993-01 0.50 481 | vinyl single 1994-01 0.40 482 | vinyl single 1995-01 0.40 483 | vinyl single 1996-01 0.40 484 | vinyl single 1997-01 0.30 485 | vinyl single 1998-01 0.20 486 | vinyl single 1999-01 0.20 487 | vinyl single 2000-01 0.20 488 | vinyl single 2001-01 0.20 489 | vinyl single 2002-01 0.20 490 | vinyl single 2003-01 0.20 491 | vinyl single 2004-01 0.20 492 | vinyl single 2005-01 0.10 493 | vinyl single 2006-01 0.10 494 | vinyl single 2007-01 0.00 495 | vinyl single 2008-01 0.00 496 | vinyl single 2009-01 0.00 497 | vinyl single 2010-01 0.00 -------------------------------------------------------------------------------- /data/orchestra.csv: -------------------------------------------------------------------------------- 1 | Orchestra type,Group,Instrument,Number 2 | Modern orchestra,Brass,Baritone horn,1 3 | Early Romantic orchestra,Woodwinds,Bass Clarinet,1 4 | Late Romantic orchestra,Woodwinds,Bass Clarinet,1 5 | Early Romantic orchestra,Percussion,Bass Drum,1 6 | Late Romantic orchestra,Percussion,Bass Drum,1 7 | Modern orchestra,Percussion,Bass Drum,1 8 | Classical orchestra,Woodwinds,Bassoons,2 9 | Early Romantic orchestra,Woodwinds,Bassoons,2 10 | Late Romantic orchestra,Woodwinds,Bassoons,3 11 | Modern orchestra,Woodwinds,Bassoons,4 12 | Late Romantic orchestra,Keyboards,Celesta,1 13 | Modern orchestra,Keyboards,Celesta,1 14 | Classical orchestra,Strings,Cellos,2 15 | Early Romantic orchestra,Strings,Cellos,8 16 | Late Romantic orchestra,Strings,Cellos,10 17 | Modern orchestra,Strings,Cellos,12 18 | Late Romantic orchestra,Percussion,Chimes,1 19 | Classical orchestra,Woodwinds,Clarinets,2 20 | Modern orchestra,Woodwinds,Clarinets,4 21 | Early Romantic orchestra,Woodwinds,Clarinets,2 22 | Late Romantic orchestra,Woodwinds,Clarinets,3 23 | Early Romantic orchestra,Woodwinds,Contrabassoon,1 24 | Late Romantic orchestra,Woodwinds,Contrabassoon,1 25 | Early Romantic orchestra,Brass,Cornet,2 26 | Early Romantic orchestra,Percussion,Cymbals,1 27 | Late Romantic orchestra,Percussion,Cymbals,1 28 | Modern orchestra,Percussion,Cymbals,1 29 | Classical orchestra,Strings,Double basses,1 30 | Early Romantic orchestra,Strings,Double basses,6 31 | Late Romantic orchestra,Strings,Double basses,8 32 | Modern orchestra,Strings,Double basses,10 33 | Early Romantic orchestra,Woodwinds,English Horn,1 34 | Late Romantic orchestra,Woodwinds,English Horn,1 35 | Classical orchestra,Woodwinds,Flutes,2 36 | Early Romantic orchestra,Woodwinds,Flutes,2 37 | Late Romantic orchestra,Woodwinds,Flutes,3 38 | Modern orchestra,Woodwinds,Flutes,4 39 | Classical orchestra,Brass,French Horns,4 40 | Early Romantic orchestra,Brass,French Horns,4 41 | Late Romantic orchestra,Brass,French Horns,8 42 | Modern orchestra,Brass,French Horns,8 43 | Early Romantic orchestra,Percussion,Glockenspiel,1 44 | Late Romantic orchestra,Percussion,Glockenspiel,1 45 | Modern orchestra,Percussion,Glockenspiel,1 46 | Early Romantic orchestra,Strings,Harps,1 47 | Late Romantic orchestra,Strings,Harps,2 48 | Modern orchestra,Strings,Harps,2 49 | Modern orchestra,Percussion,Marimba,1 50 | Classical orchestra,Woodwinds,Oboes,2 51 | Early Romantic orchestra,Woodwinds,Oboes,2 52 | Late Romantic orchestra,Woodwinds,Oboes,3 53 | Modern orchestra,Woodwinds,Oboes,4 54 | Late Romantic orchestra,Keyboards,Piano,1 55 | Modern orchestra,Keyboards,Piano,1 56 | Early Romantic orchestra,Woodwinds,Piccolo,1 57 | Late Romantic orchestra,Woodwinds,Piccolo,1 58 | Modern orchestra,Keyboards,Pipe organ,1 59 | Early Romantic orchestra,Percussion,Snare Drum,1 60 | Late Romantic orchestra,Percussion,Snare Drum,1 61 | Modern orchestra,Percussion,Snare Drum,1 62 | Late Romantic orchestra,Percussion,Tam-tam,1 63 | Modern orchestra,Percussion,Tam-tam,1 64 | Early Romantic orchestra,Percussion,Tambourine,1 65 | Late Romantic orchestra,Percussion,Tambourine,1 66 | Modern orchestra,Percussion,Tambourine,1 67 | Modern orchestra,Percussion,Tenor drum,1 68 | Classical orchestra,Percussion,Timpani,2 69 | Early Romantic orchestra,Percussion,Timpani,3 70 | Late Romantic orchestra,Percussion,Timpani,4 71 | Modern orchestra,Percussion,Timpani,1 72 | Early Romantic orchestra,Percussion,Triangle,1 73 | Late Romantic orchestra,Percussion,Triangle,1 74 | Modern orchestra,Percussion,Triangle,1 75 | Early Romantic orchestra,Brass,Trombones,3 76 | Late Romantic orchestra,Brass,Trombones,4 77 | Modern orchestra,Brass,Trombones,6 78 | Classical orchestra,Brass,Trumpets,2 79 | Early Romantic orchestra,Brass,Trumpets,2 80 | Late Romantic orchestra,Brass,Trumpets,4 81 | Modern orchestra,Brass,Trumpets,6 82 | Early Romantic orchestra,Brass,Tubas,1 83 | Late Romantic orchestra,Brass,Tubas,2 84 | Modern orchestra,Brass,Tubas,2 85 | Modern orchestra,Percussion,Tubular bells,1 86 | Modern orchestra,Percussion,Vibraphone,1 87 | Classical orchestra,Strings,Violas,4 88 | Early Romantic orchestra,Strings,Violas,10 89 | Late Romantic orchestra,Strings,Violas,12 90 | Modern orchestra,Strings,Violas,14 91 | Classical orchestra,Strings,Violins 1,6 92 | Early Romantic orchestra,Strings,Violins 1,14 93 | Late Romantic orchestra,Strings,Violins 1,16 94 | Modern orchestra,Strings,Violins 1,18 95 | Classical orchestra,Strings,Violins 2,6 96 | Early Romantic orchestra,Strings,Violins 2,12 97 | Late Romantic orchestra,Strings,Violins 2,14 98 | Modern orchestra,Strings,Violins 2,16 99 | Late Romantic orchestra,Brass,Wagner Tuba,1 100 | Modern orchestra,Percussion,Wood block,1 101 | Late Romantic orchestra,Percussion,Xylophone,1 102 | Modern orchestra,Percussion,Xylophone,1 103 | -------------------------------------------------------------------------------- /doc.hbs: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | id: api 4 | title: API 5 | sidebar_label: API 6 | slug: /api 7 | --- 8 | 9 | {{#orphans ~}} 10 | 11 | {{>header~}} 12 | {{>body}} 13 | {{>member-index~}} 14 | {{>separator~}} 15 | {{>members~}} 16 | 17 | {{/orphans~}} -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": false 4 | }, 5 | "source": { 6 | "include": "./src", 7 | "includePattern": "\\.js$", 8 | "excludePattern": "(node_modules/|docs)" 9 | }, 10 | "plugins": [ 11 | "plugins/markdown" 12 | ], 13 | "opts": { 14 | "template": "node_modules/docdash/", 15 | "encoding": "utf8", 16 | "destination": "docs/", 17 | "recurse": true, 18 | "verbose": true 19 | }, 20 | "templates": { 21 | "cleverLinks": false, 22 | "monospaceLinks": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rawgraphs/rawgraphs-core", 3 | "version": "1.0.0-beta.17", 4 | "description": "RAWGRAPHS core library", 5 | "jsdelivr": "lib/index.umd.js", 6 | "unpkg": "lib/index.umd.js", 7 | "main": "lib/index.cjs.js", 8 | "module": "lib/index.esm.js", 9 | "scripts": { 10 | "test": "jest", 11 | "test-coverage": "jest --coverage", 12 | "codecov": "jest --coverage && codecov", 13 | "prebuild": "rimraf lib", 14 | "build": "rollup -c", 15 | "buildwatch": "rollup -c -w", 16 | "nodebox": "NODE_ENV=test babel-node sandbox/node/index.js", 17 | "webbox": "webpack-dev-server", 18 | "generate-docs": "node_modules/.bin/jsdoc -c jsdoc.json", 19 | "doc2md": "jsdoc2md --no-cache -t doc.hbs src/* | tail -n +2 > website/docs/api.md" 20 | }, 21 | "files": [ 22 | "lib" 23 | ], 24 | "author": "", 25 | "license": "Apache-2.0", 26 | "devDependencies": { 27 | "@babel/core": "^7.8.4", 28 | "@babel/node": "^7.8.4", 29 | "@babel/preset-env": "^7.8.4", 30 | "@rollup/plugin-commonjs": "^11.1.0", 31 | "@rollup/plugin-json": "^4.0.3", 32 | "@rollup/plugin-node-resolve": "^7.1.3", 33 | "babel-jest": "^25.1.0", 34 | "babel-loader": "^8.0.6", 35 | "css-loader": "^3.4.2", 36 | "d3-selection": "^1.4.2", 37 | "dayjs": "^1.8.22", 38 | "docdash": "^1.2.0", 39 | "jest": "^25.1.0", 40 | "jsdoc": "^3.6.4", 41 | "jsdoc-to-markdown": "^6.0.1", 42 | "jsdom": "^16.2.0", 43 | "npm": "^6.14.5", 44 | "rimraf": "^3.0.2", 45 | "rollup": "^1.32.1", 46 | "rollup-plugin-babel": "^4.4.0", 47 | "style-loader": "^1.1.3", 48 | "webpack": "^4.41.6", 49 | "webpack-cli": "^3.3.11", 50 | "webpack-dev-server": "^3.10.3" 51 | }, 52 | "dependencies": { 53 | "d3-array": "^2.4.0", 54 | "d3-axis": "^1.0.12", 55 | "d3-color": "^1.4.1", 56 | "d3-dsv": "^1.2.0", 57 | "d3-format": "^1.4.5", 58 | "d3-interpolate": "^1.4.0", 59 | "d3-quadtree": "^2.0.0", 60 | "d3-scale": "^3.2.1", 61 | "d3-scale-chromatic": "^1.5.0", 62 | "d3-selection": "^1.4.2", 63 | "d3-svg-legend": "^2.25.6", 64 | "d3-time-format": "^2.2.3", 65 | "lodash": "^4.17.15" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | // import fs from 'fs' 3 | import pkg from './package.json' 4 | import commonjs from '@rollup/plugin-commonjs'; 5 | import resolve from '@rollup/plugin-node-resolve'; 6 | import json from '@rollup/plugin-json'; 7 | 8 | 9 | const vendors = [] 10 | // Make all external dependencies to be exclude from rollup 11 | .concat( 12 | Object.keys(pkg.dependencies), 13 | //Object.keys(pkg.peerDependencies), 14 | // 'rxjs/operators', 15 | // 'rocketjump-core/utils', 16 | ) 17 | 18 | 19 | const nonUmdConfig = [ 20 | 'cjs', 'esm' 21 | ].map(format => ({ 22 | input: { 23 | 'index': 'src/index.js', 24 | }, 25 | output: [ 26 | { 27 | dir: 'lib', 28 | entryFileNames: '[name].[format].js', 29 | exports: 'named', 30 | name: 'raw', 31 | format 32 | } 33 | ], 34 | external: vendors, 35 | 36 | plugins: [ 37 | resolve(), 38 | commonjs(), 39 | json(), 40 | babel({ exclude: [ 41 | 'node_modules/**' 42 | ] }), 43 | 44 | ], 45 | })) 46 | 47 | const umdConfig = { 48 | input: { 49 | 'index': 'src/index.js', 50 | }, 51 | output: [ 52 | { 53 | dir: 'lib', 54 | entryFileNames: '[name].[format].js', 55 | exports: 'named', 56 | name: 'raw', 57 | format: 'umd', 58 | } 59 | ], 60 | 61 | plugins: [ 62 | resolve(), 63 | commonjs(), 64 | json(), 65 | babel({ exclude: [ 66 | 'node_modules/**' 67 | ] }), 68 | 69 | ], 70 | } 71 | 72 | export default nonUmdConfig.concat(umdConfig) -------------------------------------------------------------------------------- /sandbox/node/cli.js: -------------------------------------------------------------------------------- 1 | 2 | import program from 'commander' 3 | 4 | 5 | function mapdata(){ 6 | console.log("heu") 7 | } 8 | 9 | 10 | program 11 | .requiredOption('-m, --mapping ', 'mapping argument', JSON.parse) 12 | .requiredOption('-d, --dimensions ', 'dimensions argument', JSON.parse) 13 | .requiredOption('-f, --csv ', 'csv file argument') 14 | 15 | 16 | // allow commander to parse `process.argv` 17 | program.parse(process.argv); 18 | 19 | 20 | -------------------------------------------------------------------------------- /sandbox/node/index.js: -------------------------------------------------------------------------------- 1 | import { chart } from "../../src"; 2 | import testChart from "../../src/testSupport/chart"; 3 | import { tsvParse } from "d3-dsv"; 4 | import { JSDOM } from "jsdom"; 5 | import fs from "fs"; 6 | 7 | var titanic = fs.readFileSync("data/titanic.tsv", "utf8"); 8 | 9 | const dom = new JSDOM(``); 10 | const document = dom.window.document; 11 | 12 | //hack for generating valid svgs with jsdom 13 | const createElementNS = document.createElementNS.bind(document); 14 | document.createElementNS = (ns, name) => { 15 | const o = createElementNS(ns, name); 16 | o.setAttribute("xmlns", "http://www.w3.org/2000/svg"); 17 | return o; 18 | }; 19 | 20 | const div = document.createElement("div"); 21 | 22 | const testData = tsvParse(titanic); 23 | 24 | const dispersionMapping = { 25 | x: { 26 | value: ["Age"], 27 | }, 28 | y: { 29 | value: "Fare", 30 | }, 31 | }; 32 | 33 | const viz = chart(testChart, { 34 | data: testData, 35 | mapping: dispersionMapping, 36 | }); 37 | viz.renderToDOM(div); 38 | 39 | fs.writeFileSync("test.svg", div.innerHTML); 40 | -------------------------------------------------------------------------------- /sandbox/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RAWGRAPHS EXAMPLE 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /sandbox/web/index.js: -------------------------------------------------------------------------------- 1 | import { chart } from "../../src"; 2 | // import testChart from "../../src/testSupport/chart"; 3 | 4 | const div = document.querySelector("#root"); 5 | 6 | console.log(window) 7 | 8 | const testChart = window.rawcharts.bubblechart 9 | 10 | const testData = [ 11 | { x: 10, y: 20 }, 12 | { x: 30, y: 50 }, 13 | { x: 100, y: 20 }, 14 | { x: 50, y: 70 }, 15 | ]; 16 | 17 | const dispersionMapping = { 18 | x: { 19 | value: "x", 20 | }, 21 | y: { 22 | value: "y", 23 | }, 24 | }; 25 | 26 | const viz = chart(testChart, { 27 | data: testData, 28 | mapping: dispersionMapping, 29 | dataTypes: {}, 30 | visualOptions: {}, 31 | }); 32 | 33 | const x = viz.renderToDOM(div); 34 | 35 | -------------------------------------------------------------------------------- /sandbox/web/testfetch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RAWGRAPHS EXAMPLE 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /sandbox/web/testfetch.js: -------------------------------------------------------------------------------- 1 | import { chart, parseDataset } from "../../src"; 2 | 3 | const div = document.querySelector("#root"); 4 | const linechart = window.rawcharts.barchartstacked 5 | 6 | fetch( 7 | "https://raw.githubusercontent.com/pcm-dpc/COVID-19/master/dati-json/dpc-covid19-ita-andamento-nazionale.json" 8 | ) 9 | .then((response) => response.json()) 10 | .then((userData) => { 11 | 12 | const dataTypes = { 13 | totale_ospedalizzati: "number", 14 | isolamento_domiciliare: "number", 15 | data: {type: "date"}, 16 | } 17 | const { dataset, errors } = parseDataset(userData, dataTypes); 18 | const reducedDataset = dataset.slice(dataset.length -100, dataset.length) 19 | const mapping = { 20 | stacks: { value: "data" }, 21 | bars: { value: ["totale_ospedalizzati", "isolamento_domiciliare"], config: { aggregation: ["sum", "sum"] } }, 22 | }; 23 | 24 | const visualOptions = { 25 | colorScale: { 26 | scaleType: "ordinal", 27 | interpolator: "schemeCategory10" 28 | }, 29 | showLegend: true, 30 | marginLeft : 50, 31 | marginBottom : 50, 32 | }; 33 | 34 | const viz = chart(linechart, { 35 | data: reducedDataset, 36 | 37 | mapping, 38 | dataTypes, 39 | 40 | visualOptions, 41 | }); 42 | 43 | viz.renderToDOM(div) 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /src/__tests__/colors.test.js: -------------------------------------------------------------------------------- 1 | import { getColorScale } from "../colors" 2 | 3 | describe("colors", () => { 4 | it("should generate an ordinal color scale with schemeCategory10", () => { 5 | const colorDataset = ["Merlino", "King Kong", "Sandokan"] 6 | const colorDataType = "string" 7 | const scaleType = "ordinal" 8 | const interpolator = "schemeCategory10" 9 | 10 | const colorScale = getColorScale( 11 | colorDataset, 12 | colorDataType, 13 | scaleType, 14 | interpolator 15 | ) 16 | //console.log(colorScale.range(), colorScale.domain()) 17 | 18 | //expect(types).toEqual({ x: "number", y: "number", c: "string", d: "date" }); 19 | }) 20 | 21 | it("should generate an ordinal color scale with interpolateTurbo", () => { 22 | const colorDataset = ["Merlino", "King Kong", "Sandokan"] 23 | const colorDataType = "string" 24 | const scaleType = "ordinal" 25 | const interpolator = "interpolateTurbo" 26 | 27 | const colorScale = getColorScale( 28 | colorDataset, 29 | colorDataType, 30 | scaleType, 31 | interpolator 32 | ) 33 | // console.log(colorScale.range(), colorScale.domain()) 34 | 35 | //expect(types).toEqual({ x: "number", y: "number", c: "string", d: "date" }); 36 | }) 37 | 38 | it("should generate an ordinal color scale with interpolateSpectral", () => { 39 | const colorDataset = ["Merlino", "King Kong", "Sandokan"] 40 | const colorDataType = "string" 41 | const scaleType = "ordinal" 42 | const interpolator = "interpolateSpectral" 43 | 44 | const colorScale = getColorScale( 45 | colorDataset, 46 | colorDataType, 47 | scaleType, 48 | interpolator 49 | ) 50 | // console.log(colorScale.range(), colorScale.domain()) 51 | 52 | //expect(types).toEqual({ x: "number", y: "number", c: "string", d: "date" }); 53 | }) 54 | 55 | it("should generate a sequential color scale with interpolateBlues", () => { 56 | const colorDataset = [1, 2, 3] 57 | const colorDataType = "number" 58 | const scaleType = "sequential" 59 | const interpolator = "interpolateBlues" 60 | 61 | const colorScale = getColorScale( 62 | colorDataset, 63 | colorDataType, 64 | scaleType, 65 | interpolator 66 | ) 67 | // console.log(colorScale.range(), colorScale.domain()) 68 | 69 | //expect(types).toEqual({ x: "number", y: "number", c: "string", d: "date" }); 70 | }) 71 | 72 | it("should generate a diverging color scale with interpolateRdBu", () => { 73 | const colorDataset = [1, 2, 3, 4, 5] 74 | const colorDataType = "number" 75 | const scaleType = "diverging" 76 | const interpolator = "interpolateRdBu" 77 | 78 | const colorScale = getColorScale( 79 | colorDataset, 80 | colorDataType, 81 | scaleType, 82 | interpolator 83 | ) 84 | console.log(colorScale.range(), colorScale.domain()) 85 | 86 | //expect(types).toEqual({ x: "number", y: "number", c: "string", d: "date" }); 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /src/__tests__/dataset.test.js: -------------------------------------------------------------------------------- 1 | import { inferTypes, parseDataset } from "../dataset" 2 | import dayjs from "dayjs" 3 | import customParseFormat from "dayjs/plugin/customParseFormat" 4 | 5 | dayjs.extend(customParseFormat) 6 | 7 | const exampleTypes = [ 8 | String, 9 | Number, 10 | Date, 11 | Boolean, 12 | 13 | "string", 14 | "date", 15 | "number", 16 | "boolean", 17 | 18 | { type: Date, dateFormat: "DD/MM/YYYY" }, 19 | { type: "date", dateFormat: "DD/MM/YYYY" }, 20 | { type: Boolean, decode: (x) => (x.toLowerCase() === "true" ? true : false) }, 21 | ] 22 | 23 | const exampleData = [ 24 | { x: 1, y: 2, c: "M", d: new Date(2020, 0, 1, 0, 0) }, 25 | { x: 2, y: 4, c: "C", d: new Date(2021, 1, 1, 0, 0) }, 26 | { x: 3, y: 6, c: "G", d: new Date(2022, 2, 1, 0, 0) }, 27 | ] 28 | 29 | const exampleDataFormatted = [ 30 | { x: 1, y: 2, c: "M", d: "2020-01-01 00:00" }, 31 | { x: 2, y: 4, c: "C", d: "2021-02-01 00:00" }, 32 | { x: 3, y: 6, c: "G", d: "2022-03-01 00:00" }, 33 | ] 34 | 35 | describe("dataset", () => { 36 | it("should infer correct types", () => { 37 | const types = inferTypes(exampleData) 38 | expect(types).toEqual({ x: "number", y: "number", c: "string", d: "date" }) 39 | }) 40 | 41 | it("should get no errors with date formats with decode", () => { 42 | let types = inferTypes(exampleDataFormatted) 43 | expect(types).toEqual({ 44 | x: "number", 45 | y: "number", 46 | c: "string", 47 | d: "string", 48 | }) 49 | 50 | types.d = { 51 | type: Date, 52 | decode: (value) => dayjs(value, "YYYY-MM-DD HH:mm").toDate(), 53 | } 54 | 55 | const { dataset, dataTypes, errors } = parseDataset( 56 | exampleDataFormatted, 57 | types 58 | ) 59 | const newTypes = inferTypes(dataset) 60 | expect(newTypes).toEqual({ 61 | x: "number", 62 | y: "number", 63 | c: "string", 64 | d: "date", 65 | }) 66 | 67 | expect(dataset).toEqual(exampleData) 68 | 69 | // types.d = { 70 | // type: Date, 71 | // dateFormat: "YYYY-MM-DD HH:mm", 72 | // }; 73 | 74 | // const { 75 | // dataset: datasetWithFormat, 76 | // dataTypes: dataTypesWithFormat, 77 | // errors: errorsWithFormat, 78 | // } = parseDataset(exampleDataFormatted, types); 79 | // expect(datasetWithFormat).toEqual(exampleData); 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /src/__tests__/mapper.test.js: -------------------------------------------------------------------------------- 1 | import fs from "fs" 2 | import makeMapper, { validateMapping } from "../mapping" 3 | import { tsvParse } from "d3-dsv" 4 | import { RAWError } from "../utils" 5 | import { registerAggregation, getAggregatorNames } from "../expressionRegister" 6 | import uniq from "lodash/uniq" 7 | 8 | var titanic = fs.readFileSync("data/titanic.tsv", "utf8") 9 | 10 | const x = { 11 | id: "x", 12 | name: "x", 13 | validTypes: ["number", "date"], 14 | required: true, 15 | operation: "get", 16 | } 17 | 18 | const y = { 19 | id: "y", 20 | name: "y", 21 | validTypes: ["number", "date"], 22 | required: true, 23 | operation: "get", 24 | } 25 | 26 | const groupAgg = { 27 | id: "groupAgg", 28 | name: "groupAgg", 29 | validTypes: ["number", "date"], 30 | required: true, 31 | operation: "groupAggregate", 32 | multiple: true, 33 | } 34 | 35 | const group = { 36 | id: "group", 37 | name: "group", 38 | validTypes: ["number", "date"], 39 | required: true, 40 | operation: "group", 41 | multiple: true, 42 | } 43 | 44 | const dispersionDimensions = [x, y] 45 | const groupAggregateDimensions = [groupAgg, x, y] 46 | const groupDimensions = [group, x, y] 47 | 48 | let testData = tsvParse(titanic) 49 | testData = testData.slice(0, 10) 50 | 51 | const itemsUniq = (items) => uniq(items) 52 | registerAggregation("distinct", itemsUniq) 53 | 54 | const dispersionMapping = { 55 | x: { 56 | value: "Age", 57 | }, 58 | y: { 59 | value: "Fare", 60 | }, 61 | } 62 | 63 | const groupAggregateMapping = { 64 | x: { 65 | value: "Fare", 66 | config: { 67 | // aggregation: rows => mean(rows.map(x => +x)) 68 | aggregation: "mean", 69 | }, 70 | }, 71 | y: { 72 | value: "Age", 73 | }, 74 | groupAgg: { 75 | value: ["Gender", "Destination"], 76 | }, 77 | } 78 | 79 | const groupMapping = { 80 | x: { 81 | value: "Age", 82 | }, 83 | y: { 84 | value: "Fare", 85 | }, 86 | group: { 87 | value: ["Gender", "Destination"], 88 | }, 89 | } 90 | 91 | describe("makeMapper", () => { 92 | it("should perform some mappings", () => { 93 | const mappingFunctionDispersion = makeMapper( 94 | dispersionDimensions, 95 | dispersionMapping 96 | ) 97 | const mappedDataDispersion = mappingFunctionDispersion(testData) 98 | 99 | // console.log(mappedDataDispersion) 100 | 101 | const mappingFunctionGroupAggregate = makeMapper( 102 | groupAggregateDimensions, 103 | groupAggregateMapping 104 | ) 105 | const mappedDataGroupAggregate = mappingFunctionGroupAggregate(testData) 106 | 107 | // console.log(mappedDataGroupAggregate) 108 | 109 | const mappingFunctionGroup = makeMapper(groupDimensions, groupMapping) 110 | const mappedDataGroup = mappingFunctionGroup(testData) 111 | }) 112 | 113 | it("throw an error if a required dimension is not set", () => { 114 | const requiredException = { 115 | y: { 116 | value: "Fare", 117 | }, 118 | group: { 119 | value: ["Gender", "Destination"], 120 | }, 121 | } 122 | expect(() => { 123 | validateMapping(groupDimensions, requiredException) 124 | makeMapper(groupDimensions, requiredException) 125 | }).toThrow(RAWError) 126 | }) 127 | 128 | it("throw an error if multiple is not set on dimension x", () => { 129 | const groupMultipleException = { 130 | x: { 131 | value: ["Age", "Fare"], 132 | }, 133 | y: { 134 | value: "Fare", 135 | }, 136 | group: { 137 | value: ["Gender", "Destination"], 138 | }, 139 | } 140 | expect(() => { 141 | validateMapping(groupDimensions, groupMultipleException) 142 | //makeMapper(groupDimensions, groupMultipleException); 143 | }).toThrow(RAWError) 144 | }) 145 | 146 | it("throw an error if minValues and maxValues are not ok", () => { 147 | const testMappingMinMax = [ 148 | { 149 | id: "x", 150 | name: "x", 151 | validTypes: ["number", "date"], 152 | required: true, 153 | operation: "get", 154 | multiple: true, 155 | minValues: 3, 156 | maxValues: 4, 157 | }, 158 | ] 159 | 160 | const testMappingMinMaxExceptionMin = { 161 | x: { 162 | value: ["Gender", "Destination"], 163 | }, 164 | } 165 | expect(() => { 166 | validateMapping(testMappingMinMax, testMappingMinMaxExceptionMin) 167 | //makeMapper(testMappingMinMax, testMappingMinMaxExceptionMin); 168 | }).toThrow(RAWError) 169 | 170 | const testMappingMinMaxExceptionMax = { 171 | x: { 172 | value: ["Gender", "Destination", "Age", "Fare", "Survival"], 173 | }, 174 | } 175 | expect(() => { 176 | validateMapping(testMappingMinMax, testMappingMinMaxExceptionMax) 177 | makeMapper(testMappingMinMax, testMappingMinMaxExceptionMax) 178 | }).toThrow(RAWError) 179 | }) 180 | 181 | it("tests rollup", () => { 182 | const rollupConfig = [ 183 | { 184 | id: "group", 185 | name: "group", 186 | required: true, 187 | operation: "rollup", 188 | multiple: true, 189 | }, 190 | ] 191 | 192 | const rollupMapping = { 193 | group: { 194 | value: ["Gender"], 195 | }, 196 | } 197 | 198 | const rollupMapper = makeMapper(rollupConfig, rollupMapping) 199 | const rolledUpData = rollupMapper(testData) 200 | 201 | const rollupMappingLeaf = { 202 | group: { 203 | value: ["Gender"], 204 | config: { 205 | leafAggregation: ["distinct", "Port of Embarkation"], 206 | }, 207 | }, 208 | } 209 | const rollupMapperLeaf = makeMapper(rollupConfig, rollupMappingLeaf) 210 | const rolledUpDataLeaf = rollupMapperLeaf(testData) 211 | }) 212 | 213 | const rollupWithLeafConfig = [ 214 | { 215 | id: "group", 216 | name: "group", 217 | required: true, 218 | operation: "rollup", 219 | multiple: true, 220 | }, 221 | { 222 | id: "groupLeaf", 223 | name: "groupLeaf", 224 | required: true, 225 | operation: "rollupLeaf", 226 | }, 227 | ] 228 | const rollupMappingWithLeaf = { 229 | group: { 230 | value: ["Gender"], 231 | }, 232 | groupLeaf: { 233 | value: "Port of Embarkation", 234 | config: { aggregation: "distinct" }, 235 | }, 236 | } 237 | 238 | const rollupMapperWithLeaf = makeMapper( 239 | rollupWithLeafConfig, 240 | rollupMappingWithLeaf 241 | ) 242 | const rolledUpDataWithLeafData = rollupMapperWithLeaf(testData) 243 | }) 244 | -------------------------------------------------------------------------------- /src/__tests__/options.test.js: -------------------------------------------------------------------------------- 1 | describe("options", () => { 2 | it("should test options", () => {}) 3 | 4 | it("throw an error if a required dimension is not set", () => { 5 | // const requiredException = { 6 | // y: { 7 | // value: "Fare", 8 | // }, 9 | // group: { 10 | // value: ["Gender", "Destination"], 11 | // }, 12 | // }; 13 | // expect(() => { 14 | // validateMapping(groupDimensions, requiredException) 15 | // makeMapper(groupDimensions, requiredException); 16 | // }).toThrow(RAWError); 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/__tests__/rawchart.test.js: -------------------------------------------------------------------------------- 1 | import rawChart from "../rawGraphs" 2 | import testChart from "../testSupport/chart" 3 | import { tsvParse } from "d3-dsv" 4 | import { JSDOM } from "jsdom" 5 | import fs from "fs" 6 | import path from "path" 7 | 8 | const dom = new JSDOM(``) 9 | const document = dom.window.document 10 | //hack for generating valid svgs with jsdom 11 | const createElementNS = document.createElementNS.bind(document) 12 | document.createElementNS = (ns, name) => { 13 | const o = createElementNS(ns, name) 14 | o.setAttribute("xmlns", "http://www.w3.org/2000/svg") 15 | return o 16 | } 17 | 18 | var dataPath = path.join(__dirname, "../testSupport/titanic.tsv") 19 | var titanic = fs.readFileSync(dataPath, "utf8") 20 | const testData = tsvParse(titanic) 21 | const dispersionMapping = { 22 | x: { 23 | value: ["Age"], 24 | }, 25 | y: { 26 | value: "Fare", 27 | }, 28 | } 29 | 30 | describe("raw", () => { 31 | it("should be hello raw", () => { 32 | const viz = rawChart(testChart, { 33 | data: testData, 34 | mapping: dispersionMapping, 35 | //dataTypes: some, 36 | visualOptions: {}, 37 | }) 38 | 39 | const { optionsConfig, optionsValues } = viz._getOptions() 40 | console.log(optionsConfig) 41 | console.log(optionsValues) 42 | 43 | const div = document.createElement("div") 44 | viz.renderToDOM(div) 45 | }) 46 | 47 | it("should be hello raw", () => {}) 48 | }) 49 | -------------------------------------------------------------------------------- /src/colors.js: -------------------------------------------------------------------------------- 1 | import * as d3Color from "d3-color" 2 | import * as d3ScaleChromatic from "d3-scale-chromatic" 3 | import { scaleDiverging, scaleSequential, scaleOrdinal } from "d3-scale" 4 | import { min, max, extent } from "d3-array" 5 | import isEqual from "lodash/isEqual" 6 | import get from "lodash/get" 7 | import { quantize, interpolateRgbBasis } from "d3-interpolate" 8 | import uniqBy from "lodash/uniqBy" 9 | import { getValueType } from "./dataset" 10 | import { RawGraphsError } from "./utils" 11 | 12 | const NO_COLOR = "#cccccc" 13 | 14 | const sequential = { 15 | interpolateBlues: { 16 | value: d3ScaleChromatic.interpolateBlues, 17 | label: "Blues (sequential)", 18 | }, 19 | interpolateGreens: { 20 | value: d3ScaleChromatic.interpolateGreens, 21 | label: "Greens (sequential)", 22 | }, 23 | interpolateReds: { 24 | value: d3ScaleChromatic.interpolateReds, 25 | label: "Reds (sequential)", 26 | }, 27 | } 28 | 29 | const diverging = { 30 | interpolateRdBu: { 31 | value: d3ScaleChromatic.interpolateRdBu, 32 | label: "RdBu (diverging)", 33 | }, 34 | interpolateBrBG: { 35 | value: d3ScaleChromatic.interpolateBrBG, 36 | label: "BrBG (diverging)", 37 | }, 38 | interpolatePiYG: { 39 | value: d3ScaleChromatic.interpolatePiYG, 40 | label: "PiYG (diverging)", 41 | }, 42 | } 43 | 44 | const ordinal = { 45 | schemeCategory10: { 46 | value: d3ScaleChromatic.schemeCategory10, 47 | label: "Category10 (ordinal)", 48 | }, 49 | interpolateTurbo: { 50 | value: d3ScaleChromatic.interpolateTurbo, 51 | label: "Interpolate Turbo (ordinal)", 52 | }, 53 | interpolateSpectral: { 54 | value: d3ScaleChromatic.interpolateSpectral, 55 | label: "Interpolate Spectral (ordinal)", 56 | }, 57 | } 58 | /** 59 | * @constant 60 | * @description Color presets objects 61 | */ 62 | export const colorPresets = { 63 | sequential, 64 | diverging, 65 | ordinal, 66 | } 67 | 68 | /** 69 | * @constant 70 | * @description Scale types (names) 71 | */ 72 | export const scaleTypes = Object.keys(colorPresets) 73 | 74 | /** 75 | * 76 | * @param {*} scaleType 77 | * @param {*} domain 78 | * @param {*} interpolator 79 | * @returns {function} a d3 scale 80 | */ 81 | export function getPresetScale(scaleType, domain, interpolator) { 82 | if (scaleType === "sequential") { 83 | if (!colorPresets.sequential[interpolator]) { 84 | throw new RawGraphsError( 85 | `interpolator ${interpolator} not valid for sequential scaletype` 86 | ) 87 | } 88 | return scaleSequential(colorPresets.sequential[interpolator].value) 89 | .domain(domain) 90 | .unknown(NO_COLOR) 91 | .clamp(true) 92 | } else if (scaleType === "diverging") { 93 | if (!colorPresets.diverging[interpolator]) { 94 | throw new RawGraphsError( 95 | `interpolator ${interpolator} not valid for diverging scaletype` 96 | ) 97 | } 98 | return scaleDiverging(colorPresets.diverging[interpolator].value) 99 | .domain(domain) 100 | .unknown(NO_COLOR) 101 | .clamp(true) 102 | } else { 103 | if (!colorPresets.ordinal[interpolator]) { 104 | throw new RawGraphsError( 105 | `interpolator ${interpolator} not valid for ordinal scaletype` 106 | ) 107 | } 108 | const interpolatorValue = colorPresets.ordinal[interpolator].value 109 | let scaleRange = Array.isArray(interpolatorValue) 110 | ? interpolatorValue 111 | : quantize(interpolatorValue, domain.length) 112 | 113 | let finalDomain = domain 114 | 115 | if (scaleRange.length < domain.length) { 116 | finalDomain = domain.slice(0, scaleRange.length) 117 | } 118 | 119 | return scaleOrdinal() 120 | .domain(finalDomain) 121 | .range(scaleRange) 122 | .unknown(NO_COLOR) 123 | } 124 | } 125 | 126 | /** 127 | * Extracts the color domain, given a color dataset, a color data type and a scale type 128 | * for sequential scales will return 2 points domain (min and max values) 129 | * for diverging scales will have 3 points domain (min value, mid value and max value) 130 | * for ordinal scales the domain consists of all unique values found in the color dataset 131 | * @param {*} colorDataset 132 | * @param {*} colorDataType 133 | * @param {*} scaleType 134 | * @returns {Array} 135 | */ 136 | export function getColorDomain(colorDataset, colorDataType, scaleType) { 137 | const sample = get(colorDataset, "[0]") 138 | const sampleDataType = 139 | sample !== undefined ? getValueType(sample) : colorDataType 140 | if (sampleDataType === "string" || scaleType === "ordinal") { 141 | return uniqBy([...colorDataset], (item) => item && item.toString()).sort() 142 | } else { 143 | const typedDataset = colorDataset 144 | if (scaleType === "diverging") { 145 | const minValue = min(typedDataset) 146 | const maxValue = max(typedDataset) 147 | let midValue = 0 148 | if (sampleDataType === "date") { 149 | midValue = new Date((minValue.getTime() + maxValue.getTime()) / 2) 150 | } else { 151 | midValue = (minValue + maxValue) / 2 152 | } 153 | 154 | return [minValue, midValue, maxValue] 155 | } else { 156 | return extent(typedDataset) 157 | } 158 | } 159 | } 160 | 161 | function finalizeScale(inputScale, userScaleValuesMapped, scaleType) { 162 | if ( 163 | inputScale.range && 164 | isEqual( 165 | inputScale.range().map((d) => d3Color.color(d).formatHex()), 166 | userScaleValuesMapped.range 167 | ) 168 | ) { 169 | return inputScale.copy().domain(userScaleValuesMapped.domain) 170 | } else { 171 | if (scaleType === "ordinal") { 172 | return inputScale 173 | .copy() 174 | .domain(userScaleValuesMapped.domain) 175 | .range(userScaleValuesMapped.range) 176 | } else { 177 | return inputScale 178 | .copy() 179 | .domain(userScaleValuesMapped.domain) 180 | .interpolator(interpolateRgbBasis(userScaleValuesMapped.range)) 181 | } 182 | } 183 | } 184 | 185 | function getUserScaleValuesMapped(userScaleValues) { 186 | return { 187 | range: userScaleValues.map((item) => item.range), 188 | domain: userScaleValues.map((item) => item.domain), 189 | } 190 | } 191 | 192 | /** 193 | * Compute the initial ranges and domains, given a domain, a scale type and an interpolator. Used to initialize the values that can be overridden by the user 194 | * @param {*} domain 195 | * @param {*} scaleType 196 | * @param {*} interpolator 197 | * @returns {Array.} 198 | */ 199 | export function getInitialScaleValues(domain, scaleType, interpolator) { 200 | const inputScale = getPresetScale(scaleType, domain, interpolator) 201 | return domain.map((d, i) => ({ 202 | domain: d, 203 | range: d3Color.color(inputScale(d)).formatHex(), 204 | index: i, 205 | })) 206 | } 207 | 208 | /** 209 | * 210 | * @param {Array} colorDataset the array of values of the dataset mapped on the color dimension 211 | * @param {'number'|'string'|'date'|DataTypeObject} colorDataType the type of the 212 | * @param {string} scaleType the name of the scale type used 213 | * @param {string} interpolator the name of the interpolator used (must be compatible with scaleType) 214 | * @param {Array.} userScaleValues overrides of ranges/domains points provided by the user 215 | * @returns {function} The d3 color scale 216 | */ 217 | export function getColorScale( 218 | colorDataset, 219 | colorDataType, 220 | scaleType, 221 | interpolator, 222 | userScaleValues 223 | ) { 224 | if (!colorDataset || !colorDataset.length || !colorDataType) { 225 | return getDefaultColorScale(NO_COLOR) 226 | } 227 | const domain = getColorDomain(colorDataset, colorDataType, scaleType) 228 | const presetScale = getPresetScale(scaleType, domain, interpolator) 229 | const scaleValues = 230 | userScaleValues || getInitialScaleValues(domain, scaleType, interpolator) 231 | const scaleValuesMapped = getUserScaleValuesMapped(scaleValues) 232 | const finalScale = finalizeScale(presetScale, scaleValuesMapped, scaleType) 233 | return finalScale 234 | } 235 | 236 | /** 237 | * @param {*} defaultColor 238 | * @returns A d3 scale that map any value to the default color. 239 | */ 240 | export function getDefaultColorScale(defaultColor) { 241 | return scaleOrdinal().unknown(defaultColor) 242 | } 243 | 244 | /** 245 | * @description gets the array of names of available scale types, given the color data type and color dataset 246 | * @param {*} colorDataType 247 | * @param {*} colorDataset 248 | * @returns {Array.} 249 | */ 250 | export function getAvailableScaleTypes(colorDataType, colorDataset) { 251 | if (!colorDataset || !Array.isArray(colorDataset) || !colorDataset.length) { 252 | return ["ordinal"] 253 | } 254 | 255 | if (colorDataType === "number" || colorDataType === "date") { 256 | const sample = colorDataset[0] 257 | const valueType = getValueType(sample) 258 | if (valueType === "number" || valueType === "date") { 259 | return scaleTypes 260 | } 261 | } 262 | 263 | return ["ordinal"] 264 | } 265 | -------------------------------------------------------------------------------- /src/dataset.js: -------------------------------------------------------------------------------- 1 | import isNumber from "lodash/isNumber" 2 | import isDate from "lodash/isDate" 3 | import isPlainObject from "lodash/isPlainObject" 4 | import isString from "lodash/isString" 5 | import isNaN from "lodash/isNaN" 6 | import get from "lodash/get" 7 | import isFunction from "lodash/isFunction" 8 | import maxBy from "lodash/maxBy" 9 | import { RawGraphsError, getType, NumberParser } from "./utils" 10 | import { timeParse, timeFormatLocale } from "d3-time-format" 11 | import { dateFormats } from "./dateFormats" 12 | 13 | const EMPTY_DATE_MARKER = "__||_||_||__" 14 | 15 | function getFormatter(dataType, parsingOptions = {}) { 16 | if (!isPlainObject(dataType)) { 17 | //we have no format, just trying to parse the date with Date. 18 | if (getType(dataType) === Date) { 19 | return (value) => { 20 | if (!value) { 21 | return EMPTY_DATE_MARKER 22 | } 23 | return new Date(value) 24 | } 25 | } 26 | } 27 | 28 | if (isFunction(dataType.decode)) { 29 | return dataType.decode 30 | } 31 | 32 | //as our date parsers return 'null' when failing parsing we need another marker. see https://github.com/d3/d3-time-format 33 | if (getType(dataType) === Date) { 34 | if (isString(dataType.dateFormat) && !!dateFormats[dataType.dateFormat]) { 35 | const mappedFormat = dateFormats[dataType.dateFormat] 36 | 37 | const parser = parsingOptions.dateLocale 38 | ? timeFormatLocale(parsingOptions.dateLocale).parse(mappedFormat) 39 | : timeParse(mappedFormat) 40 | return (value) => { 41 | if (!value) { 42 | return EMPTY_DATE_MARKER 43 | } 44 | const parsedValue = parser(value) 45 | return parsedValue 46 | } 47 | } 48 | } 49 | 50 | if (getType(dataType) === Number) { 51 | const { locale, decimal, group, numerals } = parsingOptions 52 | if (locale || decimal || group || numerals) { 53 | const numberParser = new NumberParser({ 54 | locale, 55 | decimal, 56 | group, 57 | numerals, 58 | }) 59 | 60 | return (value) => { 61 | return value !== "" ? numberParser.parse(value) : null 62 | } 63 | } 64 | } 65 | 66 | return undefined 67 | } 68 | 69 | export function getValueType(value, options = {}) { 70 | const { strict, locale, numberParser, dateParser } = options 71 | 72 | let jsonValue = value 73 | if (!strict) { 74 | try { 75 | jsonValue = JSON.parse(value) 76 | } catch (err) {} 77 | } 78 | 79 | if (numberParser) { 80 | const numberFromParser = numberParser.parse(jsonValue) 81 | if (isNumber(numberFromParser) && !isNaN(numberFromParser)) { 82 | return { 83 | type: "number", 84 | locale, 85 | decimal: numberParser.decimal, 86 | group: numberParser.group, 87 | numerals: numberParser.numerals, 88 | } 89 | } 90 | } 91 | 92 | if (isNumber(jsonValue)) { 93 | return "number" 94 | } 95 | 96 | // #TODO: understand if we should handle boolean type 97 | // if (isBoolean(jsonValue)) { 98 | // return { 99 | // type: 'string', 100 | // formatBoolean: true, 101 | // } 102 | // } 103 | 104 | if (isDate(value)) { 105 | return "date" 106 | } 107 | 108 | //testing "YYYY-MM-DD" date format 109 | if (dateParser) { 110 | const dateFormatTest = dateFormats["YYYY-MM-DD"] 111 | const testDateWithFormat = dateParser(dateFormatTest)(value) 112 | if (testDateWithFormat !== null) { 113 | return { 114 | type: "date", 115 | dateFormat: "YYYY-MM-DD", 116 | } 117 | } 118 | } 119 | 120 | //testing "YYYY-MM-DDTHH:mm:ss" date format 121 | if (dateParser) { 122 | const dateFormatTest = dateFormats["YYYY-MM-DDTHH:mm:ss"] 123 | const testDateWithFormat = dateParser(dateFormatTest)(value) 124 | if (testDateWithFormat !== null) { 125 | return { 126 | type: "date", 127 | dateFormat: "YYYY-MM-DDTHH:mm:ss", 128 | } 129 | } 130 | } 131 | 132 | return "string" 133 | } 134 | 135 | function castTypeToString(type) { 136 | return type.name ? type.name.toLowerCase() : type 137 | } 138 | 139 | function castTypesToString(types) { 140 | return Object.keys(types).reduce((acc, item) => { 141 | acc[item] = castTypeToString(types[item]) 142 | return acc 143 | }, {}) 144 | } 145 | 146 | /** 147 | * Types guessing 148 | * 149 | * @param {array} data data to be parsed (list of objects) 150 | * @param {parsingOptions} parsingOptions 151 | * @return {DataTypes} the types guessed (object with column names as keys and value type as value) 152 | */ 153 | export function inferTypes(data, parsingOptions = {}) { 154 | let candidateTypes = {} 155 | if (!Array.isArray(data)) { 156 | return candidateTypes 157 | } 158 | 159 | const { 160 | strict, 161 | locale, 162 | decimal, 163 | group, 164 | numerals, 165 | dateLocale, 166 | } = parsingOptions 167 | let numberParser 168 | if (locale || decimal || group || numerals) { 169 | numberParser = new NumberParser({ locale, decimal, group, numerals }) 170 | } 171 | 172 | let dateParser 173 | if (dateLocale) { 174 | dateParser = timeFormatLocale(dateLocale).parse 175 | } else { 176 | dateParser = timeParse 177 | } 178 | 179 | data.forEach((datum, rowIndex) => { 180 | Object.keys(datum).forEach((key) => { 181 | if (candidateTypes[key] === undefined) { 182 | candidateTypes[key] = [] 183 | } 184 | const inferredType = getValueType(datum[key], { 185 | strict, 186 | numberParser, 187 | locale, 188 | dateParser, 189 | }) 190 | candidateTypes[key].push(castTypeToString(inferredType)) 191 | }) 192 | }) 193 | 194 | let inferredTypes = {} 195 | Object.keys(candidateTypes).map((k) => { 196 | let counts = {} 197 | candidateTypes[k].forEach((type) => { 198 | if (!counts[type]) { 199 | counts[type] = { count: 0, value: type } 200 | } 201 | counts[type].count += 1 202 | }) 203 | 204 | const mostFrequentTypeKey = maxBy( 205 | Object.keys(counts), 206 | (t) => counts[t].count 207 | ) 208 | inferredTypes[k] = counts[mostFrequentTypeKey].value 209 | }) 210 | return inferredTypes 211 | } 212 | 213 | function basicGetter(rowValue, dataType) { 214 | if (rowValue === null || rowValue === undefined) { 215 | return null 216 | } 217 | return dataType(rowValue) 218 | } 219 | 220 | function checkTypeAndGetFinalValue(value, type) { 221 | if (type === Number && value !== null && isNaN(value)) { 222 | throw new RawGraphsError(`invalid type number for value ${value}`) 223 | } 224 | 225 | //as our date parsers return 'null' when failing parsing we need another marker. see https://github.com/d3/d3-time-format 226 | if (type === Date) { 227 | if (value === EMPTY_DATE_MARKER) { 228 | return null 229 | } else { 230 | if (!(value instanceof Date)) { 231 | throw new RawGraphsError(`invalid type date for value ${value}`) 232 | } 233 | } 234 | } 235 | 236 | return value 237 | } 238 | 239 | // builds a parser function 240 | function rowParser(types, parsingOptions = {}, onError) { 241 | let propGetters = {} 242 | 243 | Object.keys(types).forEach((k) => { 244 | let dataType = types[k] 245 | const type = getType(dataType) 246 | const formatter = getFormatter(dataType, parsingOptions) 247 | propGetters[k] = (row) => { 248 | const rowValue = get(row, k) 249 | const formattedValue = formatter ? formatter(rowValue) : rowValue 250 | let out = basicGetter(formattedValue, formatter ? (x) => x : type) 251 | out = checkTypeAndGetFinalValue(out, type) 252 | return out 253 | } 254 | }) 255 | 256 | return function (row, i) { 257 | const error = {} 258 | let out = {} 259 | Object.keys(propGetters).forEach((k) => { 260 | const getter = propGetters[k] 261 | try { 262 | out[k] = getter(row) 263 | } catch (err) { 264 | out[k] = undefined 265 | error[k] = err.toString() 266 | } 267 | }) 268 | 269 | if (Object.keys(error).length) { 270 | onError && onError(error, i) 271 | } 272 | return out 273 | } 274 | } 275 | 276 | function filterEmpty(row) { 277 | return Object.values(row).filter((x) => x !== null && x !== "").length > 0 278 | } 279 | 280 | function parseRows(data, dataTypes, parsingOptions) { 281 | //#TODO: eventually add a sentinel to stop parsing 282 | let errors = [] 283 | const parser = rowParser(dataTypes, parsingOptions, (error, i) => { 284 | errors.push({ row: i, error }) 285 | }) 286 | 287 | const dataset = data.map(parser).filter(filterEmpty) 288 | return [dataset, errors] 289 | } 290 | 291 | /** 292 | * Dataset parser 293 | * 294 | * @param {array} data data to be parsed (list of objects) 295 | * @param {DataTypes} [types] optional column types 296 | * @param {ParsingOptions} [parsingOptions] optional parsing options 297 | * @return {ParserResult} dataset, dataTypes, errors 298 | */ 299 | export function parseDataset(data, types, parsingOptions) { 300 | const dataTypes = types || inferTypes(data, parsingOptions) 301 | const [dataset, errors] = parseRows(data, dataTypes, parsingOptions) 302 | return { dataset, dataTypes, errors } 303 | } 304 | -------------------------------------------------------------------------------- /src/dateFormats.js: -------------------------------------------------------------------------------- 1 | //date formats mapping 2 | 3 | /* 4 | 5 | %a - abbreviated weekday name.* 6 | %A - full weekday name.* 7 | %b - abbreviated month name.* 8 | %B - full month name.* 9 | %c - the locale’s date and time, such as %x, %X.* 10 | %d - zero-padded day of the month as a decimal number [01,31]. 11 | %e - space-padded day of the month as a decimal number [ 1,31]; equivalent to %_d. 12 | %f - microseconds as a decimal number [000000, 999999]. 13 | %g - ISO 8601 week-based year without century as a decimal number [00,99]. 14 | %G - ISO 8601 week-based year with century as a decimal number. 15 | %H - hour (24-hour clock) as a decimal number [00,23]. 16 | %I - hour (12-hour clock) as a decimal number [01,12]. 17 | %j - day of the year as a decimal number [001,366]. 18 | %m - month as a decimal number [01,12]. 19 | %M - minute as a decimal number [00,59]. 20 | %L - milliseconds as a decimal number [000, 999]. 21 | %p - either AM or PM.* 22 | %q - quarter of the year as a decimal number [1,4]. 23 | %Q - milliseconds since UNIX epoch. 24 | %s - seconds since UNIX epoch. 25 | %S - second as a decimal number [00,61]. 26 | %u - Monday-based (ISO 8601) weekday as a decimal number [1,7]. 27 | %U - Sunday-based week of the year as a decimal number [00,53]. 28 | %V - ISO 8601 week of the year as a decimal number [01, 53]. 29 | %w - Sunday-based weekday as a decimal number [0,6]. 30 | %W - Monday-based week of the year as a decimal number [00,53]. 31 | %x - the locale’s date, such as %-m/%-d/%Y.* 32 | %X - the locale’s time, such as %-I:%M:%S %p.* 33 | %y - year without century as a decimal number [00,99]. 34 | %Y - year with century as a decimal number, such as 1999. 35 | %Z - time zone offset, such as -0700, -07:00, -07, or Z. 36 | %% - a literal percent sign (%). 37 | 38 | */ 39 | 40 | /* 41 | 42 | Input Example Description 43 | YY 18 Two-digit year 44 | YYYY 2018 Four-digit year 45 | M 1-12 Month, beginning at 1 46 | MM 01-12 Month, 2-digits 47 | MMM Jan-Dec The abbreviated month name 48 | MMMM January-December The full month name 49 | D 1-31 Day of month 50 | DD 01-31 Day of month, 2-digits 51 | H 0-23 Hours 52 | HH 00-23 Hours, 2-digits 53 | h 1-12 Hours, 12-hour clock 54 | hh 01-12 Hours, 12-hour clock, 2-digits 55 | m 0-59 Minutes 56 | mm 00-59 Minutes, 2-digits 57 | s 0-59 Seconds 58 | ss 00-59 Seconds, 2-digits 59 | S 0-9 Hundreds of milliseconds, 1-digit 60 | SS 00-99 Tens of milliseconds, 2-digits 61 | SSS 000-999 Milliseconds, 3-digits 62 | Z -05:00 Offset from UTC 63 | ZZ -0500 Compact offset from UTC, 2-digits 64 | A AM PM Post or ante meridiem, upper-case 65 | a am pm Post or ante meridiem, lower-case 66 | Do 1st... 31st Day of Month with ordinal 67 | 68 | */ 69 | 70 | //#TODO: HANDLE DATEFORMATS WITH REGISTRATION APPROACH + DEFAULT 71 | 72 | const dateTokensMap = { 73 | YYYY: "%Y", 74 | MM: "%m", 75 | DD: "%d", 76 | YY: "%y", 77 | Month: "%B", 78 | HH: "%H", 79 | mm: "%M", 80 | ss: "%S", 81 | } 82 | 83 | export const translateDateFormat = function (df) { 84 | let out = new String(df) 85 | Object.keys(dateTokensMap).forEach((token) => { 86 | const reg = new RegExp(token, "g") 87 | out = out.replace(reg, dateTokensMap[token]) 88 | }) 89 | return out 90 | } 91 | 92 | // actual dateFormats export 93 | const formatsLabels = [ 94 | "YYYY-MM-DD", 95 | "DD/MM/YYYY", 96 | "YYYY-MM", 97 | "YY-MM", 98 | "MM/YY", 99 | "MM/YYYY", 100 | "DD Month YYYY", 101 | "YYYY", 102 | "YYYY-MM-DD HH:mm:ss", 103 | "YYYY-MM-DDTHH:mm:ss", 104 | ] 105 | 106 | export const dateFormats = {} 107 | formatsLabels.forEach((label) => { 108 | dateFormats[label] = translateDateFormat(label) 109 | }) 110 | -------------------------------------------------------------------------------- /src/expressionRegister.js: -------------------------------------------------------------------------------- 1 | import { RawGraphsError, getTypeName } from "./utils" 2 | import mean from "lodash/mean" 3 | import max from "lodash/max" 4 | import min from "lodash/min" 5 | import sum from "lodash/sum" 6 | import isFunction from "lodash/isFunction" 7 | import isString from "lodash/isString" 8 | import uniq from "lodash/uniq" 9 | import range from "lodash/range" 10 | import find from "lodash/find" 11 | import get from "lodash/get" 12 | import isPlainObject from "lodash/isPlainObject" 13 | import { median } from "d3-array" 14 | // import first from 'lodash/first' 15 | // import last from 'lodash/last' 16 | 17 | const aggregationsRegister = {} 18 | 19 | export function registerAggregation(name, fun) { 20 | aggregationsRegister[name] = fun 21 | } 22 | 23 | export function unregisterAggregation(name) { 24 | delete aggregationsRegister[name] 25 | } 26 | 27 | export function getAggregatorNames() { 28 | return Object.keys(aggregationsRegister) 29 | } 30 | 31 | export function getAggregator(aggregatorExpression) { 32 | if (isFunction(aggregatorExpression)) { 33 | return aggregatorExpression 34 | } 35 | 36 | if (isString(aggregatorExpression)) { 37 | if (aggregationsRegister[aggregatorExpression]) { 38 | return aggregationsRegister[aggregatorExpression] 39 | } else { 40 | throw new RawGraphsError( 41 | `Aggregator "${aggregatorExpression}" is is not registered in RawGraphs.` 42 | ) 43 | } 44 | } 45 | } 46 | 47 | export function getAggregatorArray(aggregator, length) { 48 | return function (items) { 49 | return range(length).map((idx) => { 50 | const aggregatorExpression = Array.isArray(aggregator) 51 | ? get(aggregator, idx, aggregator[0]) 52 | : aggregator 53 | const aggr = getAggregator(aggregatorExpression) 54 | return aggr(items.map((i) => i[idx])) 55 | }) 56 | } 57 | } 58 | 59 | // Aggregators available in RAW 60 | // general purpose 61 | registerAggregation("count", (items) => items.length) 62 | registerAggregation("countDistinct", (items) => uniq(items).length) 63 | // #TODO understand if we must add these 64 | // registerAggregation("last", last) 65 | // registerAggregation("first", first) 66 | 67 | // numbers 68 | registerAggregation("mean", mean) 69 | registerAggregation("max", max) 70 | registerAggregation("min", min) 71 | registerAggregation("sum", sum) 72 | registerAggregation("median", median) 73 | 74 | //string 75 | const commaSeparated = (items) => items.join(",") 76 | // #TODO understand if we must add these 77 | // const tabSeparated = items => items.join("\t") 78 | // const newLineSeparated = items => items.join("\n") 79 | // const itemsList = items => items 80 | // const itemsUniq = items => uniq(items) 81 | 82 | registerAggregation("csv", commaSeparated) 83 | registerAggregation("csvDistinct", (items) => commaSeparated(uniq(items))) 84 | // #TODO understand if we must add these 85 | // registerAggregation("commaSeparated", commaSeparated) 86 | // registerAggregation("tsv", tabSeparated) 87 | // registerAggregation("tsvDistinct", items => tabSeparated(uniq(items))) 88 | // registerAggregation("tabSeparated", tabSeparated) 89 | // registerAggregation("newLineSeparated", newLineSeparated) 90 | // registerAggregation("list", itemsList) 91 | // registerAggregation("distinct", itemsUniq) 92 | 93 | export function getDefaultDimensionAggregation(dimension, dataType) { 94 | if (!dimension.aggregation) { 95 | throw new RawGraphsError(`Dimension ${dimension.id} is not aggregable`) 96 | } 97 | const names = getAggregatorNames() 98 | 99 | const typeName = getTypeName(dataType) 100 | const defaultAggregation = get(dimension, "aggregationDefault") 101 | 102 | //#TODO check that default aggregation exists in registered ones 103 | if (defaultAggregation) { 104 | if (isPlainObject(defaultAggregation)) { 105 | return get(defaultAggregation, typeName, names[0]) 106 | } else { 107 | return defaultAggregation 108 | } 109 | } 110 | return names[0] 111 | } 112 | 113 | export function getDimensionAggregator( 114 | dimensionId, 115 | mapping, 116 | dataTypes, 117 | dimensions 118 | ) { 119 | const dimension = find(dimensions, (x) => x.id === dimensionId) 120 | 121 | const mappingValue = get( 122 | mapping[dimensionId], 123 | "value", 124 | dimension.multiple ? [] : undefined 125 | ) 126 | 127 | //#TODO: this is done to return function returning a scalar in any case 128 | // works well with undefined "size" dimensions (See matrix plot at rawgraphs-charts at commit 04013f633e32f4c630a5db2b855c6cf270b3af03), 129 | // but this needs investigation 130 | if (!dimension.multiple && !mappingValue) { 131 | return () => 1 132 | } 133 | 134 | function getSingleDim(dimension, columnName, index) { 135 | const dataType = get(dataTypes, columnName) 136 | const defaultAggregation = getDefaultDimensionAggregation( 137 | dimension, 138 | dataType 139 | ) 140 | let aggregation = get( 141 | mapping[dimension.id], 142 | "config.aggregation", 143 | defaultAggregation 144 | ) 145 | if (index !== undefined) { 146 | aggregation = aggregation[index] 147 | } 148 | const aggregator = getAggregator(aggregation) 149 | return aggregator 150 | } 151 | 152 | if (Array.isArray(mappingValue)) { 153 | const out = mappingValue.map((columnName, i) => 154 | getSingleDim(dimension, columnName, i) 155 | ) 156 | return out 157 | } else { 158 | return getSingleDim(dimension, mappingValue) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/groupBy.js: -------------------------------------------------------------------------------- 1 | import get from "lodash/get" 2 | 3 | export default function groupByAsMap(arr, getter) { 4 | return arr.reduce(function (obj, item) { 5 | const groupKey = get(item, getter) 6 | 7 | if (!obj.has(groupKey)) { 8 | obj.set(groupKey, []) 9 | } 10 | 11 | obj.set(groupKey, obj.get(groupKey).concat([item])) 12 | 13 | return obj 14 | }, new Map()) 15 | } 16 | -------------------------------------------------------------------------------- /src/importExport/importExportV1.1.js: -------------------------------------------------------------------------------- 1 | import get from "lodash/get" 2 | import has from "lodash/has" 3 | import { matrixToObjects, objectsToMatrix } from "./utils" 4 | 5 | export const VERSION = "1.1" 6 | 7 | export function serializeProject({ 8 | userInput, 9 | userData, 10 | userDataType, 11 | parseError, 12 | unstackedData, 13 | unstackedColumns, 14 | data, 15 | separator, 16 | thousandsSeparator, 17 | decimalsSeparator, 18 | locale, 19 | stackDimension, 20 | dataSource, 21 | currentChart, 22 | mapping, 23 | visualOptions, 24 | }) { 25 | const project = { 26 | version: VERSION, 27 | } 28 | 29 | /* First stage: user input */ 30 | project.userInput = userInput 31 | project.userInputFormat = userDataType 32 | project.dataSource = dataSource 33 | 34 | /* Second stage: parsed */ 35 | project.rawData = objectsToMatrix(userData, Object.keys(data.dataTypes)) 36 | project.parseError = parseError 37 | project.parseOptions = { 38 | separator, 39 | thousandsSeparator, 40 | decimalsSeparator, 41 | locale, 42 | stackDimension, 43 | unstackedData, 44 | unstackedColumns, 45 | } 46 | 47 | /* Third stage: typed data ready for chart */ 48 | project.dataTypes = data.dataTypes 49 | 50 | /* Chart: mapping and visual options */ 51 | project.chart = currentChart.metadata.id 52 | project.mapping = mapping 53 | project.visualOptions = visualOptions 54 | 55 | return project 56 | } 57 | 58 | function getOrError(object, path) { 59 | if (!has(object, path)) { 60 | console.log("IMPORT ERROR", object, path) 61 | throw new Error("Selected project is not valid") 62 | } 63 | return get(object, path) 64 | } 65 | 66 | export function deserializeProject(project, charts) { 67 | if (project.version !== VERSION) { 68 | throw new Error( 69 | "Invalid version number, please use a suitable deserializer" 70 | ) 71 | } 72 | 73 | const chartId = getOrError(project, "chart") 74 | const chart = charts.find((c) => c.metadata.id === chartId) 75 | if (!chart) { 76 | throw new Error("Unknown chart!") 77 | } 78 | 79 | return { 80 | userInput: getOrError(project, "userInput"), 81 | userData: matrixToObjects( 82 | getOrError(project, "rawData"), 83 | Object.keys(getOrError(project, "dataTypes")) 84 | ), 85 | userDataType: getOrError(project, "userInputFormat"), 86 | parseError: getOrError(project, "parseError"), 87 | unstackedData: getOrError(project, "parseOptions.unstackedData"), 88 | unstackedColumns: getOrError(project, "parseOptions.unstackedColumns"), 89 | dataTypes: getOrError(project, "dataTypes"), 90 | separator: getOrError(project, "parseOptions.separator"), 91 | thousandsSeparator: getOrError(project, "parseOptions.thousandsSeparator"), 92 | decimalsSeparator: getOrError(project, "parseOptions.decimalsSeparator"), 93 | locale: getOrError(project, "parseOptions.locale"), 94 | stackDimension: get(project, "parseOptions.stackDimension", undefined), 95 | dataSource: getOrError(project, "dataSource"), 96 | currentChart: chart, 97 | mapping: getOrError(project, "mapping"), 98 | visualOptions: getOrError(project, "visualOptions"), 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/importExport/importExportV1.2.js: -------------------------------------------------------------------------------- 1 | import get from "lodash/get" 2 | import has from "lodash/has" 3 | import { matrixToObjects, objectsToMatrix } from "./utils" 4 | 5 | export const VERSION = "1.2" 6 | 7 | export function serializeProject({ 8 | userInput, 9 | userData, 10 | userDataType, 11 | parseError, 12 | unstackedData, 13 | unstackedColumns, 14 | data, 15 | separator, 16 | thousandsSeparator, 17 | decimalsSeparator, 18 | locale, 19 | stackDimension, 20 | dataSource, 21 | currentChart, 22 | mapping, 23 | visualOptions, 24 | customChart, 25 | }) { 26 | const project = { 27 | version: VERSION, 28 | } 29 | 30 | /* First stage: user input */ 31 | project.userInput = userInput 32 | project.userInputFormat = userDataType 33 | project.dataSource = dataSource 34 | 35 | /* Second stage: parsed */ 36 | project.rawData = objectsToMatrix(userData, Object.keys(data.dataTypes)) 37 | project.parseError = parseError 38 | project.parseOptions = { 39 | separator, 40 | thousandsSeparator, 41 | decimalsSeparator, 42 | locale, 43 | stackDimension, 44 | unstackedData, 45 | unstackedColumns, 46 | } 47 | 48 | /* Third stage: typed data ready for chart */ 49 | project.dataTypes = data.dataTypes 50 | 51 | /* Chart: mapping and visual options */ 52 | project.chart = currentChart.metadata.id 53 | project.mapping = mapping 54 | project.visualOptions = visualOptions 55 | 56 | /* Custom chart */ 57 | project.customChart = customChart 58 | 59 | return project 60 | } 61 | 62 | function getOrError(object, path) { 63 | if (!has(object, path)) { 64 | console.log("IMPORT ERROR", object, path) 65 | throw new Error("Selected project is not valid") 66 | } 67 | return get(object, path) 68 | } 69 | 70 | export function deserializeProject(project, defaultCharts) { 71 | if (project.version !== VERSION) { 72 | throw new Error( 73 | "Invalid version number, please use a suitable deserializer" 74 | ) 75 | } 76 | 77 | const chartId = getOrError(project, "chart") 78 | let chart = defaultCharts.find((c) => c.metadata.id === chartId) 79 | if (!chart) { 80 | if (!project.customChart) { 81 | throw new Error("Unknown chart!") 82 | } 83 | // NOTE: OK, this is not very good... 84 | // But the alternativie is to break to the deserializeProject signature 85 | // and make it async ... for now we try to mantein the same signature 86 | // of old importers 87 | chart = { metadata: { id: chartId }, rawCustomChart: project.customChart } 88 | } 89 | 90 | return { 91 | userInput: getOrError(project, "userInput"), 92 | userData: matrixToObjects( 93 | getOrError(project, "rawData"), 94 | Object.keys(getOrError(project, "dataTypes")) 95 | ), 96 | userDataType: getOrError(project, "userInputFormat"), 97 | parseError: getOrError(project, "parseError"), 98 | unstackedData: getOrError(project, "parseOptions.unstackedData"), 99 | unstackedColumns: getOrError(project, "parseOptions.unstackedColumns"), 100 | dataTypes: getOrError(project, "dataTypes"), 101 | separator: getOrError(project, "parseOptions.separator"), 102 | thousandsSeparator: getOrError(project, "parseOptions.thousandsSeparator"), 103 | decimalsSeparator: getOrError(project, "parseOptions.decimalsSeparator"), 104 | locale: getOrError(project, "parseOptions.locale"), 105 | stackDimension: get(project, "parseOptions.stackDimension", undefined), 106 | dataSource: getOrError(project, "dataSource"), 107 | currentChart: chart, 108 | mapping: getOrError(project, "mapping"), 109 | visualOptions: getOrError(project, "visualOptions"), 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/importExport/importExportV1.js: -------------------------------------------------------------------------------- 1 | import get from "lodash/get" 2 | import has from "lodash/has" 3 | import { matrixToObjects, objectsToMatrix } from "./utils" 4 | 5 | export const VERSION = "1" 6 | 7 | export function serializeProject({ 8 | userInput, 9 | userData, 10 | userDataType, 11 | parseError, 12 | unstackedData, 13 | unstackedColumns, 14 | data, 15 | separator, 16 | thousandsSeparator, 17 | decimalsSeparator, 18 | locale, 19 | stackDimension, 20 | dataSource, 21 | currentChart, 22 | mapping, 23 | visualOptions, 24 | }) { 25 | const project = { 26 | version: "1", 27 | } 28 | 29 | /* First stage: user input */ 30 | project.userInput = userInput 31 | project.userInputFormat = userDataType 32 | project.dataSource = dataSource 33 | 34 | /* Second stage: parsed */ 35 | project.rawData = objectsToMatrix(userData, Object.keys(data.dataTypes)) 36 | project.parseError = parseError 37 | project.parseOptions = { 38 | separator, 39 | thousandsSeparator, 40 | decimalsSeparator, 41 | locale, 42 | stackDimension, 43 | unstackedData, 44 | unstackedColumns, 45 | } 46 | 47 | /* Third stage: typed data ready for chart */ 48 | project.dataTypes = data.dataTypes 49 | 50 | /* Chart: mapping and visual options */ 51 | project.chart = currentChart.metadata.name 52 | project.mapping = mapping 53 | project.visualOptions = visualOptions 54 | 55 | return project 56 | } 57 | 58 | function getOrError(object, path) { 59 | if (!has(object, path)) { 60 | console.log("IMPORT ERROR", object, path) 61 | throw new Error("Selected project is not valid") 62 | } 63 | return get(object, path) 64 | } 65 | 66 | export function deserializeProject(project, charts) { 67 | if (project.version !== "1") { 68 | throw new Error( 69 | "Invalid version number, please use a suitable deserializer" 70 | ) 71 | } 72 | 73 | const chartName = getOrError(project, "chart") 74 | const chart = charts.find((c) => c.metadata.name === chartName) 75 | if (!chart) { 76 | throw new Error("Unknown chart!") 77 | } 78 | 79 | return { 80 | userInput: getOrError(project, "userInput"), 81 | userData: matrixToObjects( 82 | getOrError(project, "rawData"), 83 | Object.keys(getOrError(project, "dataTypes")) 84 | ), 85 | userDataType: getOrError(project, "userInputFormat"), 86 | parseError: getOrError(project, "parseError"), 87 | unstackedData: getOrError(project, "parseOptions.unstackedData"), 88 | unstackedColumns: getOrError(project, "parseOptions.unstackedColumns"), 89 | dataTypes: getOrError(project, "dataTypes"), 90 | separator: getOrError(project, "parseOptions.separator"), 91 | thousandsSeparator: getOrError(project, "parseOptions.thousandsSeparator"), 92 | decimalsSeparator: getOrError(project, "parseOptions.decimalsSeparator"), 93 | locale: getOrError(project, "parseOptions.locale"), 94 | stackDimension: get(project, "parseOptions.stackDimension", undefined), 95 | dataSource: getOrError(project, "dataSource"), 96 | currentChart: chart, 97 | mapping: getOrError(project, "mapping"), 98 | visualOptions: getOrError(project, "visualOptions"), 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/importExport/index.js: -------------------------------------------------------------------------------- 1 | import * as V1 from "./importExportV1" 2 | import * as V1_1 from "./importExportV1.1" 3 | import * as V1_2 from "./importExportV1.2" 4 | import get from "lodash/get" 5 | import keyBy from "lodash/keyBy" 6 | 7 | const DESERIALIZERS = keyBy([V1, V1_1, V1_2], "VERSION") 8 | 9 | /** 10 | * Serializes a rawgraphs project to json format 11 | * @param {Object} project 12 | * @param {string} [version="latest"] 13 | * @returns {string} 14 | */ 15 | 16 | export function serializeProject(project, version = "latest") { 17 | const defaultSerializer = 18 | version === "latest" 19 | ? DESERIALIZERS[V1_2.VERSION].serializeProject 20 | : () => { 21 | throw new Error("No serializer found for version " + version) 22 | } 23 | const serializer = get( 24 | DESERIALIZERS, 25 | version + ".serializeProject", 26 | defaultSerializer 27 | ) 28 | return serializer(project) 29 | } 30 | 31 | 32 | /** 33 | * Deserializes a project from JSON 34 | * @param {string} serializedProject 35 | * @param {object} charts 36 | * @returns 37 | */ 38 | export function deserializeProject(serializedProject, charts) { 39 | try { 40 | const parsedProject = JSON.parse(serializedProject) 41 | const version = get(parsedProject, "version", "unknown") 42 | if (DESERIALIZERS[version]) { 43 | try { 44 | return DESERIALIZERS[version].deserializeProject(parsedProject, charts) 45 | } catch (e) { 46 | throw new Error("Can't open your project. " + e.message) 47 | } 48 | } else { 49 | throw new Error("Can't open your project. Invalid file") 50 | } 51 | } catch (e) { 52 | throw new Error("Can't open your project. " + e.message) 53 | } 54 | } 55 | 56 | export function registerSerializerDeserializer( 57 | version, 58 | serializer, 59 | deserializer 60 | ) { 61 | DESERIALIZERS[version] = { 62 | serializeProject: serializer, 63 | deserializeProject: deserializer, 64 | VERSION: version, 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/importExport/utils.js: -------------------------------------------------------------------------------- 1 | 2 | export function objectsToMatrix(listOfObjects, columns) { 3 | return listOfObjects.map((obj) => { 4 | return columns.map((col) => obj[col]) 5 | }) 6 | } 7 | 8 | export function matrixToObjects(matrix, columns) { 9 | return matrix.map((record) => { 10 | const obj = {} 11 | for (let i = 0; i < columns.length; i++) { 12 | obj[columns[i]] = record[i] 13 | } 14 | return obj 15 | }) 16 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as chart } from "./rawGraphs" 2 | export { parseDataset, inferTypes, getValueType } from "./dataset" 3 | export { 4 | default as makeMapper, 5 | validateMapping, 6 | validateMapperDefinition, 7 | arrayGetter, 8 | } from "./mapping" 9 | export { 10 | registerAggregation, 11 | unregisterAggregation, 12 | getAggregator, 13 | getAggregatorNames, 14 | getDefaultDimensionAggregation, 15 | getDimensionAggregator, 16 | } from "./expressionRegister" 17 | export { 18 | baseOptions, 19 | overrideBaseOptions, 20 | getDefaultOptionsValues, 21 | getOptionsConfig, 22 | getEnabledOptions, 23 | getContainerOptions, 24 | } from "./options" 25 | export { 26 | getInitialScaleValues, 27 | getColorScale, 28 | getDefaultColorScale, 29 | getPresetScale, 30 | colorPresets, 31 | getColorDomain, 32 | getAvailableScaleTypes, 33 | } from "./colors" 34 | export { getTypeName, NumberParser } from "./utils" 35 | export { legend } from "./legend" 36 | export { labelsOcclusion } from "./labels" 37 | export { dateFormats, translateDateFormat } from "./dateFormats" 38 | 39 | export { 40 | serializeProject, 41 | deserializeProject, 42 | registerSerializerDeserializer, 43 | } from "./importExport" 44 | -------------------------------------------------------------------------------- /src/labels.js: -------------------------------------------------------------------------------- 1 | import { descending } from "d3-array" 2 | import { select } from "d3-selection" 3 | import { quadtree as qt } from "d3-quadtree" 4 | // adapted from https://observablehq.com/@bmschmidt/finding-text-occlusion-with-quadtrees 5 | 6 | function hasOverlaps(corners, compCorners) { 7 | return ( 8 | corners[2] < compCorners[3] && 9 | corners[3] > compCorners[2] && 10 | corners[0] < compCorners[1] && 11 | corners[1] > compCorners[0] 12 | ) 13 | } 14 | 15 | function insert_and_check(datum, quadtree) { 16 | const corners = datum._bbox 17 | quadtree._max_width = quadtree._max_width || 0 18 | quadtree._max_height = quadtree._max_height || 0 19 | datum._occluded = false 20 | 21 | quadtree["visit"]( 22 | (node, x0, y0, x1, y1) => { 23 | if (datum._occluded) { 24 | return true 25 | } 26 | 27 | if (node.length) { 28 | const box_intersects_quad = hasOverlaps(corners, [ 29 | x0 - quadtree._max_width / 2, 30 | x1 + quadtree._max_width / 2, 31 | y0 - quadtree._max_height / 2, 32 | y1 + quadtree._max_height / 2, 33 | ]) 34 | if (!box_intersects_quad) { 35 | return true 36 | } else { 37 | return undefined 38 | } 39 | } else { 40 | if (hasOverlaps(corners, node.data._bbox)) { 41 | datum._occluded = true 42 | return "break" 43 | } 44 | } 45 | }, 46 | [quadtree.x()(datum), quadtree.y()(datum)] 47 | ) 48 | 49 | if (!datum._occluded) { 50 | quadtree.add(datum) 51 | if (quadtree._max_width < corners[1] - corners[0]) { 52 | quadtree._max_width = corners[1] - corners[0] 53 | } 54 | if (quadtree._max_height < corners[3] - corners[2]) { 55 | quadtree._max_height = corners[3] - corners[2] 56 | } 57 | } 58 | } 59 | 60 | function formatOcclusion(data) { 61 | let labels 62 | labels = qt() 63 | .x((d) => (d._bbox[0] + d._bbox[1]) / 2) 64 | .y((d) => (d._bbox[2] + d._bbox[3]) / 2) 65 | 66 | //labels.extent([-80, -35], [width + 80, height + 35]); 67 | data.forEach((d, i) => { 68 | insert_and_check(d, labels) 69 | d.order = i 70 | }) 71 | } 72 | 73 | export function labelsOcclusion(d3Selection, priority = (d) => d.priority) { 74 | if (!d3Selection.size()) return 75 | const labels = [] 76 | d3Selection.each((d, i, e) => { 77 | const bbox = e[i].getBoundingClientRect() 78 | labels.push({ 79 | priority: priority(d) || 0, 80 | node: e[i], 81 | _bbox: [bbox.x, bbox.x + bbox.width, bbox.y, bbox.y + bbox.height], 82 | }) 83 | }) 84 | 85 | labels.sort((a, b) => descending(a.priority, b.priority)) 86 | formatOcclusion(labels) 87 | const filled = [] 88 | 89 | labels.forEach((d) => { 90 | select(d.node).style("opacity", d._occluded ? 0 : 1) 91 | if (!d._occluded) filled.push(d) 92 | }) 93 | 94 | return filled 95 | } 96 | -------------------------------------------------------------------------------- /src/legend.js: -------------------------------------------------------------------------------- 1 | import * as d3Legend from "d3-svg-legend" 2 | import { format } from "d3-format" 3 | 4 | function scaleType(scale) { 5 | if (scale.interpolate) { 6 | return "continuous" 7 | } else if (scale.interpolator) { 8 | return "sequential" 9 | } else if (scale.invertExtent) { 10 | return "other" 11 | } else { 12 | return "ordinal" 13 | } 14 | } 15 | 16 | export function legend( 17 | legendColor, 18 | legendSize, 19 | legendWidth = 200, 20 | shapePadding = 5, 21 | shapeWidth = 15, 22 | shapeHeight = 15, 23 | margin = { top: 0, right: 5, bottom: 0, left: 5 } 24 | ) { 25 | const legendFn = (_selection) => { 26 | let d3LegendSize, d3legendColor 27 | const w = legendWidth - margin.left - margin.right 28 | 29 | const legendContainer = _selection 30 | .append("g") 31 | .attr("transform", `translate(${margin.left},${0})`) 32 | 33 | //draw size scale 34 | if (legendSize && legendSize.title) { 35 | legendContainer 36 | .append("g") 37 | .attr("class", "legendSize") 38 | .attr("transform", `translate(0,${margin.top})`) 39 | 40 | let d3LegendSize = d3Legend 41 | .legendSize() 42 | .scale(legendSize.scale) 43 | .cells(legendSize.scale.domain()) 44 | .shape(legendSize.shape ? legendSize.shape : "circle") 45 | .title(legendSize.title) 46 | .titleWidth(w) 47 | .labelWrap(w - shapePadding - shapeWidth) 48 | .labelOffset(5) 49 | .shapePadding( 50 | legendSize.shape === "circle" 51 | ? legendSize.scale.range()[1] 52 | : shapePadding 53 | ) 54 | 55 | legendContainer.select(".legendSize").call(d3LegendSize) 56 | } 57 | //draw color scale 58 | if (legendColor && legendColor.title) { 59 | const legendColorHeight = legendContainer.select(".legendSize").empty() 60 | ? 0 61 | : legendContainer.select(".legendSize").node().getBBox().height + 20 62 | 63 | legendContainer 64 | .append("g") 65 | .attr("class", "legendColor") 66 | .attr("transform", "translate(0," + legendColorHeight + ")") 67 | 68 | d3legendColor = d3Legend 69 | .legendColor() 70 | .shapePadding(shapePadding) 71 | .title(legendColor.title) 72 | .titleWidth(w) 73 | .labelWrap(w - shapePadding - shapeWidth) 74 | .labelOffset(5) 75 | .scale(legendColor.scale) 76 | 77 | if (scaleType(legendColor.scale) !== "ordinal") { 78 | d3legendColor 79 | .shapePadding(0) 80 | .orient("horizontal") 81 | .shapeWidth(1) 82 | .shapeHeight(10) 83 | .cells(w) 84 | .classPrefix("horizontal-") 85 | .labelAlign("start") 86 | .labels(({ i, genLength, generatedLabels, domain }) => { 87 | if (i === 0 || i === genLength - 1) { 88 | return generatedLabels[i] 89 | } 90 | if (domain.length === 3 && i === genLength / 2 - 1) { 91 | return format(".01f")((domain[0] + domain[2]) / 2) 92 | } 93 | }) 94 | } 95 | 96 | legendContainer.select(".legendColor").call(d3legendColor) 97 | } 98 | 99 | //Hardcore style with much love 100 | legendContainer 101 | .selectAll("text") 102 | .attr("font-family", '"Arial", sans-serif') 103 | .attr("font-size", "10px") 104 | 105 | legendContainer 106 | .selectAll(".legendTitle") 107 | .attr("font-size", "12px") 108 | .attr("font-weight", "bold") 109 | 110 | legendContainer 111 | .selectAll(".horizontal-legendTitle") 112 | .attr("font-size", "12px") 113 | .attr("font-weight", "bold") 114 | 115 | legendContainer 116 | .selectAll(".horizontal-cell text") 117 | .style("text-anchor", "middle") 118 | .attr("text-anchor", "middle") 119 | 120 | legendContainer 121 | .selectAll(".horizontal-cell:first-of-type text") 122 | .style("text-anchor", "start") 123 | .attr("text-anchor", "start") 124 | 125 | legendContainer 126 | .selectAll(".horizontal-cell:last-of-type text") 127 | .style("text-anchor", "end") 128 | .attr("text-anchor", "end") 129 | 130 | legendContainer 131 | .selectAll(".legendSize circle") 132 | .attr("fill", "none") 133 | .attr("stroke", "#ccc") 134 | 135 | legendContainer 136 | .selectAll(".legendSize rect") 137 | .attr("fill", "none") 138 | .attr("stroke", "#ccc") 139 | } 140 | 141 | legendFn.addColor = function (_title, _scale) { 142 | if (!arguments.length) return legendColor 143 | 144 | legendColor = { title: _title, scale: _scale } 145 | return legendFn 146 | } 147 | 148 | legendFn.addSize = function (_title, _scale, _shape) { 149 | if (!arguments.length) return legendSize 150 | legendSize = { 151 | title: _title, 152 | scale: _scale, 153 | shape: _shape, 154 | } 155 | return legendFn 156 | } 157 | 158 | legendFn.legendWidth = function (_legendWidth) { 159 | if (!arguments.length) return legendWidth 160 | legendWidth = _legendWidth 161 | return legendFn 162 | } 163 | 164 | return legendFn 165 | } 166 | -------------------------------------------------------------------------------- /src/rawGraphs.js: -------------------------------------------------------------------------------- 1 | import { 2 | validateMapperDefinition, 3 | validateMapping, 4 | annotateMapping, 5 | default as makeMapper, 6 | } from "./mapping" 7 | import { inferTypes } from "./dataset" 8 | import { RawGraphsError, mergeStyles } from "./utils" 9 | import { 10 | getOptionsValues, 11 | getOptionsConfig, 12 | getContainerOptions, 13 | } from "./options" 14 | import isObject from "lodash/isObject" 15 | import isFunction from "lodash/isFunction" 16 | import get from "lodash/get" 17 | import * as __defs from "./typeDefs" 18 | 19 | /** 20 | * @class 21 | * @description Internal class used to represent a visual model with its actual configuration of data, dataTypes, mapping, visualOptions and styles. 22 | */ 23 | class Chart { 24 | /** 25 | * @param {ChartImplementation} chartImplementation chart implementation 26 | * @param {Array.} data 27 | * @param {DataTypes} dataTypes 28 | * @param {Mapping} mapping 29 | * @param {VisualOptions} visualOptions 30 | * @param {Object} styles 31 | */ 32 | constructor( 33 | chartImplementation, 34 | data, 35 | dataTypes, 36 | mapping, 37 | visualOptions, 38 | styles 39 | ) { 40 | this._chartImplementation = chartImplementation 41 | this._data = data 42 | 43 | if ( 44 | data && 45 | (!dataTypes || 46 | (typeof dataTypes === "object" && Object.keys(dataTypes).length === 0)) 47 | ) { 48 | this._dataTypes = inferTypes(data) 49 | } else { 50 | this._dataTypes = dataTypes 51 | } 52 | 53 | this._mapping = mapping 54 | this._visualOptions = visualOptions 55 | this._styles = styles 56 | } 57 | 58 | /** 59 | * @param {Array.} nextData 60 | * @returns {Chart} 61 | * @description Sets or updates new data and returns a new Chart instance. 62 | */ 63 | data(nextData) { 64 | if (!arguments.length) { 65 | return this._data 66 | } 67 | 68 | let dataTypes 69 | if ( 70 | !this._dataTypes || 71 | (typeof this._dataType === "object" && 72 | Object.keys(this._dataTypes).length) 73 | ) { 74 | dataTypes = inferTypes(nextData) 75 | } else { 76 | dataTypes = this.dataTypes 77 | } 78 | 79 | return new Chart( 80 | this._chartImplementation, 81 | nextData, 82 | dataTypes, 83 | this._mapping, 84 | this._visualOptions, 85 | this._styles 86 | ) 87 | } 88 | 89 | /** 90 | * @param {DataTypes} nextDataTypes 91 | * @returns {Chart} 92 | * @description Sets or updates dataTypes and returns a new Chart instance. 93 | */ 94 | dataTypes(nextDataTypes) { 95 | if (!arguments.length) { 96 | return this._dataTypes 97 | } 98 | return new RAWChart( 99 | this._chartImplementation, 100 | this._data, 101 | nextDataTypes, 102 | this._mapping, 103 | this._visualOptions, 104 | this._styles 105 | ) 106 | } 107 | 108 | /** 109 | * @param {Mapping} nextMapping 110 | * @returns {Chart} 111 | * @description Sets or updates mapping and returns a new Chart instance. 112 | */ 113 | mapping(nextMapping) { 114 | if (!arguments.length) { 115 | return this._mapping 116 | } 117 | return new RAWChart( 118 | this._chartImplementation, 119 | this._data, 120 | this._dataTypes, 121 | nextMapping, 122 | this._visualOptions, 123 | this._styles 124 | ) 125 | } 126 | 127 | /** 128 | * @param {VisualOptions} nextVisualOptions 129 | * @returns {Chart} 130 | * @description Sets or updates visual options and returns a new Chart instance. 131 | */ 132 | visualOptions(nextVisualOptions) { 133 | if (!arguments.length) { 134 | return this._visualOptions 135 | } 136 | return new RAWChart( 137 | this._chartImplementation, 138 | this._data, 139 | this._dataTypes, 140 | this._mapping, 141 | nextVisualOptions, 142 | this._styles 143 | ) 144 | } 145 | 146 | /** 147 | * @param {styles} Object 148 | * @returns {Chart} 149 | * @description Sets or updates styles and returns a new Chart instance. 150 | */ 151 | styles(_styles) { 152 | if (!arguments.length) { 153 | return this._styles 154 | } 155 | return new RAWChart( 156 | this._chartImplementation, 157 | this._data, 158 | this._dataTypes, 159 | this._mapping, 160 | _visualOptions, 161 | _styles 162 | ) 163 | } 164 | 165 | /** 166 | * @param {document} document 167 | * @param {containerType} string 168 | * @param {dataReady} (array|object) 169 | * @returns {Node} 170 | * @private 171 | * @description Creates the container node that will be passed to the actual chart implementation. In the current implementation, an svg node is always created. 172 | */ 173 | getContainer(document, containerType, dataReady) { 174 | let container 175 | switch (containerType.toLowerCase()) { 176 | case "canvas": 177 | container = document.createElement("canvas") 178 | break 179 | 180 | case "div": 181 | container = document.createElement("div") 182 | break 183 | 184 | case "svg": 185 | container = document.createElementNS( 186 | "http://www.w3.org/2000/svg", 187 | "svg" 188 | ) 189 | break 190 | 191 | default: 192 | throw new RawGraphsError( 193 | `Container of type ${containerType} is not supported.` 194 | ) 195 | } 196 | 197 | const { optionsConfig, optionsValues } = this._getOptions(dataReady) 198 | 199 | const { width, height, style } = getContainerOptions( 200 | optionsConfig, 201 | optionsValues 202 | ) 203 | 204 | if (width) { 205 | container.setAttribute("width", width) 206 | } 207 | if (height) { 208 | container.setAttribute("height", height) 209 | } 210 | 211 | if (style) { 212 | Object.keys(style).forEach((k) => { 213 | container.style[k] = style[k] 214 | }) 215 | } 216 | 217 | return container 218 | } 219 | 220 | mapData() { 221 | let dimensions = this._chartImplementation.dimensions 222 | 223 | validateMapperDefinition(dimensions) 224 | validateMapping(dimensions, this._mapping, this._dataTypes) 225 | 226 | if (isFunction(this._chartImplementation.mapData)) { 227 | const annotatedMapping = annotateMapping( 228 | dimensions, 229 | this._mapping, 230 | this._dataTypes 231 | ) 232 | return this._chartImplementation.mapData( 233 | this._data, 234 | annotatedMapping, 235 | this._dataTypes, 236 | dimensions 237 | ) 238 | } else if (isObject(this._chartImplementation.mapData)) { 239 | const dimensionsWithOperations = dimensions.map((dim) => { 240 | return { 241 | ...dim, 242 | operation: this._chartImplementation.mapData[dim.id], 243 | } 244 | }) 245 | const mapFunction = makeMapper( 246 | dimensionsWithOperations, 247 | this._mapping, 248 | this._dataTypes 249 | ) 250 | return mapFunction(this._data) 251 | } else { 252 | throw new RawGraphsError( 253 | "mapData property of chartImplementation should be a function or an object" 254 | ) 255 | } 256 | } 257 | 258 | _getOptions(dataReady) { 259 | const optionsConfig = getOptionsConfig( 260 | this._chartImplementation.visualOptions 261 | ) 262 | const vizData = dataReady || this._getVizData() 263 | const optionsValues = getOptionsValues( 264 | optionsConfig, 265 | this._visualOptions, 266 | this._mapping, 267 | this._dataTypes, 268 | this._data, 269 | vizData, 270 | this._chartImplementation 271 | ) 272 | return { optionsConfig, optionsValues } 273 | } 274 | 275 | _getVizData() { 276 | return this._chartImplementation.skipMapping ? this._data : this.mapData() 277 | } 278 | 279 | _getVizStyles() { 280 | const styles = this._chartImplementation.styles || {} 281 | const localStyles = this._styles || {} 282 | let mergedStyles = mergeStyles(styles, localStyles) 283 | return mergedStyles 284 | } 285 | 286 | /** 287 | * @param {Node} node 288 | * @param {dataReady} (array|object) mapped data if available 289 | * @returns {DOMChart} 290 | */ 291 | renderToDOM(node, dataReady) { 292 | if (!this._chartImplementation) { 293 | throw new RawGraphsError("cannot render: chartImplementation is not set") 294 | } 295 | 296 | const containerType = get(this._chartImplementation, "type", "svg") 297 | const container = this.getContainer( 298 | node.ownerDocument, 299 | containerType, 300 | dataReady 301 | ) 302 | const vizData = dataReady || this._getVizData() 303 | const dimensions = this._chartImplementation.dimensions 304 | const annotatedMapping = annotateMapping( 305 | dimensions, 306 | this._mapping, 307 | this._dataTypes 308 | ) 309 | const styles = this._getVizStyles() 310 | 311 | const { optionsConfig, optionsValues } = this._getOptions(vizData) 312 | 313 | node.innerHTML = "" 314 | node.appendChild(container) 315 | 316 | this._chartImplementation.render( 317 | container, 318 | vizData, 319 | optionsValues, 320 | annotatedMapping, 321 | this._data, 322 | styles 323 | ) 324 | 325 | return new DOMChart( 326 | node, 327 | this._chartImplementation, 328 | this._data, 329 | this._dataTypes, 330 | this._mapping, 331 | this._visualOptions, 332 | this._styles 333 | ) 334 | } 335 | 336 | /** 337 | * @param {document} document HTML document context (optional if window is available) 338 | * @param {dataReady} (array|object) mapped data if available 339 | * @returns {string} 340 | */ 341 | renderToString(document, dataReady) { 342 | if (!this._chartImplementation) { 343 | throw new RawGraphsError("cannot render: chartImplementation is not set") 344 | } 345 | 346 | if (!document && window === undefined) { 347 | throw new RawGraphsError("Document must be passed or window available") 348 | } 349 | const containerType = get(this._chartImplementation, "type", "svg") 350 | const container = this.getContainer( 351 | document || window.document, 352 | containerType, 353 | dataReady 354 | ) 355 | const vizData = dataReady || this._getVizData() 356 | const dimensions = this._chartImplementation.dimensions 357 | const annotatedMapping = annotateMapping( 358 | dimensions, 359 | this._mapping, 360 | this._dataTypes 361 | ) 362 | const styles = this._getVizStyles() 363 | 364 | const { optionsConfig, optionsValues } = this._getOptions(vizData) 365 | this._chartImplementation.render( 366 | container, 367 | vizData, 368 | optionsValues, 369 | annotatedMapping, 370 | this._data, 371 | styles 372 | ) 373 | return container.outerHTML 374 | } 375 | } 376 | 377 | /** 378 | * @class 379 | * @description Internal class used to represent a Chart instance rendered to a DOM node. 380 | * The class has no extra methods for now, but il could be used to provide an "update" functionality in the future. 381 | */ 382 | class DOMChart extends Chart { 383 | constructor(node, ...args) { 384 | super(...args) 385 | this._node = node 386 | } 387 | } 388 | 389 | /** 390 | * raw factory function 391 | * @description This is the entry point for creating a chart with raw. It will return an instance of the RAWChart class 392 | * @param {ChartImplementation} chartImplementation 393 | * @param {RawConfig} [config] Config object. 394 | * @returns {Chart} 395 | */ 396 | function chart(chartImplementation, config = {}) { 397 | const { 398 | data, 399 | dataTypes = {}, 400 | mapping, 401 | visualOptions = {}, 402 | styles = {}, 403 | } = config 404 | return new Chart( 405 | chartImplementation, 406 | data, 407 | dataTypes, 408 | mapping, 409 | visualOptions, 410 | styles 411 | ) 412 | } 413 | 414 | export default chart 415 | -------------------------------------------------------------------------------- /src/testSupport/chart.js: -------------------------------------------------------------------------------- 1 | import { select } from "d3-selection" 2 | import { scaleLinear } from "d3-scale" 3 | import { arrayGetter } from "../../src" 4 | import { extent } from "d3-array" 5 | import { axisBottom, axisLeft } from "d3-axis" 6 | 7 | const testChart = { 8 | dimensions: [ 9 | { 10 | id: "x", 11 | name: "x", 12 | operation: "get", 13 | validTypes: ["number"], 14 | required: true, 15 | }, 16 | { 17 | id: "y", 18 | name: "y", 19 | operation: "get", 20 | validTypes: ["number"], 21 | required: true, 22 | }, 23 | ], 24 | 25 | mapData: function (data, mapping, dataTypes, dimensions) { 26 | const getX = arrayGetter(mapping["x"].value) 27 | const getY = arrayGetter(mapping["y"].value) 28 | 29 | const out = data.map((d) => ({ 30 | x: getX(d), 31 | y: getY(d), 32 | })) 33 | return out 34 | }, 35 | 36 | //declarative version ... with automatic mapping 37 | mapData_: { 38 | x: "get", 39 | y: "get", 40 | }, 41 | 42 | options: { 43 | setOriginAt0: { 44 | type: "boolean", 45 | default: true, 46 | group: "chart", 47 | label: "Set origin at 0,0", 48 | }, 49 | }, 50 | 51 | render: function (node, data, visualOptions, mapping, originalData) { 52 | const { width, height } = visualOptions 53 | const margin = { 54 | top: 25, 55 | right: 20, 56 | bottom: 35, 57 | left: 40, 58 | } 59 | 60 | const x = scaleLinear() 61 | .domain(extent(data, (d) => d.x)) 62 | .nice() 63 | .range([margin.left, width - margin.right]) 64 | 65 | const y = scaleLinear() 66 | .domain(extent(data, (d) => d.y)) 67 | .nice() 68 | .range([height - margin.bottom, margin.top]) 69 | 70 | const xAxis = (g) => 71 | g 72 | .attr("transform", `translate(0,${height - margin.bottom})`) 73 | .call(axisBottom(x).ticks(width / 80)) 74 | .call((g) => g.select(".domain").remove()) 75 | .call((g) => 76 | g 77 | .append("text") 78 | .attr("x", width) 79 | .attr("y", margin.bottom - 4) 80 | .attr("fill", "red") 81 | .attr("text-anchor", "end") 82 | .text(data.x) 83 | ) 84 | 85 | const yAxis = (g) => 86 | g 87 | .attr("transform", `translate(${margin.left},0)`) 88 | .call(axisLeft(y)) 89 | .call((g) => g.select(".domain").remove()) 90 | .call((g) => 91 | g 92 | .append("text") 93 | .attr("x", -margin.left) 94 | .attr("y", 10) 95 | .attr("fill", "red") 96 | .attr("text-anchor", "start") 97 | .text(data.y) 98 | ) 99 | 100 | const svg = select(node) 101 | 102 | svg.append("g").call(xAxis) 103 | 104 | svg.append("g").call(yAxis) 105 | 106 | // svg.append("g") 107 | // .call(grid); 108 | 109 | svg 110 | .append("g") 111 | .attr("stroke", "steelblue") 112 | .attr("stroke-width", 1.5) 113 | .attr("fill", "none") 114 | .selectAll("circle") 115 | .data(data) 116 | .join("circle") 117 | .attr("cx", (d) => x(d.x)) 118 | .attr("cy", (d) => y(d.y)) 119 | .attr("r", 3) 120 | 121 | svg 122 | .append("g") 123 | .attr("font-family", "sans-serif") 124 | .attr("font-size", 10) 125 | .selectAll("text") 126 | .data(data) 127 | .join("text") 128 | .attr("dy", "0.35em") 129 | .attr("x", (d) => x(d.x) + 7) 130 | .attr("y", (d) => y(d.y)) 131 | .text((d) => d.y) 132 | }, 133 | } 134 | 135 | export default testChart 136 | -------------------------------------------------------------------------------- /src/typeDefs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef DataTypeObject 3 | * @global 4 | * @type {object|string} 5 | * @property {'number'|'string'|'date'} type 6 | * @property {string} [dateFormat] date format for dates 7 | * @example 8 | * { type: 'date', dateFormat: 'DD-MM-YYYY } 9 | */ 10 | 11 | /** 12 | * @typedef DataTypes 13 | * @global 14 | * @type {Object.<'number'|'string'|'date'|DataTypeObject>} 15 | * @example 16 | * { x: 'number', y: { type: 'date', dateFormat: 'DD-MM-YYYY } } 17 | */ 18 | 19 | /** 20 | * @typedef AggregationdDefaultObject 21 | * @global 22 | * @type {object} 23 | * @property {string} [date] default aggregation function for dates 24 | * @property {string} [number] default aggregation function for numbers 25 | * @property {string} [string] default aggregation function for strings 26 | * @example 27 | * { 28 | number: 'sum', 29 | string: 'csvDistinct', 30 | date: 'csvDistinct', 31 | } 32 | */ 33 | 34 | /** 35 | * @typedef Dimension 36 | * @global 37 | * @type {object} 38 | * @property {string} id unique id 39 | * @property {string} name label 40 | * @property {boolean} required 41 | * @property {Boolean} [multiple=false] controls if a dimension accept a value with more than one item 42 | * @property {number} [minValues=undefined] min number of items required for the value of the dimension 43 | * @property {number} [maxValues=undefined] max number of items required for the value of the dimension 44 | * @property {Array} validTypes valid data types for the dimension (one or more of 'number', 'string', 'date', 'boolean') 45 | * @property {Boolean} [aggregation] true if a dimension will be aggregated 46 | * @property {string|AggregationdDefaultObject} [aggregationDefault] default for aggregation 47 | * @example 48 | * { 49 | id: 'size', 50 | name: 'Size', 51 | validTypes: ['number'], 52 | required: false, 53 | aggregation: true, 54 | aggregationDefault: 'sum', 55 | } 56 | */ 57 | 58 | /** 59 | * @typedef DimensionsDefinition 60 | * @description An array of dimensions, used to describe dimensions of a chart 61 | * @global 62 | * @type {Array.} 63 | * @example 64 | * [ 65 | { 66 | id: 'steps', 67 | name: 'Steps', 68 | validTypes: ['number', 'date', 'string'], 69 | required: true, 70 | multiple: true, 71 | minValues: 2, 72 | }, 73 | { 74 | id: 'size', 75 | name: 'Size', 76 | validTypes: ['number'], 77 | required: false, 78 | aggregation: true, 79 | aggregationDefault: 'sum', 80 | }, 81 | ] 82 | */ 83 | 84 | /** 85 | * @typedef MappedConfigValue 86 | * @global 87 | * @type {object} 88 | * @property {string|Array.} aggregation aggregation(s) function name(s) 89 | */ 90 | 91 | /** 92 | * @typedef MappedDimension 93 | * @global 94 | * @type {object} 95 | * @property {string|Array.} value the mapping value 96 | * @property {MappedConfigValue} [config] the optional config 97 | */ 98 | 99 | /** 100 | * @typedef Mapping 101 | * @global 102 | * @type {Object.} 103 | */ 104 | 105 | /** 106 | * @typedef VisualOptionDefinition 107 | * @global 108 | * @type {object} 109 | * @property {'number'|'boolean'|'text'|'colorScale'} type type of option 110 | * @property {string} label the option label 111 | * @property {any} default the default value for the option. should match the option type 112 | * @property {string} [group] the name of the options panel 113 | * @property {object} [disabled] cross-conditions disabling the option 114 | * @property {Array.} [requiredDimensions] dimensions that must have a value in mapping for enabling the option 115 | * @property {string} [container] container node property reference 116 | * @property {object} [containerCondition] conditions for applying container node property reference 117 | * 118 | * @example 119 | * { 120 | type: 'number', 121 | label: 'Maximum radius', 122 | default: 20, 123 | group: 'chart', 124 | } 125 | * @example 126 | { 127 | type: 'boolean', 128 | label: 'Show legend', 129 | default: false, 130 | group: 'artboard', 131 | } 132 | 133 | */ 134 | 135 | /** 136 | * @typedef VisualOptionsDefinition 137 | * @global 138 | * @type {Object.} 139 | * @example 140 | * { 141 | maxRadius: { 142 | type: 'number', 143 | label: 'Maximum radius', 144 | default: 20, 145 | group: 'chart', 146 | }, 147 | 148 | showLegend: { 149 | type: 'boolean', 150 | label: 'Show legend', 151 | default: false, 152 | group: 'artboard', 153 | }, 154 | 155 | legendWidth: { 156 | type: 'number', 157 | label: 'Legend width', 158 | default: 200, 159 | group: 'artboard', 160 | disabled: { 161 | showLegend: false, 162 | }, 163 | container: 'width', 164 | containerCondition: { 165 | showLegend: true, 166 | }, 167 | }, 168 | 169 | layout: { 170 | type: 'text', 171 | label: 'Layout algorythm', 172 | group: 'chart', 173 | options: ['Cluster Dendogram', 'Tree'], 174 | default: 'Tree', 175 | }, 176 | 177 | colorScale: { 178 | type: 'colorScale', 179 | label: 'Color scale', 180 | dimension: 'color', 181 | default: { 182 | scaleType: 'ordinal', 183 | interpolator: 'interpolateSpectral', 184 | }, 185 | group: 'color', 186 | } 187 | 188 | } 189 | */ 190 | 191 | /** 192 | * @typedef VisualOptions 193 | * @global 194 | * @type {Object} 195 | * @example 196 | * { with: 100, showLegend: true } 197 | */ 198 | 199 | /** 200 | * @typedef MappingFunction 201 | * @global 202 | * @type {function} 203 | * @param {array} dataset the input dataset 204 | * @param {Mapping} mapping the mapping object 205 | * @param {DataTypes} dataTypes 206 | * @param {DimensionsDefinition} dimensions the chart dimensions 207 | */ 208 | 209 | /** 210 | * @typedef RenderFunction 211 | * @global 212 | * @type {function} 213 | * @param {Node} node an empty DOMNode that conforms to the `type` exposed by the chart implementation. 214 | * @param {any} data the data output from the mapData function defined in the cart 215 | * @param {object} visualOptions the current values of the chart visual options 216 | * @param {object} mapping the mapping from column names to chart dimensions 217 | * @param {array} originalData the original tabular dataset 218 | * @param {styles} Object css in js styles definitions, defined by the chart itself and possibly overridden when the chart instance is created. 219 | */ 220 | 221 | /** 222 | * @typedef ChartMetadata 223 | * @global 224 | * @type {object} 225 | * @property {string} id An unique id for the chart 226 | * @property {string} name The chart name 227 | * @property {string} description The chart description 228 | * @property {Array.} categories The list of chart categories 229 | * @property {string} icon url or base64 representation of chart icon (will be used as `src` attribute of an `` tag) 230 | * @property {string} thumbnail url or base64 representation of chart thumbnail (will be used as `src` attribute of an `` tag) 231 | * @example 232 | * { 233 | name: 'Bumpchart', 234 | id: 'rawgraphs.bumpchart', 235 | thumbnail: 'data:image/svg+xml;base64...', 236 | icon: 'data:image/svg+xml;base64...', 237 | categories: ['correlations', 'proportions'], 238 | description: 239 | 'It allows the comparison on multiple categories over a continuous dimension and the evolution of its sorting. By default, sorting is based on the stream size.', 240 | } 241 | */ 242 | 243 | /** 244 | * @typedef ChartImplementation 245 | * @global 246 | * @type {object} 247 | * @property {'svg'|'canvas'|div} [type='svg'] the chart type (defaults to svg) 248 | * @property {ChartMetadata} metadata the chart metadata 249 | * @property {RenderFunction} render the render function 250 | * @property {Boolean} [skipMapping=false] if set to true will skip the mapping phase (current mapping is still passed to the render function) 251 | * @property {MappingFunction} mapData the mapping function 252 | * @property {DimensionsDefinition} dimensions the dimensions configuration (mapping definition) 253 | * @property {VisualOptionsDefinition} visualOptions the visual options exposed by the model 254 | * @property {Object} [styles={}] - css in js styles definitions 255 | */ 256 | 257 | /** 258 | * @typedef RawConfig 259 | * @global 260 | * @type {object} 261 | * @property {Array.} data - the tabular data to be represented 262 | * @property {DataTypes} [dataTypes] - object with data types annotations (column name => type name). if not passed will be inferred with the `inferTypes` function 263 | * @property {Mapping} mapping - the current mapping of column names to dimensions of the current visual model 264 | * @property {VisualOptions} [visualOptions={}] - visual options values 265 | * @property {Object} [styles={}] - css in js styles definitions 266 | */ 267 | 268 | 269 | /** 270 | * @typedef ParsingOptions 271 | * @global 272 | * @type {object} 273 | * @property {boolean} strict if strict is false, a JSON parsing of the values is tried. (if strict=false: "true" -> true) 274 | * @property {string} locale 275 | * @property {string} decimal 276 | * @property {string} group 277 | * @property {Array.} numerals 278 | * @property {string} dateLocale 279 | */ 280 | 281 | /** 282 | * @typedef ParserResult 283 | * @global 284 | * @type {object} 285 | * @property {Array} dataset parsed dataset (list of objects) 286 | * @property {Object} dataTypes dataTypes used for parsing dataset 287 | * @property {Array} errors list of errors from parsing 288 | */ 289 | 290 | // 291 | // * @property {'get'| 'group'|'groups'|'rollup'|'rollup-leaf'|'rollups'|'groupAggregate'|'groupBy'|'proxy'} operation the operation type (used for declarative mapping) 292 | // * @property {Object} targets only for proxy operations 293 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import isPlainObject from "lodash/isPlainObject" 2 | import isString from "lodash/isString" 3 | import isNumber from "lodash/isNumber" 4 | import get from "lodash/get" 5 | import { formatLocale } from "d3-format" 6 | 7 | export class RawGraphsError extends Error { 8 | constructor(message) { 9 | super(message) 10 | this.name = "RawGraphsError" 11 | this.message = message 12 | } 13 | } 14 | 15 | export class ValidationError extends Error { 16 | constructor(errors) { 17 | super("validation error") 18 | this.name = "ValidationError" 19 | 20 | this.message = Object.values(errors).join("\n") 21 | this.errors = errors 22 | } 23 | } 24 | 25 | export function getType(dataType) { 26 | if (isPlainObject(dataType)) { 27 | return getType(dataType.type) 28 | } 29 | 30 | if (isString(dataType)) { 31 | switch (dataType.toLowerCase()) { 32 | case "string": 33 | return String 34 | case "number": 35 | return Number 36 | case "boolean": 37 | return Boolean 38 | case "date": 39 | return Date 40 | 41 | default: 42 | return String 43 | } 44 | } 45 | 46 | return dataType 47 | } 48 | 49 | export function getTypeName(dataType) { 50 | const type = getType(dataType) 51 | return type && type.name ? type.name.toLowerCase() : undefined 52 | } 53 | 54 | // taken from: https://observablehq.com/@mbostock/localized-number-parsing 55 | export class LocaleNumberParser { 56 | constructor(locale) { 57 | const parts = new Intl.NumberFormat(locale).formatToParts(12345.6) 58 | const numerals = [ 59 | ...new Intl.NumberFormat(locale, { useGrouping: false }).format( 60 | 9876543210 61 | ), 62 | ].reverse() 63 | const index = new Map(numerals.map((d, i) => [d, i])) 64 | this._group = 65 | group || 66 | new RegExp(`[${parts.find((d) => d.type === "group").value}]`, "g") 67 | this._decimal = new RegExp( 68 | `[${parts.find((d) => d.type === "decimal").value}]` 69 | ) 70 | this._numeral = new RegExp(`[${numerals.join("")}]`, "g") 71 | this._index = (d) => index.get(d) 72 | } 73 | parse(string) { 74 | return (string = string 75 | .trim() 76 | .replace(this._group, "") 77 | .replace(this._decimal, ".") 78 | .replace(this._numeral, this._index)) 79 | ? +string 80 | : NaN 81 | } 82 | } 83 | 84 | export class NumberParser { 85 | constructor({ locale, decimal, group, numerals }, useLocale = false) { 86 | const parts = new Intl.NumberFormat(locale).formatToParts(12345.6) 87 | const defaultGroup = "" 88 | const defaultDecimal = "." 89 | 90 | this.numerals = 91 | numerals || 92 | Array.from( 93 | new Intl.NumberFormat(locale, { useGrouping: false }).format(9876543210) 94 | ).reverse() 95 | this.group = 96 | group || 97 | (useLocale ? parts.find((d) => d.type === "group").value : defaultGroup) 98 | this.decimal = 99 | decimal || 100 | (useLocale 101 | ? parts.find((d) => d.type === "decimal").value 102 | : defaultDecimal) 103 | 104 | const index = new Map(this.numerals.map((d, i) => [d, i])) 105 | 106 | //#todo: infer from locale in NumberParser 107 | const groupingChars = 3 108 | 109 | this._groupRegexp = new RegExp( 110 | `[${this.group}}](\\d{${groupingChars}})`, 111 | "g" 112 | ) 113 | this._decimalRegexp = new RegExp(`[${this.decimal}]`) 114 | this._numeralRegexp = new RegExp(`[${this.numerals.join("")}]`, "g") 115 | this._index = (d) => index.get(d) 116 | 117 | this.formatter = formatLocale({ 118 | decimal: this.decimal, 119 | grouping: [groupingChars], 120 | thousands: this.group, 121 | numerals: this.numerals, 122 | }).format(",") 123 | } 124 | 125 | parse(string) { 126 | if (isNumber(string)) { 127 | return string 128 | } 129 | 130 | const trimmedString = string.trim ? string.trim() : string.toString().trim() 131 | const parsed = trimmedString 132 | .replace(this._groupRegexp, function (match, captured) { 133 | return captured 134 | }) 135 | .replace(this._decimalRegexp, ".") 136 | .replace(this._numeralRegexp, this._index) 137 | 138 | let out = parsed ? +parsed : NaN 139 | return out 140 | } 141 | 142 | format(n) { 143 | return this.formatter(n) 144 | } 145 | } 146 | 147 | // https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6 148 | export function mergeStyles(target, source) { 149 | // Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties 150 | for (const key of Object.keys(source)) { 151 | if (source[key] instanceof Object) 152 | Object.assign(source[key], mergeStyles(target[key], source[key])) 153 | } 154 | 155 | // Join `target` and modified `source` 156 | Object.assign(target || {}, source) 157 | return target 158 | } 159 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const files = fs.readdirSync('./sandbox/web/') 5 | 6 | 7 | const targetFiles = files.filter(function(file) { 8 | return path.extname(file).toLowerCase() === '.js'; 9 | }); 10 | 11 | const entry = {} 12 | targetFiles.forEach(filename => { 13 | const name = filename.substr(0, filename.lastIndexOf(".")) 14 | entry[name] = `./sandbox/web/${filename}` 15 | }) 16 | 17 | 18 | 19 | 20 | module.exports = { 21 | entry: entry, 22 | mode: 'development', 23 | output: { 24 | filename: '[name].bundle.js', 25 | publicPath: '/', 26 | }, 27 | devServer: { 28 | contentBase: './sandbox/web', 29 | historyApiFallback: true, 30 | port: 9000, 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.js$/, 36 | exclude: /(node_modules)/, 37 | use: { 38 | loader: 'babel-loader', 39 | options: { 40 | presets: ['@babel/preset-env'], 41 | plugins: [ 42 | // '@babel/plugin-syntax-dynamic-import', 43 | ], 44 | }, 45 | }, 46 | }, 47 | { 48 | test: /\.css$/, 49 | use: ['style-loader', 'css-loader'], 50 | }, 51 | ], 52 | }, 53 | resolve: { 54 | alias: { 55 | 'raw': path.resolve(__dirname, 'src'), 56 | }, 57 | }, 58 | } 59 | 60 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator. 4 | 5 | ## Installation 6 | 7 | ```console 8 | yarn install 9 | ``` 10 | 11 | ## Local Development 12 | 13 | ```console 14 | yarn start 15 | ``` 16 | 17 | This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ## Build 20 | 21 | ```console 22 | yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ## Deployment 28 | 29 | ```console 30 | GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/blog/2019-05-28-hola.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hola 3 | title: Hola 4 | author: Gao Wei 5 | author_title: Docusaurus Core Team 6 | author_url: https://github.com/wgao19 7 | author_image_url: https://avatars1.githubusercontent.com/u/2055384?v=4 8 | tags: [hola, docusaurus] 9 | --- 10 | 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 12 | -------------------------------------------------------------------------------- /website/blog/2019-05-29-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hello-world 3 | title: Hello 4 | author: Endilie Yacop Sucipto 5 | author_title: Maintainer of Docusaurus 6 | author_url: https://github.com/endiliey 7 | author_image_url: https://avatars1.githubusercontent.com/u/17883920?s=460&v=4 8 | tags: [hello, docusaurus] 9 | --- 10 | 11 | Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://v2.docusaurus.io/). 12 | 13 | 14 | 15 | This is a test post. 16 | 17 | A whole bunch of other information. 18 | -------------------------------------------------------------------------------- /website/blog/2019-05-30-welcome.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | author: Yangshun Tay 5 | author_title: Front End Engineer @ Facebook 6 | author_url: https://github.com/yangshun 7 | author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4 8 | tags: [facebook, hello, docusaurus] 9 | --- 10 | 11 | Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well! 12 | 13 | Delete the whole directory if you don't want the blog features. As simple as that! 14 | -------------------------------------------------------------------------------- /website/docs/chart-utilities.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: chart-utilities 3 | title: Chart utilities 4 | sidebar_label: Chart utilities 5 | slug: /chart-utilities 6 | --- 7 | 8 | RAWGraphs core provides a set of utility functions that can be used in the `render` function of a chart implementation. 9 | 10 | ## Legends 11 | 12 | This library includes utilities to generate legends like the ones seen in charts of the RAWGraphs app. 13 | 14 | These utilities may be used in svg charts rendered using the **d3** library, as are based on d3 scales to automatically 15 | draw legends for color and size scales. 16 | 17 | Here's an example of the legend displayed in a **bubblechart** from the RAWGraphs app: 18 | 19 | ![Legend in bubble chart](../static/img/bubble-legend.svg) 20 | 21 | [Here](https://github.com/rawgraphs/rawgraphs-charts/blob/master/src/bubblechart/render.js#L224) you can see an example of using these functions in the bubble chart 22 | from the [rawgraphs-charts repository](https://github.com/rawgraphs/rawgraphs-charts). 23 | 24 | ## Labels occlusion 25 | 26 | One recurring problem when implementing visualisations is the collision of labels. 27 | 28 | If the chart rendering is based on d3 and your using the `data` method of a `selection` to generate the viz, 29 | the **`labelsOcclusion`** utility helps you solve this problem. 30 | 31 | The function takes a d3 selection and the function for getting the priority for each datum. 32 | Here's an usage example: 33 | 34 | ```js 35 | 36 | import { labelsOcclusion } from 'rawgraphs-core' 37 | import { d3 } from 'd3' 38 | 39 | 40 | const svg = d3.select('#somediv').append("svg") 41 | const labelsLayer = svg.append("g").attr("id", "labels"); 42 | 43 | const data = [ 44 | { x: 1, y: 1, label: "hello", size: 10 }, 45 | { x: 2, y: 1, label: "from", size: 30 }, 46 | { x: 3, y: 2, label: "rawgraphs", size: 10 }, 47 | ... 48 | ] 49 | 50 | labelsLayer 51 | .selectAll("g") 52 | .data(data) 53 | .join("g") 54 | .attr("transform", (d) => `translate(${d.x * 10},${d.y * 10})`) 55 | .append("text") 56 | .text((d) => d.label) 57 | 58 | labelsOcclusion(labelsLayer.selectAll('text'), (d) => d.size) 59 | 60 | ``` 61 | 62 | You can see the utility in action in the **beeswarm** chart in the RAWGraphs app, when setting to `true` the **Auto hide labels** visual option: 63 | 64 | ![Labels occlusion in beeswarm](../static/img/beeswarm-occlusion.svg) 65 | 66 | [Here](https://github.com/rawgraphs/rawgraphs-charts/blob/master/src/beeswarm/render.js#L271) you can see an example of using the function in the beeswarm 67 | from the [rawgraphs-charts repository](https://github.com/rawgraphs/rawgraphs-charts). 68 | -------------------------------------------------------------------------------- /website/docs/core.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: core 3 | title: rawgraphs-core 4 | sidebar_label: rawgraphs-core 5 | slug: / 6 | --- 7 | 8 | Welcome to the rawgraphs-core documentation! 9 | 10 | This library was born to simplify and modularize development of the [RawGraphs web app](https://app.rawgraphs.io), but it can be used to implement the RAWGraphs workflow and rendering charts from javascript code. 11 | 12 | The library roughly contains: 13 | 14 | - functions for rendering a visual model and a dataset to the DOM, just like the RawGraphs web app works 15 | - helper functions used for the various subtasks of the workflow like data parsing, aggregation, visual options encoding/decoding, etc. 16 | - utility functions that can be used in visual models implementations (ex: legends) 17 | 18 | 19 | Please refer to the [workflow](workflow.md) description for more details about the relations between this library and the actual visual models, and to [chart interface](chart-interface.md) for more info about how to create your custom charts that can be used with rawgraphs-core and with the RAWGraphs app. 20 | 21 | -------------------------------------------------------------------------------- /website/docs/data-parsing.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: data-parsing 3 | title: Data parsing 4 | sidebar_label: Data parsing 5 | slug: /data-parsing 6 | --- 7 | 8 | User data is the entry point of the RAWGraphs workflow, and the rawgraphs-core library introduces some concepts and utilities 9 | to deal with user provided datasets. 10 | 11 | RAWGraphs works on a tabular dataset, that will be transformed internally according to the chart type we want to draw. 12 | 13 | The task of importing such kind of dataset from common formats like csv into javascript is already solved by other libraries 14 | (we use [d3-dsv](https://github.com/d3/d3-dsv) in the RAWGraphs app), but as we normally work with text-based data formats, 15 | we also must define the data types of the different columns. 16 | 17 | For example in this csv dataset: 18 | 19 | ```csv 20 | year, orders, total, client 21 | 2000, 100, 1000, A and Co. 22 | 2001, 10, 2000, M and Co. 23 | 2002, 30, 300, C and Co. 24 | ``` 25 | 26 | all data points and values are formally strings, but the column `year` could be paresed as a date, and the columns `orders` and `total` 27 | are numbers. 28 | 29 | ## Data Types in RAWGraphs 30 | 31 | RAWGraphs has the concept of **data type** and can handle **strings**, **number** and **dates**. 32 | 33 | When handling a user dataset, it is required that each data column in the tabular dataset has the same data type for all 34 | the data points. 35 | 36 | When we create an instance of a rawgraphs chart you have the ability to declare the datatypes of the columns in the dataset, 37 | otherwise raw will infer them from the dataset. 38 | Given a set of column, its datatypes are expressed as an object where keys are the names of columns and values a- one of "number", "date", or "string". 39 | 40 | For example: 41 | 42 | ```js 43 | const dataTypes = { 44 | age: "number", 45 | height: "number", 46 | born: "date", 47 | name: "string", 48 | } 49 | ``` 50 | 51 | Let's go back to the basic example of rendering a chart: 52 | 53 | ```js 54 | // 1. imports: chart factory function and chart implementation 55 | import { chart } from '@rawgraphs/rawgraphs-core' 56 | import { bubblechart } from '@rawgraphs/rawgraphs-charts' 57 | 58 | // 2. defining data 59 | const data = [{age:10, height:130}, {age:18, height:170}] 60 | 61 | // 3. defining mapping 62 | const mapping = { x: { value: 'age' }, y: { value: 'height' } } 63 | 64 | // 4. creating a chart instance 65 | const viz = new chart(bubblechart { data, mapping}) 66 | 67 | // 5. rendering to the DOM 68 | const targetNode = document.getElementById('some-div') 69 | viz.renderToDOM(targetNode) 70 | ``` 71 | 72 | As you can see there's no mentioning of datatypes, but under the hood rawgraphs has been able to 73 | identify the "age" and "height" columns in the dataset. In this case this is important as the bubblechart 74 | only accepts numbers or dates on the `x` dimension and numbers on the `y` dimensions. 75 | 76 | We could have been more verbose, by specifying the datatypes: 77 | 78 | ```js 79 | // 1. imports: chart factory function and chart implementation 80 | import { chart } from '@rawgraphs/rawgraphs-core' 81 | import { bubblechart } from '@rawgraphs/rawgraphs-charts' 82 | 83 | // 2. defining data and data types 84 | const data = [{age:10, height:130}, {age:18, height:170}] 85 | const dataTypes = { 86 | age: "number", 87 | height: "number", 88 | } 89 | 90 | // 3. defining mapping 91 | const mapping = { x: { value: 'age' }, y: { value: 'height' } } 92 | 93 | // 4. creating a chart instance 94 | const viz = new chart(bubblechart { data, dataTypes, mapping}) 95 | 96 | // 5. rendering to the DOM 97 | const targetNode = document.getElementById('some-div') 98 | viz.renderToDOM(targetNode) 99 | ``` 100 | 101 | In this way you can "force" the visualization to use your explicit data types. 102 | 103 | ## "Real-world" data and data types inference 104 | 105 | When dealing with real-world datasets, we often start from spreadsheets, text files, database exports, copy and paste, 106 | that come with no information about data types, and are often "formatted" with conventions based on language 107 | and culture of who produced the data. 108 | 109 | An obvious case is related to dates formats, which standard change from nation to nation, or that can be 110 | expressed with a mixture of words and numbers (ex: Jan 2021), specify date and time (ex: 2021-01-01 18:00:00), date only (ex: 2021-01-01) or just a part of it (ex: 2021). 111 | 112 | Another case is number formatting, where the dot `.` or the comma `,` sign are used as decimal separators in different languages. 113 | 114 | RAWGraphs includes some functions used in the RAWGraphs app to solve this problem for the, with the `inferTypes` and `parseDataset` functions. 115 | These utilities are used in the "Data loading" section of the app. 116 | 117 | `inferTypes` 118 | ----------- 119 | 120 | This function can be used to detect the data types of a dataset. The signature is the following 121 | 122 | ### inferTypes(data, parsingOptions) ⇒ [DataTypes](#DataTypes) 123 | 124 | - The `data` parameter is the array of objects that must be parsed 125 | - The `parsingOptions` is an optional objects with the following properties: 126 | 127 | | Name | Type | Description | 128 | | ---------- | --------------------------------- | -------------------------------------------------------------------------------------------- | 129 | | strict | boolean | if strict is false, a JSON parsing of the values is tried. (if strict=false: "true" -> true) | 130 | | locale | string | | 131 | | decimal | string | | 132 | | group | string | | 133 | | numerals | Array.<string> | | 134 | | dateLocale | string | | 135 | 136 | The return value of the function is an object representing the guessed **data types**. 137 | Its shape is described in the [api docs](api#datatypes--objectnumberstringdatedatatypeobject) 138 | 139 | and extends the datatypes definition of RAWGraphs by allowing to specify custom properties for the different data types. 140 | For example, the `date` format allows to specify a `dateFormat` property. 141 | 142 | **Example of datatypes** 143 | 144 | ```js 145 | { x: 'number', y: { type: 'date', dateFormat: 'DD-MM-YYYY } } 146 | ``` 147 | 148 | :::info 149 | The `dateFormat` property in each data type definition plays a role similar to the options available in the `parsingOptions` 150 | parameter, the only difference is that the date format may be specified for each column, while the numeric formatting is 151 | global. This reflects the actual user interface of the RAWGraphs app. 152 | ::: 153 | 154 | Let's see how the function is used: 155 | 156 | **inferTypes example** 157 | 158 | ```js 159 | import { inferTypes } from "@rawgraphs/rawgraphs-core" 160 | const data = [ 161 | { a: 1, b: "2021" }, 162 | { a: 2000, b: "2011" }, 163 | { a: 1, b: "2000" }, 164 | ] 165 | const detectedTypes = inferTypes(data) 166 | 167 | // the value of detectedTypes is 168 | // {a: "number", b: "number"} 169 | ``` 170 | 171 | As you can see, even if the `b` column of our dataset is formally a string, the library was able to cast it to numbers. 172 | 173 | :::info 174 | For each column, Rawgraphs tries to cast each datum to each data type. If the majority of data points can be casted to a type, 175 | that type is chosen for the column. 176 | ::: 177 | 178 | Let's see a couple more of examples. The function is able to detect ISO dates formats: 179 | 180 | ```js 181 | import { inferTypes } from "@rawgraphs/rawgraphs-core" 182 | const data = [ 183 | { a: 1, b: "2021-01-01" }, 184 | { a: 2000, b: "2011-01-01" }, 185 | { a: 1, b: "2000-02-02" }, 186 | ] 187 | const detectedTypes = inferTypes(data) 188 | 189 | // the value of detectedTypes is 190 | // {a: "number", b: {type: "date", dateFormat: "YYYY-MM-DD"}} 191 | ``` 192 | 193 | And we can use the `parsingOption` parameter for specifying that the comma is the decimal separator: 194 | 195 | ```js 196 | import { inferTypes } from "@rawgraphs/rawgraphs-core" 197 | const data = [ 198 | { a: 1, b: "1,2" }, 199 | { a: 2000, b: "3,1" }, 200 | { a: 1, b: "2,2" }, 201 | ] 202 | const detectedTypes = inferTypes(data, { decimal: "," }) 203 | 204 | // the value of detectedTypes is 205 | // { 206 | // a: { 207 | // type: "number" 208 | // locale: undefined 209 | // decimal: "," 210 | // group: "" 211 | // numerals: Array(10) ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] 212 | // }, 213 | // b: { 214 | // type: "number" 215 | // locale: undefined 216 | // decimal: "," 217 | // group: "" 218 | // numerals: Array(10) ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] 219 | // } 220 | // } 221 | ``` 222 | 223 | :::info 224 | The function `inferTypes` limits its search for data types to numbers with `.` as decimal separator, dates and datetimes in ISO format, 225 | but doesn't explore all the possible date formats and formatting options. 226 | ::: 227 | 228 | `parseDataset` 229 | ------------- 230 | 231 | This function can be used to parse a "formatted" dataset, already split in row and columns, and has the following syntax: 232 | 233 | ### parseDataset(data, [types], [parsingOptions]) ⇒ [ParserResult](api#parserresult--object) 234 | 235 | 236 | - The `data` parameter is the array of objects that must be parsed 237 | - The `types` is an optional object specifying the types we want to enforce, with the same syntax of the dataTypes output described for the `inferTypes` function 238 | - The `parsingOptions` (optional) is the same object described for the `inferTypes` function. 239 | 240 | Note that in case we don't pass the `types` parameter, the library will try and detect types automatically using the `inferTypes` function described above, 241 | and if no datatype can be detected for a column, the library will recognize it as a simple string. 242 | 243 | The function returns an object with three properties: 244 | 245 | - `dataset`: the set of rows that could be parsed 246 | - `dataTypes`: the datatypes used for parsing 247 | - `errors`: errors that prevented from parsing rows according to dataTypes 248 | 249 | **Examples** 250 | 251 | Let's try and parse a dataset with some values changing type in the different rows. 252 | We won't specify any "hint" for data types and no parsing options. 253 | 254 | ```js 255 | import { parseDataset } from "@rawgraphs/rawgraphs-core" 256 | 257 | const data = [ 258 | { a: 1, b: "3" }, 259 | { a: 2, b: 2 }, 260 | { a: 1, b: "3" }, 261 | ] 262 | const { dataset, dataTypes, errors } = parseDataset(data) 263 | 264 | // will return 265 | // 266 | // dataset -> [{a: 1, b: "hello"}, {a: 2, b: "2"}, {a: 1, b: "3"}] 267 | // dataTypes: {a: "number", b: "number" } 268 | // errors -> [] 269 | ``` 270 | 271 | Here's an example of parsing dates with some parsing errors: 272 | 273 | ```js 274 | import { parseDataset } from '@rawgraphs/rawgraphs-core' 275 | const data = [{a: 1, b: "2001-01-01"}, {a: 2000, b: "2001-02-02"}, {a: 1, b: "no data"}] 276 | const { dataset, dataTypes, errors } = parseDataset(data) 277 | 278 | // result = Object { 279 | // dataset: Array(3) [ 280 | // 0: Object {a: 1, b: 2001-01-01T00:00} 281 | // 1: Object {a: 2000, b: 2001-02-02T00:00} 282 | // 2: Object {a: 1, b: undefined} 283 | // ], 284 | // dataTypes: Object { 285 | // a: "number" 286 | // b: Object {type: "date", dateFormat: "YYYY-MM-DD"} 287 | // } 288 | // errors: Array(1) [ 289 | // 0: Object {row: 2, error: Object} 290 | // ] 291 | 292 | 293 | ``` 294 | 295 | In this case the library has been able to detect the `b` column as ISO date, but could just parse correctly 2 of 3 records in the dataset, and the array 296 | of errors contains information about the row and column that could not be parsed. 297 | 298 | Values that cannot be parsed with the specified type become `undefined` in the parsed dataset. 299 | -------------------------------------------------------------------------------- /website/docs/declarative-mapping.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: declarative-mapping 3 | title: Declarative mapping 4 | sidebar_label: Declarative mapping 5 | slug: /declarative-mapping 6 | --- 7 | 8 | As described in the [chart interface page](chart-interface.md) each chart implementation must define a 9 | `mapData` function that prepares the dataset for the rendering. 10 | 11 | Each chart might need a different shape of data, but it's possible to identify some common patterns for transforming data. 12 | For this purpose, we're introducing a "declarative mapping" approach, in which the rawgraphs chart interface can implement the `mapData` 13 | as a plain object describing the "role" of each dimension in the data transformation. 14 | 15 | 16 | :::caution 17 | 18 | This API is still under definition and might change in the future versions. 19 | 20 | ::: 21 | 22 | 23 | 24 | ## Simple column picking 25 | 26 | This form of declarative mapping implements the operation of "translating" column names 27 | from the user dataset to the dimensions described in the charts. 28 | 29 | Let's suppose we have a dataset like: 30 | 31 | ``` 32 | height, color, weight, specie 33 | 10,red,10,A 34 | 12,red,20,A 35 | 12,violet,10,B 36 | 14,violet,10,C 37 | 10,red,10,A 38 | ``` 39 | 40 | and a chart exposing two dimensions: 41 | 42 | ```js 43 | const chart = { 44 | ... 45 | dimensions: [ 46 | { 47 | id: 'x', 48 | label: 'x', 49 | required: 'true', 50 | }, 51 | { 52 | id: 'y', 53 | label: 'y', 54 | required: 'true', 55 | }, 56 | ], 57 | ... 58 | 59 | } 60 | ``` 61 | 62 | If we just want to "reduce" the dataset to a list of `x` and `y` based on column mappings, 63 | the `mapData` function could be written like 64 | 65 | ```js 66 | const chart = { 67 | ... 68 | mapData: function (data, mapping, dataTypes, dimensions) { 69 | return data.map((item) => ({ 70 | x: item[mapping.x.value], 71 | y: item[mapping.y.value], 72 | })); 73 | }, 74 | ... 75 | } 76 | ``` 77 | 78 | Using the declarative mapping, we can just write: 79 | 80 | ```js 81 | const chart = { 82 | ... 83 | mapData: { 84 | x: "get", 85 | y: "get", 86 | } 87 | }, 88 | ... 89 | } 90 | ``` 91 | 92 | Which tells RAWGraphs to just pick the two columns mapped on x and y dimensions. 93 | Note that this implementation would also work for multi-valued dimensions, while the function shown above 94 | would not work in this case. 95 | 96 | :::info 97 | 98 | You can see an example of this kind of mapping in the [core bubble chart](https://github.com/rawgraphs/rawgraphs-charts/blob/master/src/bubblechart/mapping.js) 99 | used in the RAWGraphs app. 100 | ::: 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /website/docs/example-npm.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: example-npm 3 | title: Quick example (npm) 4 | sidebar_label: Quick example (npm) 5 | slug: /example-npm 6 | --- 7 | 8 | Here's a super-quick example of using the rawgraphs from javascript code. 9 | 10 | In this case we'll assume we're using `yarn` to install the package from npm. Refer to 11 | [installation](install.md) for other options. 12 | 13 | See the [live demo](#live-demo) at the end of the page for a complete example. 14 | 15 | 16 | ## Installation 17 | 18 | ```bash 19 | yarn add @rawgraphs/rawgraphs-core 20 | ``` 21 | 22 | 23 | ## Install some charts 24 | 25 | To do something useful with rawgraphs-core, we'll need some charts as well. 26 | Let's use the charts from the rawgraphs-charts package. 27 | 28 | ```bash 29 | yarn add @rawgraphs/rawgraphs-charts 30 | ``` 31 | 32 | ## Rendering a bubblechart 33 | 34 | In this example we'll build a bubblechart from the @rawgraphs/rawgraphs-core repository. 35 | We'll assume a basic html structure like the following 36 | 37 | ```html 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 | 46 | 48 | 49 | 50 | 51 | ``` 52 | 53 | 54 | The chart will be renderend by javascript code in the `index.js`: 55 | 56 | 57 | 58 | ```js 59 | import { chart } from "@rawgraphs/rawgraphs-core" 60 | import { bubblechart } from "@rawgraphs/rawgraphs-charts" 61 | 62 | 63 | // defining some data. 64 | const userData = [ 65 | { size: 10, price: 2, cat: "a" }, 66 | { size: 12, price: 1.2, cat: "a" }, 67 | { size: 1.3,price: 2, cat: "b" }, 68 | { size: 1.5,price: 2.2, cat: "c" }, 69 | { size: 10, price: 4.2, cat: "b" }, 70 | { size: 10, price: 6.2, cat: "c" }, 71 | { size: 12, price: 2.2, cat: "b" }, 72 | ] 73 | 74 | // getting the target HTML node 75 | const root = document.getElementById("app") 76 | 77 | 78 | // define a mapping between dataset and the visual model 79 | const mapping = { 80 | x: { value: "size" }, 81 | y: { value: "price" }, 82 | color: { value: "cat" }, 83 | } 84 | 85 | //instantiating the chart 86 | const viz = chart(bubblechart, { 87 | data: userData, 88 | mapping, 89 | }) 90 | 91 | //rendering into the HTML node 92 | viz.renderToDOM(root) 93 | ``` 94 | 95 | 96 | ## Live demo 97 | 98 | Here's a live demo of the code shown above running in codesandbox 99 | 100 | 101 | 111 | -------------------------------------------------------------------------------- /website/docs/example-script.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: example-script 3 | title: Quick example (direct script inclusion) 4 | sidebar_label: Quick example (direct script inclusion) 5 | slug: /example-script 6 | --- 7 | 8 | Here's a super-quick example of using the rawgraphs from javascript code. 9 | 10 | In this case we'll assume that we'll add rawgraphs to our webpage by 11 | [direct script inclusion](install.md#direct-script-inclusion). Refer to 12 | [installation](install.md) for other options. 13 | 14 | See the [live demo](#live-demo) at the end of the page for a complete example. 15 | 16 | 17 | ## Installation 18 | 19 | We'll install the core with a ` 23 | ``` 24 | 25 | ## Install some charts 26 | 27 | To do something useful with rawgraphs-core, we'll need some charts as well. 28 | Let's use the charts from the rawgraphs-charts package. 29 | Again, we'll use a ` 33 | ``` 34 | 35 | In this case the rawgraphs-core api will be available in the `raw` object in the global (window) scope, 36 | and the rawgraphs-charts contents will be available in the `rawcharts` object. 37 | 38 | ## Rendering a bubblechart 39 | 40 | In this example we'll build a bubblechart from the @rawgraphs/rawgraphs-core repository. 41 | The final html could be the following 42 | 43 | ```html 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | 89 | 90 | 91 | 92 | ``` 93 | 94 | ## Live demo 95 | 96 | Here's a live demo of the code shown above running in codesandbox 97 | 98 | 99 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /website/docs/glossary.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: glossary 3 | title: Glossary 4 | sidebar_label: Glossary 5 | slug: /glossary 6 | --- 7 | 8 | 9 | 10 | #### Chart 11 | 12 | Charts are implementations in RAWGraphs to create a specific [visual model](#visual-model). They returns the [visualisation](#visualization) (an image, usually an SVG node) that the user can then download. 13 | 14 | #### Data column 15 | 16 | A column of the input [dataset](#dataset). RAWgraphs requires as input a single plain table. Each column must have a unique header in the first row. 17 | 18 | #### Dataset 19 | 20 | The data to be visualized. RAWGraphs is able to process tabular datasets, where each row represents a data point and each column a property. Rawgraphs expects headers in the first line. 21 | 22 | #### Data type 23 | 24 | The kind of data contained in each [column](#data-column) of the [dataset](#dataset). RAWgraphs is able to handle `strings` (texts), `dates` and `numbers`. 25 | 26 | #### Dimension 27 | 28 | Dimensions are the data inputs required to render a [chart](#chart), allowing the user to choose the appropriate [data column](#data-column) to be passed to the [mapping](#mapping) and to create the data structure needed by the chart. 29 | 30 | While often dimensions are directly binded to visual variables (e.g. in a `bubble chart` dimensions are and position, and ), dimensions can have a more complex role in the mapping. For example, they can be used to group data, or to create nested structures as in a `treemap`. 31 | 32 | #### Mapping 33 | 34 | Each [chart](#chart) requires an appropriate data structure to be rendered. In short, the mapping transforms [data columns](#data-column) into the 'something else' charts need to work with through the [dimensions](#dimension). 35 | 36 | Mapping returns a data table strucuterd as need by the chart. 37 | 38 | #### Visual model 39 | 40 | The visual result that you want to achieve through a [chart](#chart) implementation. For example, the "bar chart" could be created using different data structures and could feature different [mapping options](#mapping) and as well [visual options](#visual-option). 41 | 42 | #### Visual option 43 | 44 | Visual features of the [chart](#chart) not related to data, for example the artboard width and height, text style, margins, etc. 45 | An exception are colour scale that are exposed as visual options to enable users to choose their own palette. 46 | 47 | #### Visualization 48 | 49 | The image rendered by a [chart](#chart). -------------------------------------------------------------------------------- /website/docs/import-export.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: import-export 3 | title: Import and export 4 | sidebar_label: Import and export 5 | slug: /import-export 6 | --- 7 | 8 | 9 | The rawgraphs-core library provides the functions used in the RAWGraphs app to load and save visualizazions in `.rawgraphs` format. 10 | 11 | This format a JSON file that contains all the data and choices made with the graphical interface (dataset, parsing options, the id of the chart used, mapping, visual options). 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /website/docs/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: install 3 | title: Installation 4 | sidebar_label: Installation 5 | slug: /installation 6 | --- 7 | 8 | ## Environment 9 | 10 | The rawgraphs-core library has no strict requirements of a browser envirnonment. 11 | However, as the charts rendering leverages the DOM api (in particular additional apis provided by SVG) the library always needs a valid html document to operate on. 12 | 13 | At the moment we could not find any pure node.js implementation of the DOM api supporting the full svg specification, so **rawgraphs-core is officially fully supported only in a browser environment**. 14 | 15 | :::info 16 | It's still possible to use rawgraphs-core in a server environment, for example using the [Puppeteer](https://pptr.dev/) library or some other headless browser, but this is out of the scope of the rawgraphs-core documentation 17 | ::: 18 | 19 | 20 | ## Installing from npm 21 | 22 | You can install the library from npm, using your favourite package manager: 23 | 24 | ```bash 25 | # NPM 26 | npm install @rawgraphs/rawgraphs-core 27 | 28 | # Yarn 29 | yarn add @rawgraphs/rawgraphs-core 30 | ``` 31 | 32 | 33 | ## Direct ` 40 | ``` 41 | 42 | In this case the rawgraphs-core api will be available in the `raw` object in the global (window) scope. -------------------------------------------------------------------------------- /website/docs/rendering.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: rendering 3 | title: Rendering charts 4 | sidebar_label: Rendering charts 5 | slug: /rendering 6 | --- 7 | 8 | 9 | The following is a simple example of rendering a chart with rawgraphs-core: 10 | 11 | ```js 12 | // 1. imports: chart factory function and chart implementation 13 | import { chart } from '@rawgraphs/rawgraphs-core' 14 | import { bubblechart } from '@rawgraphs/rawgraphs-charts' 15 | 16 | // 2. defining data 17 | const data = [{age:10, height:130}, {age:18, height:170}] 18 | 19 | // 3. defining mapping 20 | const mapping = { x: { value: 'age' }, y: { value: 'height' } } 21 | 22 | // 4. creating a chart instance 23 | const viz = new chart(bubblechart { data, mapping}) 24 | 25 | // 5. rendering to the DOM 26 | const targetNode = document.getElementById('some-div') 27 | viz.renderToDOM(targetNode) 28 | ``` 29 | 30 | Let's analyze the imports, variables and methods call we introduced in this snippet. 31 | 32 | ## 1. Imports: chart factory function and chart implementation 33 | Here we're importing the `chart` function from 'rawgraphs-core' (this library). 34 | We'll also need an actual **chart implementation** and we'll using a `bubblechart` from [@rawgraphs/rawgraphs-charts](https://github.com/rawgraphs/rawgraphs-charts), 35 | the official charts package used in the RAWGraphs app. 36 | 37 | ## 2. Defining `data` 38 | The data that must be rendered. RAWGraphs expects tabular data as input, in particular a list of objects, 39 | that should all have the same property names. Those property names (the **columns** of the tabular dataset) will be 40 | used as in **mapping**. 41 | 42 | In our example we have a dataset with columns 'age' and 'height' (two numbers), and two data points. 43 | 44 | 45 | 46 | ## 3. Defining `mapping` 47 | The **mapping** object is used to "map" the chart semantics to the columns of the dataset. 48 | It should be an object where: 49 | 50 | - keys represent **dimension** of the chart we're going to render 51 | - values are objects with a **value** property that should contain one or more columns of the dataset 52 | 53 | 54 | ## 4. Creating a chart instance (`viz`) 55 | In rawgraphs-core, high level operations, such as chart rendering, are implemented by the`Chart` class. 56 | 57 | To get an instance of this class, we use the `chart` factory function from 'rawgraphs-core'. 58 | 59 | ### chart(visualModel, config) ⇒ [Chart](#Chart) 60 | This is the entry point for creating a chart with raw. It will return an instance of the RAWChart class and takes two parameters: 61 | 62 | | Param | Type | 63 | | --- | --- | 64 | | chartImplementation | [ChartImplementation](#ChartImplementation) | 65 | | config | [RawConfig](#RawConfig) | 66 | 67 | 68 | The first parameter, the `chartImplementation` is a chart object conforming the [chart interface](chart-interface.md). 69 | 70 | The second parameter, the `config` object, has following properties: 71 | 72 | | Name | Type | Default | Description | 73 | | --- | --- | --- | --- | 74 | | data | Array.<Object> | | the tabular data to be represented | 75 | | dataTypes | [DataTypes](#DataTypes) | | object with data types annotations (column name => type name) | 76 | | mapping | [Mapping](#Mapping) | | the current mapping of column names to dimensions of the current visual model | 77 | | [visualOptions] | [VisualOptions](#VisualOptions) | {} | visual options values | 78 | | [styles] | Object | {} | css in js styles definitions | 79 | 80 | 81 | 82 | ## 5. Rendering to the DOM 83 | Once we have a chart instance, we're ready to render in to the DOM. 84 | First, we must get an instance of valid DOM node, in our case we're using the `getElementById` method of the current document. This node will be the container of our visualization. 85 | 86 | To render the chart, we call the `renderToDOM` method of our chart instance, which has the following signature 87 | 88 | ### chart.renderToDOM(domNode) ⇒ [DOMChart](api.md#domchart) 89 | 90 | this function call will draw the chart in your document and return an instance of the `DOMChart` class, which extends the `Chart` class and has an internal reference to the DOM node. 91 | 92 | :::info 93 | At the moment rawgraphs-core has not support for **updating** a chart once it's rendered. In the `renderToDOM` method, the target dom node inner html is always cleaned before proceeding to the actual chart rendering. 94 | ::: 95 | -------------------------------------------------------------------------------- /website/docs/workflow.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: workflow 3 | title: Workflow 4 | sidebar_label: Workflow 5 | slug: /workflow 6 | --- 7 | 8 | RAWGraphs solves the generic problem of visualizing data with a modular approach, separating common operations, like parsing data or exporting a visualization to svg, from more specific ones related to the visual model we're going to render. 9 | 10 | :::info 11 | In this documentation we will use a glossary wich [you can consult here](glossary). In particular we will use [**"visual model"**](glossary#visual-model) to define the kind of visual result we want to achieve, [**"chart"**](glossary#chart) as the code implementation to realise a visual model, and [**"visualization"**](glossary#visualization) to define the rendered image. 12 | ::: 13 | 14 | The process of creating a visualization in the [rawgraphs-app](https://github.com/rawgraphs/rawgraphs-app) is broken down into these steps: 15 | 16 | 1. **data loading** 17 | 2. **visual model** selection 18 | 3. **mapping** of data variables to the visual model semantics 19 | 4. **customization** of the visual options provided by the specific model 20 | 5. **export** to open vector (svg) and raster (png, jpg) formats for refining outside RAWGraphs 21 | 22 | The [rawgraphs-core](https://github.com/rawgraphs/rawgraphs-core) (this library) provides a set of utilites to implement steps 1, 2, and 5, and for orchestrating the process. 23 | 24 | The other parts of the workflow, the ones that depend on the specific visual model, are abstracted by a **chart interface**, 25 | that must be implemented by each visual model in order to be "hooked" into the process. 26 | 27 | Each chart implementation is delegated for: 28 | 29 | - defining the semantics of the visual model (dimensions) 30 | - transforming a tabular dataset based on a "mapping" between the dimensions and data columns in the dataset 31 | - describing a set of visual options 32 | - implement the actual rendering in a HTML DOM node. 33 | 34 | The [chart interface page](chart-interface.md) describes in detail the API that can be used to create charts. 35 | 36 | The set of charts implementations used in the official RAWGraphs app can be found the [rawgraphs-charts](https://github.com/rawgraphs/rawgraphs-charts) repository. 37 | 38 | :::info 39 | The chart interface intentionally splits the task of rendering a set of data to a document, in order to be plugged into the 40 | rawgrahps-app, that provides an user interface to control the process, encouraging an explorative approach. 41 | ::: 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /website/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'RAWGraphs Core', 3 | tagline: 'RAWGraphs Core library', 4 | url: 'https://rawgraphs.github.io/rawgraphs-core', 5 | baseUrl: '/rawgraphs-core/', 6 | onBrokenLinks: 'throw', 7 | onBrokenMarkdownLinks: 'warn', 8 | favicon: 'img/favicon.png', 9 | organizationName: 'rawgraphs', // Usually your GitHub org/user name. 10 | projectName: 'rawgraphs-core', // Usually your repo name. 11 | themeConfig: { 12 | navbar: { 13 | title: 'RAWGraphs core', 14 | logo: { 15 | alt: 'RAW logo', 16 | src: 'img/icon-512x512.png', 17 | }, 18 | items: [ 19 | { 20 | to: 'docs/', 21 | activeBasePath: 'docs', 22 | label: 'Docs', 23 | position: 'left', 24 | }, 25 | { 26 | href: 'https://github.com/rawgraphs/rawgraphs-core', 27 | label: 'GitHub', 28 | position: 'right', 29 | }, 30 | ], 31 | }, 32 | footer: { 33 | style: 'dark', 34 | links: [ 35 | { 36 | title: 'Links', 37 | items: [ 38 | { 39 | label: 'Main RAWGraphs site', 40 | to: 'https://rawgraphs.io', 41 | }, 42 | { 43 | label: 'Web application', 44 | to: 'https://rawgraphs.io', 45 | }, 46 | { 47 | label: 'Blog', 48 | href: 'https://rawgraphs.io/blog', 49 | }, 50 | 51 | ], 52 | }, 53 | ], 54 | copyright: `Copyright © ${new Date().getFullYear()} RAWGraphs.`, 55 | }, 56 | }, 57 | presets: [ 58 | [ 59 | '@docusaurus/preset-classic', 60 | { 61 | docs: { 62 | sidebarPath: require.resolve('./sidebars.js'), 63 | // Please change this to your repo. 64 | editUrl: 65 | 'https://github.com/rawgraphs/rawgraphs-core/edit/master/website/', 66 | }, 67 | blog: false, 68 | theme: { 69 | customCss: require.resolve('./src/css/custom.css'), 70 | }, 71 | }, 72 | ], 73 | ], 74 | }; 75 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "serve": "docusaurus serve", 12 | "clear": "docusaurus clear" 13 | }, 14 | "dependencies": { 15 | "@docusaurus/core": "2.0.0-alpha.70", 16 | "@docusaurus/preset-classic": "2.0.0-alpha.70", 17 | "@mdx-js/react": "^1.6.21", 18 | "clsx": "^1.1.1", 19 | "react": "^16.8.4", 20 | "react-dom": "^16.8.4" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.5%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | //docs: ['core', 'concepts', 'start', 'charts', 'api'], 3 | 4 | docs: [ 5 | "core", 6 | { 7 | type: "category", 8 | label: "Concepts", 9 | items: [ 10 | "workflow", 11 | "glossary", 12 | ], 13 | }, 14 | { 15 | type: "category", 16 | label: "Getting started", 17 | items: ["install", "example-npm", "example-script"], 18 | }, 19 | "rendering", 20 | { 21 | type: "category", 22 | label: "Implementing charts", 23 | items: ["chart-interface", "chart-utilities", "declarative-mapping"], 24 | }, 25 | { 26 | type: "category", 27 | label: "Utility functions", 28 | items: [ 29 | "data-parsing", 30 | // "import-export", 31 | ], 32 | }, 33 | "api", 34 | ], 35 | } 36 | -------------------------------------------------------------------------------- /website/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #25c2a0; 11 | --ifm-color-primary-dark: rgb(33, 175, 144); 12 | --ifm-color-primary-darker: rgb(31, 165, 136); 13 | --ifm-color-primary-darkest: rgb(26, 136, 112); 14 | --ifm-color-primary-light: rgb(70, 203, 174); 15 | --ifm-color-primary-lighter: rgb(102, 212, 189); 16 | --ifm-color-primary-lightest: rgb(146, 224, 208); 17 | --ifm-code-font-size: 95%; 18 | } 19 | 20 | .docusaurus-highlight-code-line { 21 | background-color: rgb(72, 77, 91); 22 | display: block; 23 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 24 | padding: 0 var(--ifm-pre-padding); 25 | } 26 | -------------------------------------------------------------------------------- /website/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Redirect} from '@docusaurus/router'; 3 | 4 | const Home = () => { 5 | return ; 6 | }; 7 | 8 | export default Home -------------------------------------------------------------------------------- /website/src/pages/index_.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Layout from '@theme/Layout'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | import styles from './styles.module.css'; 8 | 9 | const features = [ 10 | { 11 | title: 'Easy to Use', 12 | imageUrl: 'img/undraw_docusaurus_mountain.svg', 13 | description: ( 14 | <> 15 | Docusaurus was designed from the ground up to be easily installed and 16 | used to get your website up and running quickly. 17 | 18 | ), 19 | }, 20 | { 21 | title: 'Focus on What Matters', 22 | imageUrl: 'img/undraw_docusaurus_tree.svg', 23 | description: ( 24 | <> 25 | Docusaurus lets you focus on your docs, and we'll do the chores. Go 26 | ahead and move your docs into the docs directory. 27 | 28 | ), 29 | }, 30 | { 31 | title: 'Powered by React', 32 | imageUrl: 'img/undraw_docusaurus_react.svg', 33 | description: ( 34 | <> 35 | Extend or customize your website layout by reusing React. Docusaurus can 36 | be extended while reusing the same header and footer. 37 | 38 | ), 39 | }, 40 | ]; 41 | 42 | function Feature({imageUrl, title, description}) { 43 | const imgUrl = useBaseUrl(imageUrl); 44 | return ( 45 |
46 | {imgUrl && ( 47 |
48 | {title} 49 |
50 | )} 51 |

{title}

52 |

{description}

53 |
54 | ); 55 | } 56 | 57 | function Home() { 58 | const context = useDocusaurusContext(); 59 | const {siteConfig = {}} = context; 60 | return ( 61 | 64 |
65 |
66 |

{siteConfig.title}

67 |

{siteConfig.tagline}

68 |
69 | 75 | Get Started 76 | 77 |
78 |
79 |
80 |
81 | {features && features.length > 0 && ( 82 |
83 |
84 |
85 | {features.map((props, idx) => ( 86 | 87 | ))} 88 |
89 |
90 |
91 | )} 92 |
93 |
94 | ); 95 | } 96 | 97 | export default Home; 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /website/src/pages/styles.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | /** 4 | * CSS files with the .module.css suffix will be treated as CSS modules 5 | * and scoped locally. 6 | */ 7 | 8 | .heroBanner { 9 | padding: 4rem 0; 10 | text-align: center; 11 | position: relative; 12 | overflow: hidden; 13 | } 14 | 15 | @media screen and (max-width: 966px) { 16 | .heroBanner { 17 | padding: 2rem; 18 | } 19 | } 20 | 21 | .buttons { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | } 26 | 27 | .features { 28 | display: flex; 29 | align-items: center; 30 | padding: 2rem 0; 31 | width: 100%; 32 | } 33 | 34 | .featureImage { 35 | height: 200px; 36 | width: 200px; 37 | } 38 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rawgraphs/rawgraphs-core/4eef9d5e9a4a707e7b91595b89facad043d5ef5d/website/static/.nojekyll -------------------------------------------------------------------------------- /website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rawgraphs/rawgraphs-core/4eef9d5e9a4a707e7b91595b89facad043d5ef5d/website/static/img/favicon.ico -------------------------------------------------------------------------------- /website/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rawgraphs/rawgraphs-core/4eef9d5e9a4a707e7b91595b89facad043d5ef5d/website/static/img/favicon.png -------------------------------------------------------------------------------- /website/static/img/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rawgraphs/rawgraphs-core/4eef9d5e9a4a707e7b91595b89facad043d5ef5d/website/static/img/icon-512x512.png -------------------------------------------------------------------------------- /website/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/static/img/undraw_docusaurus_tree.svg: -------------------------------------------------------------------------------- 1 | docu_tree --------------------------------------------------------------------------------