├── website
├── static
│ ├── .nojekyll
│ └── img
│ │ ├── favicon.ico
│ │ ├── favicon.png
│ │ ├── icon-512x512.png
│ │ ├── logo.svg
│ │ └── undraw_docusaurus_tree.svg
├── babel.config.js
├── src
│ ├── pages
│ │ ├── index.js
│ │ ├── styles.module.css
│ │ └── index_.js
│ └── css
│ │ └── custom.css
├── .gitignore
├── blog
│ ├── 2019-05-28-hola.md
│ ├── 2019-05-29-hello-world.md
│ └── 2019-05-30-welcome.md
├── docs
│ ├── import-export.md
│ ├── core.md
│ ├── install.md
│ ├── glossary.md
│ ├── workflow.md
│ ├── chart-utilities.md
│ ├── declarative-mapping.md
│ ├── example-npm.md
│ ├── example-script.md
│ ├── rendering.md
│ └── data-parsing.md
├── sidebars.js
├── README.md
├── package.json
└── docusaurus.config.js
├── .github
├── FUNDING.yml
└── workflows
│ └── npm-publish.yml
├── .prettierrc
├── doc.hbs
├── .eslintrc.js
├── sandbox
├── web
│ ├── index.html
│ ├── testfetch.html
│ ├── index.js
│ └── testfetch.js
└── node
│ ├── cli.js
│ └── index.js
├── .gitignore
├── src
├── groupBy.js
├── importExport
│ ├── utils.js
│ ├── index.js
│ ├── importExportV1.1.js
│ ├── importExportV1.js
│ └── importExportV1.2.js
├── __tests__
│ ├── options.test.js
│ ├── rawchart.test.js
│ ├── dataset.test.js
│ ├── colors.test.js
│ └── mapper.test.js
├── index.js
├── labels.js
├── dateFormats.js
├── testSupport
│ └── chart.js
├── utils.js
├── legend.js
├── expressionRegister.js
├── colors.js
├── dataset.js
├── typeDefs.js
└── rawGraphs.js
├── .babelrc.js
├── jsdoc.json
├── data
├── lineup.tsv
├── dispersions.csv
├── countriesGDP.csv
├── letters.tsv
├── cities.csv
├── animals.tsv
├── orchestra.csv
└── music.csv
├── README.md
├── webpack.config.js
├── rollup.config.js
├── package.json
├── CHANGELOG.md
└── LICENSE
/website/static/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [rawgraphs]
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": false
6 | }
7 |
--------------------------------------------------------------------------------
/website/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rawgraphs/rawgraphs-core/HEAD/website/static/img/favicon.ico
--------------------------------------------------------------------------------
/website/static/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rawgraphs/rawgraphs-core/HEAD/website/static/img/favicon.png
--------------------------------------------------------------------------------
/website/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/website/static/img/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rawgraphs/rawgraphs-core/HEAD/website/static/img/icon-512x512.png
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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~}}
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/sandbox/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | RAWGRAPHS EXAMPLE
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/sandbox/web/testfetch.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | RAWGRAPHS EXAMPLE
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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)
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 | 
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 | 
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/__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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.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 |
--------------------------------------------------------------------------------
/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 |

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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/website/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/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.