├── .eslintrc.js
├── .github
├── CONTRIBUTING.md
├── CONTRIBUTOR_LICENSE_AGREEMENT.md
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── build.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── .prettierignore
├── .prettierrc.json
├── .python-version
├── LICENSE
├── README.md
├── babel.config.json
├── charting.css
├── dist
├── charting.css
└── charting.js
├── example
├── README.md
├── example-rendervega.html
├── example1.html
├── example2.html
├── example3.html
├── example3.js
├── example4.html
├── example4.js
├── example5.html
├── exampleContour.html
├── exampleContour.js
├── exampleCrossSection.html
├── exampleCrossSection.js
├── exampleD3ComboChartCrossfilter.html
├── exampleD3ComboChartCrossfilter.js
├── exampleGeoHeat.html
├── exampleGeoHeat.js
├── exampleLineCrossSection.html
├── exampleLineCrossSection.js
├── exampleLinemap.html
├── exampleMultiLayerMap.html
├── exampleMultiLayerMap.js
├── exampleMultiLayerScatterplot.html
├── exampleMultiLayerScatterplot.js
├── exampleRasterMesh.html
├── exampleRasterMesh.js
├── exampleStackedBarchart.html
├── exampleWindBarbs.html
├── exampleWindBarbs.js
├── images
│ └── favicon.png
├── index.html
├── json
│ └── dark-v8.json
├── test
│ ├── .gitignore
│ ├── config
│ │ └── config.exs
│ ├── lib
│ │ └── test.ex
│ ├── mix.exs
│ ├── mix.lock
│ └── test
│ │ ├── example_1_test.exs
│ │ ├── example_2_test.exs
│ │ ├── example_3_test.exs
│ │ ├── example_4_test.exs
│ │ ├── example_5_test.exs
│ │ ├── example_multi_layer_map_test.exs
│ │ ├── example_multi_layer_scatter_test.exs
│ │ └── test_helper.exs
└── webpack.config.js
├── index.js
├── package-lock.json
├── package.json
├── scripts
└── check-formatting.sh
├── scss
├── _variables.scss
└── chart.scss
├── sonar-project.properties
├── src
├── chart-addons
│ ├── dc-legend-cont.js
│ ├── dc-legend-cont.unit.spec.js
│ ├── legend-continuous.js
│ ├── legend-continuous.spec.js
│ ├── legend.js
│ ├── legend.unit.spec.js
│ ├── stacked-legend.js
│ └── stacked-legend.unit.spec.js
├── charts
│ ├── bar-chart.js
│ ├── bar-chart.unit.spec.js
│ ├── box-plot.js
│ ├── box-plot.unit.spec.js
│ ├── bubble-chart.js
│ ├── bubble-chart.unit.spec.js
│ ├── bubble-overlay.js
│ ├── bubble-overlay.unit.spec.js
│ ├── cloud-chart.js
│ ├── cloud-chart.unit.spec.js
│ ├── composite-chart.js
│ ├── composite-chart.unit.spec.js
│ ├── count-widget.js
│ ├── count-widget.unit.spec.js
│ ├── data-count.js
│ ├── data-count.unit.spec.js
│ ├── data-grid.js
│ ├── data-grid.unit.spec.js
│ ├── geo-choropleth-chart.js
│ ├── geo-choropleth.unit.spec.js
│ ├── heatmap.js
│ ├── heatmap.unit.spec.js
│ ├── heavyai-table.js
│ ├── heavyai-table.unit.spec.js
│ ├── line-chart.js
│ ├── line-chart.unit.spec.js
│ ├── number-chart.js
│ ├── number-chart.unit.spec.js
│ ├── pie-chart.js
│ ├── pie-chart.unit.spec.js
│ ├── raster-chart.js
│ ├── raster-chart.unit.spec.js
│ ├── row-chart.js
│ ├── row-chart.unit.spec.js
│ ├── scatter-plot.js
│ └── scatter-plot.unit.spec.js
├── constants
│ ├── dates-and-times.js
│ ├── dc-constants.js
│ ├── file-types.js
│ └── paused.js
├── core
│ ├── core-async.js
│ ├── core-async.unit.spec.js
│ ├── core.js
│ ├── core.unit.spec.js
│ ├── errors.js
│ ├── errors.unit.spec.js
│ ├── events.js
│ ├── events.unit.spec.js
│ ├── filters.js
│ └── filters.unit.spec.js
├── index.js
├── mixins
│ ├── async-mixin.js
│ ├── async-mixin.unit.spec.js
│ ├── base-mixin.js
│ ├── base-mixin.unit.spec.js
│ ├── binning-mixin.js
│ ├── binning-mixin.unit.spec.js
│ ├── bubble-mixin.js
│ ├── bubble-mixin.unit.spec.js
│ ├── cap-mixin.js
│ ├── cap-mixin.unit.spec.js
│ ├── color-mixin.js
│ ├── color-mixin.unit.spec.js
│ ├── coordinate-grid-mixin.js
│ ├── coordinate-grid-mixin.unit.spec.js
│ ├── coordinate-grid-raster-mixin.js
│ ├── coordinate-grid-raster-mixin.unit.spec.js
│ ├── d3.box.js
│ ├── dc-legend-mixin.js
│ ├── dc-legend-mixin.unit.spec.js
│ ├── elastic-dimension-mixin.js
│ ├── filter-mixin.js
│ ├── filter-mixin.unit.spec.js
│ ├── label-mixin.js
│ ├── label-mixin.unit.spec.js
│ ├── legend-mixin.js
│ ├── lock-axis-mixin.js
│ ├── lock-axis-mixin.unit.spec.js
│ ├── map-draw-mixin.unit.spec.js
│ ├── map-mixin.js
│ ├── map-mixin.unit.spec.js
│ ├── margin-mixin.js
│ ├── margin-mixin.unit.spec.js
│ ├── multi-series-mixin.js
│ ├── multi-series-mixin.unit.spec.js
│ ├── multiple-key-label-mixin.js
│ ├── multiple-key-label-mixin.unit.spec.js
│ ├── raster-draw-mixin.js
│ ├── raster-layer-cross-section-terrain-mixin.js
│ ├── raster-layer-heatmap-mixin.js
│ ├── raster-layer-heatmap.unit.spec.js
│ ├── raster-layer-line-mixin.js
│ ├── raster-layer-mesh2d-mixin.js
│ ├── raster-layer-point-mixin.js
│ ├── raster-layer-point-mixin.unit.spec.js
│ ├── raster-layer-poly-mixin.js
│ ├── raster-layer-poly-mixin.unit.spec.js
│ ├── raster-layer-windbarb-mixin.js
│ ├── raster-layer.js
│ ├── raster-mixin.js
│ ├── render-vega-lite
│ │ ├── Definitions
│ │ │ ├── Config
│ │ │ │ ├── ConfigDefinitionInterface.js
│ │ │ │ └── ConfigDefinitionObject.js
│ │ │ ├── Encoding
│ │ │ │ ├── EncodingDefinitionObject.js
│ │ │ │ ├── FieldDefinitionObject.js
│ │ │ │ ├── Scale
│ │ │ │ │ ├── Continuous
│ │ │ │ │ │ ├── ContinuousScale.js
│ │ │ │ │ │ ├── LinearScale.js
│ │ │ │ │ │ ├── LogScale.js
│ │ │ │ │ │ ├── PowScale.js
│ │ │ │ │ │ └── SqrtScale.js
│ │ │ │ │ ├── Discrete
│ │ │ │ │ │ ├── DiscreteScale.js
│ │ │ │ │ │ └── OrdinalScale.js
│ │ │ │ │ ├── Discretizing
│ │ │ │ │ │ ├── DiscretizingScale.js
│ │ │ │ │ │ ├── QuantizeScale.js
│ │ │ │ │ │ └── ThresholdScale.js
│ │ │ │ │ ├── Enums
│ │ │ │ │ │ ├── AccumulatorType.js
│ │ │ │ │ │ ├── ExtentFlags.js
│ │ │ │ │ │ ├── InterpolateType.js
│ │ │ │ │ │ └── ScaleType.js
│ │ │ │ │ ├── Other
│ │ │ │ │ │ └── InternalPassthruScale.js
│ │ │ │ │ ├── ScaleDefinitionObject.js
│ │ │ │ │ └── Utils.js
│ │ │ │ └── ValueDefinitionObject.js
│ │ │ ├── Legend
│ │ │ │ └── LegendDefinitionObject.js
│ │ │ ├── Mark
│ │ │ │ ├── CrossSectionTerrainConfigDefinitionObject.js
│ │ │ │ ├── MarkConfigDefinitionObject.js
│ │ │ │ ├── MarkDefinitionObject.js
│ │ │ │ ├── Mesh2dConfigDefinitionObject.js
│ │ │ │ └── WindBarbConfigDefinitionObject.js
│ │ │ ├── PropertiesDefinitionInterface.js
│ │ │ ├── PropertyDefinition.js
│ │ │ └── Transform
│ │ │ │ ├── TransformDefinitionObject.js
│ │ │ │ └── Transforms
│ │ │ │ ├── CrossSection2dDefinitionObject.js
│ │ │ │ ├── CrossSectionTerrainDefinitionObject.js
│ │ │ │ ├── LimitDefinitionObject.js
│ │ │ │ ├── RasterMesh2dDefinitionObject.js
│ │ │ │ └── SampleDefinitionObject.js
│ │ ├── PropDescriptor
│ │ │ ├── BaseTypes
│ │ │ │ ├── BoolPropDescriptor.js
│ │ │ │ ├── NumericPropDescriptor.js
│ │ │ │ ├── NumericPropValidators.js
│ │ │ │ └── StringPropDescriptor.js
│ │ │ ├── CommonChannelDescriptors.js
│ │ │ ├── CommonChannels
│ │ │ │ ├── ColorChannelDescriptor.js
│ │ │ │ ├── GeographicChannelDescriptor.js
│ │ │ │ ├── OpacityChannelDescriptor.js
│ │ │ │ ├── PositionChannelDescriptor.js
│ │ │ │ └── SizeChannelDescriptor.js
│ │ │ ├── Enums
│ │ │ │ ├── MeasurementType.js
│ │ │ │ └── PropLocation.js
│ │ │ └── PropDescriptor.js
│ │ ├── RasterLayerContext.js
│ │ ├── RenderVegaLite.js
│ │ └── VegaPropertyOutputState.js
│ ├── scatter-mixin.js
│ ├── scatter-mixin.unit.spec.js
│ ├── spinner-mixin.js
│ ├── spinner-mixin.unit.spec.js
│ ├── stack-mixin.js
│ ├── stack-mixin.unit.spec.js
│ └── ui
│ │ ├── coordinate-grid-raster-mixin-ui.js
│ │ ├── lasso-event-constants.js
│ │ ├── lasso-shapes
│ │ ├── LatLonCircle.js
│ │ ├── LatLonPoly.js
│ │ ├── LatLonPolyLine.js
│ │ └── LatLonViewIntersectUtils.js
│ │ ├── lasso-tool-button-svg.js
│ │ ├── lasso-tool-set-types.js
│ │ └── lasso-tool-ui.js
└── utils
│ ├── binning-helpers.js
│ ├── binning-helpers.unit.spec.js
│ ├── color-helpers.js
│ ├── custom-sql-parser.js
│ ├── custom-sql-parser.unit.spec.js
│ ├── formatting-helpers.js
│ ├── formatting-helpers.unit.spec.js
│ ├── logger.js
│ ├── logger.unit.spec.js
│ ├── mapbox-ported-functions.js
│ ├── multiple-key-accessors.js
│ ├── multiple-key-accessors.unit.spec.js
│ ├── utils-contour.js
│ ├── utils-lasso.js
│ ├── utils-lasso.unit.spec.js
│ ├── utils-latlon.js
│ ├── utils-vega.js
│ ├── utils-vega.unit.spec.js
│ ├── utils.js
│ └── utils.unit.spec.js
├── test
├── .mocharc.yml
├── config.js
├── mapbox-gl-mock.js
├── register-babel.js
├── setup-unit-tests.js
└── setup.js
└── webpack.config.js
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## :beetle: If bug
2 | - [ ] Operating System and version:
3 | - [ ] Browser and version:
4 | - [ ] Steps to reproduce (including dashboard link):
5 | - [ ] Description of issue:
6 |
7 | ## :new: If feature request
8 | - [ ] User story:
9 | - [ ] Specific requirements/functionality:
10 | - [ ] Mocks (if applicable):
11 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Merge Checklist
2 | ## :wrench: Issue(s) fixed:
3 | - [ ] Author referenced issue(s) fixed by this PR:
4 | - [ ] Fixes #0
5 |
6 | ## :smoking: Smoke Test
7 | - [ ] Works in chrome
8 | - [ ] Works in firefox
9 | - [ ] Works in safari
10 | - [ ] Works in ie edge
11 | - [ ] Works in ie 11
12 |
13 | ## :ship: Merge
14 | - [ ] author crafted PR's title into release-worthy commit message.
15 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | push:
4 | branches:
5 | - master
6 | # pull_request:
7 | # types: [opened, synchronize, reopened]
8 | jobs:
9 | sonarcloud:
10 | name: SonarCloud
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | with:
15 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
16 | - name: SonarCloud Scan
17 | uses: SonarSource/sonarcloud-github-action@master
18 | env:
19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
20 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # tmp files
4 | *.log
5 | *~
6 |
7 | # local files
8 | node_modules
9 | bower_components
10 | docs
11 |
12 | # ide files
13 | *.iml
14 | .idea
15 | .project
16 | .vscode
17 |
18 | # vim files
19 | .*.swp
20 |
21 | # mac
22 | .DS_Store
23 |
24 | # ctag
25 | .tags
26 |
27 | # files for gh-pages and grunt-contrib-jasmine
28 | .grunt
29 |
30 | # jasmine spec runners
31 | spec/*.html
32 |
33 | # coverage reports
34 | coverage
35 |
36 | # browserify bundle & test
37 | bundle.js
38 | spec/index-browserify.html
39 |
40 | # font noise from some doc tool
41 | web/docs/public
42 |
43 | # example libs
44 | example/js
45 | example/assets
46 | example/css/mapbox-gl-draw.css
47 | example/css/mapdc.min.css
48 | example/css/chart.min.css
49 | example/css/mapdc.css
50 | example/css/chart.css
51 |
52 | .nyc_output
53 | *.lcov
54 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # tmp files
4 | *.log
5 | *~
6 |
7 | # local files
8 | node_modules
9 | bower_components
10 | scripts
11 |
12 | # ide files
13 | *.iml
14 | .idea
15 | .project
16 |
17 | # vim files
18 | .*.swp
19 |
20 | # mac
21 | .DS_Store
22 |
23 | # ctag
24 | .tags
25 |
26 | # files for gh-pages and grunt-contrib-jasmine
27 | .grunt
28 |
29 | # jasmine spec runners
30 | spec/*.html
31 |
32 | # coverage reports
33 | coverage
34 |
35 | # browserify bundle & test
36 | bundle.js
37 | spec/index-browserify.html
38 |
39 | # font noise from some doc tool
40 | web/docs/public
41 | .nyc_output
42 | coverage
43 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 14.21.3
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | src/mixins/coordinate-grid-mixin.js
2 | src/mixins/coordinate-grid-raster-mixin.js
3 | src/mixins/raster-layer-point-mixin.unit.spec.js
4 | src/utils/utils-lasso.unit.spec.js
5 | test/setup-unit-tests.js
6 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "flow",
3 | "semi": false,
4 | "trailingComma": "none"
5 | }
6 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.10
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2020 HEAVY.AI, Inc.
2 | Copyright 2012-2017 Nick Zhu & the dc.js Developers https://github.com/dc-js/dc.js/blob/master/AUTHORS
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HEAVY.AI Charting
2 |
3 | Dimensional charting built to work natively with crossfilter rendered using d3.js.
4 |
5 | # Screenshots
6 |
7 | #### Flights Dataset: Brushing on timeline with Bubble Chart and Row Chart
8 |
9 | 
10 |
11 | #### Tweets Dataset: Brushing on timeline and hovering on Pointmap datapoint which displays row information
12 |
13 | 
14 |
15 | #### Tweets Dataset: Using draw-js tool on pointmap to select specific areas on a map
16 |
17 | 
18 |
19 | # Examples
20 |
21 | Visit our [examples page](https://heavyai.github.io/heavyai-charting/example/) for ideas of what can be created with HEAVY.AI Charting
22 |
23 | # Quick Start
24 |
25 | ##### Step 1: Install Dependencies
26 |
27 | ```bash
28 | npm install #downloads all dependencies and devDependencies
29 | ```
30 |
31 | ##### Step 2: Run Start Script
32 | ```bash
33 | npm run start
34 | or
35 | npm run watch
36 | ```
37 |
38 | # Synopsis
39 |
40 | HEAVY.AI Charting is a superfast charting library that works natively with [crossfilter](https://github.com/square/crossfilter) that is based off [dc.js](https://github.com/dc-js/dc.js). It is designed to work with HEAVY.AI Connector and HEAVY.AI Crossfilter to create charts instantly with our HeavyDB SQL Database. Please see [examples](#examples) for further understanding to quickly create interactive charts.
41 |
42 | Our [Tweetmap Demo](https://www.heavy.ai/demos/tweetmap/) was made only using HEAVY.AI Charting.
43 |
44 | # Documentation
45 |
46 | Visit our [API Docs](https://heavyai.github.io/heavyai-charting/docs/) for additional information on HEAVY.AI Charting
47 |
48 | # Testing
49 |
50 | New components in HEAVY.AI Charting should be unit-tested and linted. All tests will be in the same folder as the new component.
51 |
52 | ```
53 | +-- src
54 | | +-- /mixins/new-mixin-component.js
55 | | +-- /mixins/new-mixin-component.unit.spec.js
56 | ```
57 |
58 | The linter and all tests run on
59 | ```bash
60 | npm run test
61 | ```
62 |
63 | To check only unit-tests run:
64 | ```bash
65 | npm run test:unit
66 | ```
67 |
68 | ### Linting
69 |
70 | Please lint all your code in `@heavyai/charting/`. The lint config file can be found in `.eslintrc.json`. For new components, please fix all lint warnings and errors.
71 |
72 | # Scripts
73 |
74 | | Command | Description |
75 | --- | ---
76 | `npm run start` | Copies files for examples and then serves the example
77 | `npm run build` | Runs webpack and builds js and css in `/dist`
78 | `npm run docs` | Creates and opens docs
79 | `npm run test` | Runs both linting and unit tests
80 | `npm run clean` | Removes node modules, dist, docs, and example files
81 |
82 | # Documentation
83 | The charting library uses [documentation.js](https://github.com/documentationjs/documentation) for API documentation. Docs can be built and viewed locally with the `npm run docs` command.
84 |
85 | ## Contributing
86 |
87 | Interested in contributing? We'd love for you to help! Check out [Contributing.MD](.github/CONTRIBUTING.md)
88 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react",
4 | "@babel/env",
5 | "@babel/preset-flow"
6 | ],
7 | "env": {
8 | "test": {
9 | "ignore": [
10 | "node_modules/mapbox-gl/dist/mapbox-gl.js"
11 | ],
12 | "plugins": [
13 | [
14 | "babel-plugin-transform-require-ignore",
15 | {
16 | "extensions": [
17 | ".less",
18 | ".sass",
19 | ".css",
20 | ".scss"
21 | ]
22 | }
23 | ]
24 | ]
25 | }
26 | },
27 | "plugins": [
28 | "@babel/plugin-syntax-dynamic-import",
29 | "@babel/plugin-syntax-import-meta",
30 | "@babel/plugin-proposal-class-properties",
31 | "@babel/plugin-proposal-json-strings",
32 | [
33 | "@babel/plugin-proposal-decorators",
34 | {
35 | "legacy": true
36 | }
37 | ],
38 | "@babel/plugin-proposal-function-sent",
39 | "@babel/plugin-proposal-export-namespace-from",
40 | "@babel/plugin-proposal-numeric-separator",
41 | "@babel/plugin-proposal-throw-expressions",
42 | "@babel/plugin-proposal-export-default-from",
43 | "@babel/plugin-proposal-logical-assignment-operators",
44 | "@babel/plugin-proposal-optional-chaining",
45 | [
46 | "@babel/plugin-proposal-pipeline-operator",
47 | {
48 | "proposal": "minimal"
49 | }
50 | ],
51 | "@babel/plugin-proposal-nullish-coalescing-operator",
52 | "@babel/plugin-proposal-do-expressions",
53 | "@babel/plugin-proposal-function-bind"
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Charting Examples
2 |
3 | To run the examples locally, go to the root directory and run:
4 |
5 | ```bash
6 | npm start
7 | ```
8 |
9 | ## Running Integration Tests
10 |
11 | The integrations tests for the charting examples are written in [Elixir](http://elixir-lang.org/) using the [Hound](https://github.com/HashNuke/hound) library.
12 |
13 | To run the tests, first install the selenium server by running from the root directory:
14 |
15 | ```bash
16 | npm run selenium:install
17 | ```
18 |
19 | Then start the selenium server:
20 |
21 | ```bash
22 | npm run selenium:start
23 |
24 | ```
25 |
26 | Next, go into the `example/test` directory and run:
27 |
28 | ```bash
29 | mix deps.get
30 | ```
31 |
32 | Once that is done, you can run the tests with:
33 |
34 | ```bash
35 | mix test
36 | ```
37 |
--------------------------------------------------------------------------------
/example/example-rendervega.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HEAVY.AI
5 |
6 |
9 |
10 |
11 |
12 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/example/example3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HEAVY.AI
5 |
6 |
7 |
8 |
30 |
31 |
32 |
40 |
41 |
42 |
43 |
Poly Map with Backend Rendering
44 |
45 |
46 |
47 |
Time chart
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/example/example4.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HEAVY.AI
5 |
6 |
7 |
8 |
30 |
31 |
32 |
49 |
50 |
51 |
52 |
ScatterPlot with Backend Rendering
53 |
54 |
55 |
56 |
57 |
# of Tweets per 5 Minutes
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/example/exampleContour.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
24 | Contour Example
25 |
26 |
27 |
37 |
38 |
39 |
Contour Map
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/example/exampleCrossSection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HEAVY.AI
6 |
7 |
9 |
35 |
36 |
37 |
38 |
52 |
53 |
54 |
55 |
Cross Section
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/example/exampleD3ComboChartCrossfilter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HEAVY.AI
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
34 |
35 |
36 |
37 |
51 |
52 |
53 |
54 |
Total Number of Flights by State
55 |
56 |
57 |
58 |
59 |
Carrier Departure Delay by Arrival Delay (Minutes)
60 |
61 |
62 |
63 |
64 |
Number of Flights by Departure Time
65 |
66 |
67 |
68 |
69 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/example/exampleGeoHeat.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
32 | Document
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
53 |
54 |
Geo Heatmap of Distinct Tweet Languages
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/example/exampleLineCrossSection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HEAVY.AI
6 |
7 |
9 |
10 |
36 |
37 |
38 |
39 |
53 |
54 |
55 |
Cross Section
56 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/example/exampleMultiLayerMap.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HEAVY.AI
5 |
6 |
7 |
8 |
33 |
34 |
35 |
53 |
54 |
55 |
56 |
Map with Backend Rendering of multiple shape layers
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/example/exampleMultiLayerScatterplot.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HEAVY.AI
5 |
6 |
7 |
8 |
33 |
34 |
35 |
53 |
54 |
55 |
56 |
Map with Backend Rendering of multiple shape layers
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/example/exampleRasterMesh.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HEAVY.AI
6 |
7 |
9 |
35 |
36 |
37 |
38 |
52 |
53 |
54 |
55 |
Raster Mesh Map
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/example/exampleWindBarbs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HEAVY.AI
6 |
7 |
9 |
35 |
36 |
37 |
38 |
52 |
53 |
54 |
55 |
Wind Barb Map
56 |
57 |
58 |
59 |
60 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/example/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heavyai/heavyai-charting/0662a750f1ae84252f437d54b4cc659de5478e63/example/images/favicon.png
--------------------------------------------------------------------------------
/example/test/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
--------------------------------------------------------------------------------
/example/test/config/config.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | config :hound,
4 | browser: "chrome",
5 | driver: "selenium",
6 | port: 4444,
7 | retry_time: 1500
8 |
9 | config :test,
10 | url: "http://127.0.0.1:8080"
11 |
12 | config :whippet,
13 | animation_timeout: 1500
14 |
--------------------------------------------------------------------------------
/example/test/lib/test.ex:
--------------------------------------------------------------------------------
1 | defmodule Test do
2 | @moduledoc """
3 | Documentation for Test.
4 | """
5 |
6 | @doc """
7 | Hello world.
8 |
9 | ## Examples
10 |
11 | iex> Test.hello
12 | :world
13 |
14 | """
15 | def hello do
16 | :world
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/example/test/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule Test.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [app: :test,
6 | version: "0.1.0",
7 | elixir: "~> 1.4",
8 | build_embedded: Mix.env == :prod,
9 | start_permanent: Mix.env == :prod,
10 | deps: deps()]
11 | end
12 |
13 | def application do
14 | [extra_applications: [:logger, :whippet, :beagle]]
15 | end
16 |
17 | defp deps do
18 | [
19 | {:hound, "~> 1.0"},
20 | {:beagle, github: "mrblueblue/beagle"},
21 | {:whippet, github: "mrblueblue/whippet"}
22 | ]
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/example/test/mix.lock:
--------------------------------------------------------------------------------
1 | %{"beagle": {:git, "https://github.com/mrblueblue/beagle.git", "6caf762032b004eb49ca5803cf3ef444f488db5f", []},
2 | "certifi": {:hex, :certifi, "1.2.1", "c3904f192bd5284e5b13f20db3ceac9626e14eeacfbb492e19583cf0e37b22be", [:rebar3], []},
3 | "hackney": {:hex, :hackney, "1.8.3", "9148b2311f7d68aff2dc78f2be4da8f05b1a15421b9ef4199ba4935a1d39b3c1", [:rebar3], [{:certifi, "1.2.1", [hex: :certifi, optional: false]}, {:idna, "5.0.1", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
4 | "hound": {:hex, :hound, "1.0.3", "bf1859fcb855bf7a3b84c632ba68f04c43bfeb16efebf0080b6c1efb960c30c6", [:mix], [{:hackney, "~> 1.5", [hex: :hackney, optional: false]}, {:poison, ">= 1.4.0", [hex: :poison, optional: false]}]},
5 | "idna": {:hex, :idna, "5.0.1", "5aa8fdce3f876f49d90daa13f071155a7c3259d02f7847dadd164da4a877da2a", [:rebar3], [{:unicode_util_compat, "0.1.0", [hex: :unicode_util_compat, optional: false]}]},
6 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
7 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
8 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []},
9 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []},
10 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.1.0", "f554c539c826eac8bcf101d2dbcc4a798ba8a960a176ca58d0b2f225c265b174", [:rebar3], []},
11 | "whippet": {:git, "https://github.com/mrblueblue/whippet.git", "30d55fc0a3aab4523073bc65a42e86879f4c51bf", []}}
12 |
--------------------------------------------------------------------------------
/example/test/test/example_1_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExampleOne do
2 | use ExUnit.Case, async: true
3 | use Hound.Helpers
4 | use Whippet
5 |
6 | @url Application.get_env(:test, :url)
7 |
8 | setup do
9 | Hound.start_session
10 | navigate_to("#{@url}/example1.html")
11 | :ok
12 | end
13 |
14 | test "Example 1" do
15 | assert Chart.Row.is_valid(".chart1-example")
16 | assert Chart.Bubble.is_valid(".chart2-example")
17 | assert Chart.Line.is_valid(".chart3-example")
18 | assert Chart.Count.selected() == Whippet.Chart.Count.all()
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/example/test/test/example_2_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExampleTwo do
2 | use ExUnit.Case, async: true
3 | use Hound.Helpers
4 | use Whippet
5 |
6 | @url Application.get_env(:test, :url)
7 |
8 | setup do
9 | Hound.start_session
10 | navigate_to("#{@url}/example2.html")
11 | :ok
12 | end
13 |
14 | test "Example 2" do
15 | assert Chart.Raster.is_valid("#chart1-example", %{legend: false, use_map: true})
16 | assert Chart.Line.is_valid(".chart2-example")
17 | assert Chart.Count.selected() == Whippet.Chart.Count.all()
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/example/test/test/example_3_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExampleThree do
2 | use ExUnit.Case, async: true
3 | use Hound.Helpers
4 | use Whippet
5 |
6 | @url Application.get_env(:test, :url)
7 |
8 | setup do
9 | Hound.start_session
10 | navigate_to("#{@url}/example3.html")
11 | :ok
12 | end
13 |
14 | test "Example 3" do
15 | assert Chart.Raster.is_valid("#polymap", %{legend: false, use_map: true})
16 | assert Chart.Line.is_valid("#timechart")
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/example/test/test/example_4_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExampleFour do
2 | use ExUnit.Case, async: true
3 | use Hound.Helpers
4 | use Whippet
5 |
6 | @url Application.get_env(:test, :url)
7 |
8 | setup do
9 | Hound.start_session
10 | navigate_to("#{@url}/example4.html")
11 | :ok
12 | end
13 |
14 | test "Example 4" do
15 | assert Chart.Raster.is_valid("#chart1-example", %{legend: false, use_map: false})
16 | assert Chart.Line.is_valid(".chart2-example")
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/example/test/test/example_5_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExampleFive do
2 | use ExUnit.Case, async: true
3 | use Hound.Helpers
4 | use Whippet
5 |
6 | @url Application.get_env(:test, :url)
7 | @node "#chart1-example"
8 |
9 | setup do
10 | Hound.start_session
11 | navigate_to("#{@url}/example5.html")
12 | :timer.sleep(5000)
13 | :ok
14 | end
15 |
16 | test "Example 5" do
17 | assert Chart.Raster.is_valid(@node, %{legend: false, use_map: true})
18 |
19 | selected = Chart.Count.selected()
20 | Chart.Raster.draw_polyline(@node, [{200, 200}, {300, 400}, {400, 500}])
21 | assert Chart.Count.selected() !== selected
22 | Chart.Raster.remove_selection(@node, 200, 200)
23 | assert Chart.Count.selected() == selected
24 |
25 | selected = Chart.Count.selected()
26 | Chart.Raster.draw_lasso(@node, [{200, 200}, {300, 400}, {400, 500}, {100, 300}])
27 | assert Chart.Count.selected() !== selected
28 | Chart.Raster.remove_selection(@node, 200, 200)
29 | assert Chart.Count.selected() == selected
30 |
31 |
32 | selected = Chart.Count.selected()
33 | Chart.Raster.draw_circle(@node, 600, 600)
34 | assert Chart.Count.selected() !== selected
35 | Chart.Raster.remove_selection(@node, 600, 600)
36 | assert Chart.Count.selected() == selected
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/example/test/test/example_multi_layer_map_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExampleMultiLayerMap do
2 | use ExUnit.Case, async: true
3 | use Hound.Helpers
4 | use Whippet
5 |
6 | @url Application.get_env(:test, :url)
7 |
8 | setup do
9 | Hound.start_session
10 | navigate_to("#{@url}/exampleMultiLayerMap.html")
11 | :ok
12 | end
13 |
14 | test "Example Multi-Layer Map" do
15 | assert Chart.Raster.is_valid("#chart1-example", %{legend: false, use_map: true})
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/example/test/test/example_multi_layer_scatter_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExampleMultiLayerScatter do
2 | use ExUnit.Case, async: true
3 | use Hound.Helpers
4 | use Whippet
5 |
6 | @url Application.get_env(:test, :url)
7 |
8 | setup do
9 | Hound.start_session
10 | navigate_to("#{@url}/exampleMultiLayerScatterplot.html")
11 | :ok
12 | end
13 |
14 | test "Example Multi-Layer Map" do
15 | assert Chart.Raster.is_valid("#chart1-example", %{legend: false, use_map: false})
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/example/test/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | Application.ensure_all_started :hound
2 | ExUnit.start(max_cases: 3)
3 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path")
2 |
3 | module.exports = {
4 | entry: {
5 | "app": [
6 | "script-loader!@heavyai/connector/dist/browser-connector.js",
7 | "script-loader!@heavyai/crossfilter/dist/mapd-crossfilter.js",
8 | "script-loader!@heavyai/d3-combo-chart/dist/d3ComboChart.js",
9 | path.resolve(__dirname, "../index.js")
10 | ],
11 | example3: path.resolve(__dirname, "./example3.js"),
12 | example4: path.resolve(__dirname, "./example4.js"),
13 | multilayermap: path.resolve(__dirname, "./exampleMultiLayerMap.js"),
14 | multilayerscatterplot: path.resolve(
15 | __dirname,
16 | "./exampleMultiLayerScatterplot.js"
17 | ),
18 | geoheat: path.resolve(__dirname, "./exampleGeoHeat.js"),
19 | windbarb: path.resolve(__dirname, "./exampleWindBarbs.js"),
20 | contour: path.resolve(__dirname, "./exampleContour.js"),
21 | rastermesh: path.resolve(__dirname, "./exampleRasterMesh.js"),
22 | crosssection: path.resolve(__dirname, "./exampleCrossSection.js"),
23 | linecrosssection: path.resolve(__dirname, "./exampleLineCrossSection.js"),
24 | exampleD3ComboChartCrossfilter: path.resolve(
25 | __dirname,
26 | "./exampleD3ComboChartCrossfilter.js"
27 | )
28 | },
29 | devtool: "source-map",
30 | output: {
31 | path: path.resolve(__dirname, "assets"),
32 | publicPath: "/assets/",
33 | filename: "[name].bundle.js"
34 | },
35 | module: {
36 | rules: [
37 | {
38 | test: /\.js$/,
39 | include: [
40 | path.resolve(__dirname, "./example3.js"),
41 | path.resolve(__dirname, "./exampleMultiLayerMap.js"),
42 | path.resolve(__dirname, "./exampleMultiLayerScatterplot.js")
43 | ],
44 | loader: "script-loader"
45 | },
46 | {
47 | test: /\.js$/,
48 | include: [
49 | path.resolve(__dirname, "../src"),
50 | path.resolve(__dirname, "../index.js"),
51 | path.resolve(__dirname, "./exampleGeoHeat.js"),
52 | path.resolve(__dirname, "./exampleWindBarbs.js"),
53 | path.resolve(__dirname, "./exampleContour.js"),
54 | path.resolve(__dirname, "./exampleRasterMesh.js"),
55 | path.resolve(__dirname, "./exampleCrossSection.js"),
56 | path.resolve(__dirname, "./exampleLineCrossSection.js"),
57 | path.resolve(__dirname, "./exampleD3ComboChartCrossfilter.js")
58 | ],
59 | loader: "babel-loader"
60 | },
61 | {
62 | test: /\.css$/,
63 | use: ["style-loader", "css-loader"]
64 | },
65 | {
66 | test: /\.scss$/,
67 | use: ["style-loader", "css-loader", "sass-loader"]
68 | }
69 | ]
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require("expose-loader?dc!./src/index.js")
2 |
--------------------------------------------------------------------------------
/scripts/check-formatting.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # This script basically just runs `prettier`, but it adds a header and footer so that people aren't
4 | # confused why their CI builds failed, since prettier's output is less-than-helpful.
5 |
6 | echo -e "=====> Checking code formatting\n"
7 |
8 | prettier --list-different '{src,test}/**/*.js'
9 |
10 | PRETTIER_RES=$?
11 |
12 | if [[ $PRETTIER_RES -ne 0 ]]; then
13 | echo -e "\n!!! FATAL ERROR: The above files have formatting problems. Run \`npm run format\` to fix.\n\n"
14 | else
15 | echo -e "---> No code formatting problems found\n\n"
16 | fi
17 |
18 | exit $PRETTIER_RES
--------------------------------------------------------------------------------
/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | /*--------Variables-------*/
2 |
3 | $gray1: #f5f5f5;
4 | $gray2: #e2e2e2;
5 | $gray3: #c7c7c7;
6 | $gray4: #a7a7a7;
7 | $gray5: #868686;
8 | $gray6: #666666;
9 | $gray7: #4a4a4a;
10 | $gray8: #323232;
11 | $gray9: #202020;
12 |
13 | $blue-logo: #22A7F0;
14 | $charcoal: #404040;
15 |
16 | $text-black: $charcoal;
17 | $text-gray: $gray5;
18 |
19 | $blue-main: $blue-logo;
20 |
21 | $green-bright: #00E676;
22 | $red-bright: #FF5252;
23 | $orange-bright: #FFAB40;
24 |
25 | $black: #000000;
26 | $white: #ffffff;
27 |
28 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=mapd-charting
2 | sonar.organization=omnisci
3 |
4 | # This is the name and version displayed in the SonarCloud UI.
5 | #sonar.projectName=mapd-charting
6 | #sonar.projectVersion=1.0
7 |
8 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
9 | #sonar.sources=.
10 |
11 | # Encoding of the source code. Default is default system encoding
12 | #sonar.sourceEncoding=UTF-8
13 |
--------------------------------------------------------------------------------
/src/chart-addons/dc-legend-cont.unit.spec.js:
--------------------------------------------------------------------------------
1 | import chai, { expect } from "chai"
2 | import spies from "chai-spies"
3 | import dcLegendCont from "./dc-legend-cont"
4 | import { d3, pieChart } from "../index"
5 |
6 | chai.use(spies)
7 |
8 | describe("dc legend cont", () => {
9 | let legend
10 | beforeEach(() => {
11 | legend = dcLegendCont()
12 | })
13 | describe("legend lock", () => {
14 | it("should instanciate the legend with it unlocked", () => {
15 | expect(legend.isLocked()).to.equal(false)
16 | })
17 | it("should set the legend to locked state", () => {
18 | legend.isLocked(true)
19 | expect(legend.isLocked()).to.equal(true)
20 | })
21 | })
22 |
23 | describe("legend parent", () => {
24 | it("should instanciate the legend with no parent", () => {
25 | expect(legend.parent()).to.equal(null)
26 | })
27 | it("should set the parent to node", () => {
28 | const node = window.document.createElement("DIV")
29 | const parent = {
30 | root: () => d3.select(node)
31 | }
32 | legend.parent(parent)
33 | expect(legend.parent()).to.equal(parent)
34 | })
35 | })
36 |
37 | describe("legend minMax", () => {
38 | it("should instanciate the legend with no minMax", () => {
39 | expect(legend.minMax()).to.equal(null)
40 | })
41 | it("should set the legend minMax to value", () => {
42 | legend.minMax([1, 1000])
43 | expect(legend.minMax()).to.deep.equal([1, 1000])
44 | })
45 | })
46 |
47 | describe("legend render", () => {
48 | it("should render legend without error", () => {
49 | const node = window.document.createElement("DIV")
50 | const parent = pieChart(node)
51 | legend.parent(parent)
52 | legend.render()
53 | })
54 | })
55 |
56 | describe("legendables continuous", () => {
57 | it("should return correct legend swatches, color and value", () => {
58 | const node = window.document.createElement("DIV")
59 | const parent = pieChart(node)
60 | parent.colors().domain([5.5, 15.75])
61 | parent.colors().range(["red", "blue", "green"])
62 | expect(parent.legendablesContinuous()).to.deep.equal([
63 | { color: "red", value: "5.5" },
64 | { color: "blue", value: "10.63" },
65 | { color: "green", value: "15.75" }
66 | ])
67 | })
68 | it("should round value of legendables for start value over 1000", () => {
69 | const node = window.document.createElement("DIV")
70 | const parent = pieChart(node)
71 | parent.colors().domain([1224.85, 1500.75])
72 | parent.colors().range(["red", "blue", "green"])
73 | expect(parent.legendablesContinuous()).to.deep.equal([
74 | { color: "red", value: "1,225" },
75 | { color: "blue", value: "1,363" },
76 | { color: "green", value: "1,501" }
77 | ])
78 | })
79 | })
80 | })
81 |
--------------------------------------------------------------------------------
/src/chart-addons/legend-continuous.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import legendContinuous from "./legend-continuous"
3 |
4 | describe("Legend Continuous", () => {
5 | describe("constructor", () => {
6 | it("should create a continuous legeng", () => {
7 | const legend = legendContinuous()
8 | expect(typeof legend.parent).to.equal("function")
9 | })
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/src/chart-addons/legend.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Legend", () => {
5 | describe("constructor", () => {
6 | it("should create a legeng", () => {
7 | const legend = dc.legend()
8 | expect(typeof legend.render).to.equal("function")
9 | })
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/src/chart-addons/stacked-legend.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import { toLegendState } from "./stacked-legend"
3 |
4 | describe("Stacked Legend", () => {
5 | describe("toLegendState helper", () => {
6 | it("should nominal", () => {
7 | expect(
8 | toLegendState([
9 | {
10 | type: "ordinal",
11 | domain: ["en", "pt", "es", "in", "und", "ja"],
12 | range: [
13 | "#27aeef",
14 | "#ea5545",
15 | "#87bc45",
16 | "#b33dc6",
17 | "#f46a9b",
18 | "#ede15b"
19 | ],
20 | hideOther: true,
21 | showOther: false
22 | }
23 | ])
24 | ).to.deep.equal({
25 | type: "nominal",
26 | title: "Legend",
27 | open: true,
28 | domain: ["en", "pt", "es", "in", "und", "ja"],
29 | position: "bottom-left",
30 | range: [
31 | "#27aeef",
32 | "#ea5545",
33 | "#87bc45",
34 | "#b33dc6",
35 | "#f46a9b",
36 | "#ede15b"
37 | ]
38 | })
39 | })
40 | it("should gradient", () => {
41 | expect(
42 | toLegendState([
43 | {
44 | type: "quantitative",
45 | domain: [0, 100],
46 | range: [
47 | "#27aeef",
48 | "#ea5545",
49 | "#87bc45",
50 | "#b33dc6",
51 | "#f46a9b",
52 | "#ede15b"
53 | ],
54 | legend: {
55 | title: "My Legend",
56 | locked: true
57 | }
58 | }
59 | ])
60 | ).to.deep.equal({
61 | type: "gradient",
62 | title: "My Legend",
63 | locked: true,
64 | open: true,
65 | domain: [0, 100],
66 | position: "bottom-left",
67 | range: [
68 | "#27aeef",
69 | "#ea5545",
70 | "#87bc45",
71 | "#b33dc6",
72 | "#f46a9b",
73 | "#ede15b"
74 | ]
75 | })
76 | })
77 | it("should undefined", () => {})
78 | it("should stacked", () => {})
79 | })
80 | })
81 |
--------------------------------------------------------------------------------
/src/charts/bar-chart.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import barChart from "./bar-chart"
3 |
4 | describe("Bar Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a bar chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const bar = barChart(node)
9 | expect(bar.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/box-plot.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import boxPlot from "./box-plot"
3 |
4 | describe("Box Plot Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a box plot chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const box = boxPlot(node)
9 | expect(box.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/bubble-chart.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import bubbleChart from "./bubble-chart"
3 |
4 | describe("Bubble Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a bubble chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const bar = bubbleChart(node)
9 | expect(bar.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/bubble-overlay.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import bubbleOverlay from "./bubble-overlay"
3 |
4 | describe("Bubble Overlay", () => {
5 | describe("constructor", () => {
6 | it("should create a bubble overlay", () => {
7 | const node = window.document.createElement("DIV")
8 | const bubble = bubbleOverlay(node)
9 | expect(bubble.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/cloud-chart.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import cloudChart from "./cloud-chart"
3 |
4 | describe("Cloud Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a cloud chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const cloud = cloudChart(node)
9 | expect(cloud.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/composite-chart.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import compositeChart from "./composite-chart"
3 |
4 | describe("Composite Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a composite chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const composite = compositeChart(node)
9 | expect(composite.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/count-widget.js:
--------------------------------------------------------------------------------
1 | import d3 from "d3"
2 | import { override } from "../core/core"
3 | import {
4 | groupAll,
5 | lastFilteredSize,
6 | setLastFilteredSize
7 | } from "../core/core-async"
8 | import baseMixin from "../mixins/base-mixin"
9 |
10 | export default function countWidget(parent, chartGroup) {
11 | const _chart = baseMixin({})
12 |
13 | let _formatNumber = d3.format(",")
14 | let _countLabel = "rows"
15 | let _tot = null
16 |
17 | override(_chart, "group", function(group, name) {
18 | if (!arguments.length) {
19 | return _chart._group()
20 | }
21 |
22 | groupAll(group)
23 | return _chart._group(group, name)
24 | })
25 |
26 | const noop = () => null
27 | _chart.isCountChart = () => true
28 | _chart.dataFetchRequestCallback(noop)
29 | _chart.dataFetchSuccessfulCallback(noop)
30 |
31 | _chart.formatNumber = function(formatter) {
32 | if (!arguments.length) {
33 | return _formatNumber
34 | }
35 | _formatNumber = formatter
36 | return _chart
37 | }
38 |
39 | _chart.countLabel = function(_) {
40 | if (!arguments.length) {
41 | return _countLabel
42 | }
43 | _countLabel = _
44 | return _chart
45 | }
46 |
47 | _chart.tot = function(number) {
48 | if (!arguments.length) {
49 | return _tot
50 | }
51 | _tot = number
52 | return _chart
53 | }
54 |
55 | _chart.getTotalRecordsAsync = function() {
56 | if (_chart.tot()) {
57 | return Promise.resolve()
58 | }
59 |
60 | return _chart
61 | .dimension()
62 | .sizeAsync()
63 | .then(tot => {
64 | _chart.tot(tot)
65 | return Promise.resolve()
66 | })
67 | }
68 |
69 | _chart.setDataAsync((group, callbacks) =>
70 | _chart
71 | .getTotalRecordsAsync()
72 | .then(() => {
73 | const id = group.getCrossfilterId()
74 | const filterSize = lastFilteredSize(id)
75 | if (filterSize !== undefined) {
76 | return Promise.resolve(filterSize)
77 | } else {
78 | return group.valueAsync().then(value => {
79 | setLastFilteredSize(id, value)
80 | return value
81 | })
82 | }
83 | })
84 | .then(value => {
85 | callbacks(null, value)
86 | })
87 | .catch(error => {
88 | callbacks(error)
89 | })
90 | )
91 |
92 | _chart._doRender = function(val) {
93 | const all = _formatNumber(_chart.tot())
94 | const selected = _formatNumber(val)
95 |
96 | const wrapper = _chart
97 | .root()
98 | .style("width", "auto")
99 | .style("height", "auto")
100 | .html("")
101 | .append("div")
102 | .attr("class", "count-widget")
103 |
104 | wrapper
105 | .append("span")
106 | .attr("class", "count-selected")
107 | .classed("not-filtered", selected === all)
108 | .text(selected === "-0" ? 0 : selected)
109 |
110 | wrapper
111 | .append("span")
112 | .classed("not-filtered", selected === all)
113 | .text(" of ")
114 |
115 | wrapper
116 | .append("span")
117 | .attr("class", "count-all")
118 | .text(all)
119 |
120 | wrapper
121 | .append("span")
122 | .attr("class", "count-label")
123 | .text(" " + _countLabel)
124 |
125 | return _chart
126 | }
127 |
128 | _chart._doRedraw = function(val) {
129 | return _chart._doRender(val)
130 | }
131 |
132 | return _chart.anchor(parent, chartGroup)
133 | }
134 |
--------------------------------------------------------------------------------
/src/charts/count-widget.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Count Widget Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a count widget", () => {
7 | const node = window.document.createElement("DIV")
8 | const count = dc.countWidget(node)
9 | expect(count.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/data-count.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import dataCount from "./data-count"
3 |
4 | describe("Data Count Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a data count chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const count = dataCount(node)
9 | expect(count.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/data-grid.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Data Grid Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a data grid chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const grid = dc.dataGrid(node)
9 | expect(grid.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/geo-choropleth.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Geo Choropleth Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a geo choropleth chart", () => {
7 | const node = window.document.createElement("DIV")
8 | node.setAttribute("id", "test")
9 | const geo = dc.geoChoroplethChart(node, false, null, {})
10 | expect(geo.anchor()).to.equal(node)
11 | })
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/charts/heatmap.unit.spec.js:
--------------------------------------------------------------------------------
1 | import chai, { expect } from "chai"
2 | import spies from "chai-spies"
3 | import d3 from "d3"
4 | import * as dc from "../index"
5 |
6 | chai.use(spies)
7 |
8 | describe("HEAVY.AI Heatmap Chart", () => {
9 | let heat
10 | beforeEach(() => {
11 | const node = window.document.createElement("DIV")
12 | heat = dc.heatMap(node)
13 | })
14 |
15 | describe("constructor", () => {
16 | it("should create a heatmap chart", () => {
17 | const node = window.document.createElement("DIV")
18 | const heat = dc.heatMap(node)
19 | expect(heat.anchor()).to.equal(node)
20 | })
21 | })
22 |
23 | describe("colorAccessor", () => {
24 | it("should return the value prop of data", () => {
25 | const colorAccessor = heat.colorAccessor()
26 | const value = "red"
27 | expect(colorAccessor({ value })).to.equal(value)
28 | })
29 | })
30 | describe("keyAccessor", () => {
31 | it("should return the first element of the key0 prop of data", () => {
32 | const keyAccessor = heat.keyAccessor()
33 | const key0 = [1, 10]
34 | expect(keyAccessor({ key0 })).to.equal(key0[0])
35 | })
36 | })
37 | describe("valueAccessor", () => {
38 | it("should return the key1 prop of data", () => {
39 | const valueAccessor = heat.valueAccessor()
40 | const key1 = "American Airlines"
41 | expect(valueAccessor({ key1 })).to.equal(key1)
42 | })
43 | it("should handle array case", () => {
44 | const valueAccessor = heat.valueAccessor()
45 | const key1 = [{ value: "Monday" }]
46 | expect(valueAccessor({ key1 })).to.deep.equal("Monday")
47 | })
48 | })
49 | describe("Y Axis ordering", () => {
50 | it("should sort object and string values in descending order", () => {
51 | let data = [{ key1: "American Airlines" }]
52 | expect(heat.shouldSortYAxisDescending(data)).to.equal(true)
53 | data = [{ key1: [{}, {}] }]
54 | expect(heat.shouldSortYAxisDescending(data)).to.equal(true)
55 | })
56 | it("should sort numeric values in ascending order", () => {
57 | let data = [{ key1: 12 }]
58 | expect(heat.shouldSortYAxisDescending(data)).to.equal(false)
59 | data = [{ key1: [12, 16] }]
60 | expect(heat.shouldSortYAxisDescending(data)).to.equal(false)
61 | })
62 | })
63 | describe("label functions", () => {
64 | let rowsLabel
65 | let colsLabel
66 | before(() => {
67 | rowsLabel = heat.rowsLabel()
68 | colsLabel = heat.colsLabel()
69 | })
70 | it("should properly format array data", () => {
71 | expect(rowsLabel([10000, 20000])).to.equal("10,000 \u2013 20,000")
72 | expect(colsLabel([10000, 20000])).to.equal("10,000 \u2013 20,000")
73 | })
74 | it("should return stringified Date", () => {
75 | const date = new Date(Date.UTC(2001, 0, 1))
76 | expect(rowsLabel(date)).to.equal("Jan 1, 2001 00:00:00")
77 | expect(colsLabel(date)).to.equal("Jan 1, 2001 00:00:00")
78 | })
79 | it("should return itself if not number", () => {
80 | const data = "STRING"
81 | expect(rowsLabel(data)).to.equal(data)
82 | expect(colsLabel(data)).to.equal(data)
83 | })
84 | it("should format numbers", () => {
85 | expect(rowsLabel(10000)).to.equal("10,000")
86 | expect(rowsLabel(1)).to.equal("1")
87 | expect(colsLabel(10000)).to.equal("10,000")
88 | expect(colsLabel(1)).to.equal("1")
89 | })
90 | it("should properly format extract data", () => {
91 | expect(
92 | rowsLabel([{ isExtract: true, value: 1, extractUnit: "month" }])
93 | ).to.equal("Jan")
94 | expect(
95 | rowsLabel([{ isExtract: true, value: 1, extractUnit: "hour" }])
96 | ).to.equal("1AM")
97 | })
98 | })
99 | })
100 |
--------------------------------------------------------------------------------
/src/charts/heavyai-table.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 | import { splitStrOnLastAs } from "./heavyai-table"
4 |
5 | describe("HEAVY.AI Table Chart", () => {
6 | describe("constructor", () => {
7 | it("should create a HeavyAI Table chart", () => {
8 | const node = window.document.createElement("DIV")
9 | const number = dc.heavyaiTable(node)
10 | expect(number.anchor()).to.equal(node)
11 | })
12 | })
13 | describe("splitStrOnLastAs", () => {
14 | it("should properly split on last AS within SQL statement", () => {
15 | const sqlStatement =
16 | "cast(SUM(CASE WHEN Shot_result = 'made' THEN 1 ELSE 0 END) as float)/(cast(SUM(CASE WHEN Shot_result = 'missed' THEN 1 ELSE 0 END) as float) + cast(SUM(CASE WHEN Shot_result = 'made' THEN 1 ELSE 0 END) as float)) AS col3"
17 | const result = splitStrOnLastAs(sqlStatement)
18 | expect(result).to.deep.equal([
19 | "cast(SUM(CASE WHEN Shot_result = 'made' THEN 1 ELSE 0 END) as float)/(cast(SUM(CASE WHEN Shot_result = 'missed' THEN 1 ELSE 0 END) as float) + cast(SUM(CASE WHEN Shot_result = 'made' THEN 1 ELSE 0 END) as float))",
20 | "col3"
21 | ])
22 | })
23 | })
24 | describe("Nulls Order", () => {
25 | it("should return nullsOrder", () => {
26 | const node = window.document.createElement("DIV")
27 | const tableChart = dc.heavyaiTable(node)
28 | tableChart.nullsOrder(" NULLS LAST")
29 | expect(tableChart.nullsOrder()).to.equal(" NULLS LAST")
30 | })
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/src/charts/line-chart.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Line Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a line chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const line = dc.lineChart(node)
9 | expect(line.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/number-chart.js:
--------------------------------------------------------------------------------
1 | import baseMixin from "../mixins/base-mixin"
2 | import d3 from "d3"
3 | import { utils } from "../utils/utils"
4 | import { lastFilteredSize, setLastFilteredSize } from "../core/core-async"
5 |
6 | export default function numberChart(parent, chartGroup) {
7 | const _chart = baseMixin({})
8 | let _colors = "#22a7f0"
9 | const _fontSize = null
10 | const _chartWidth = null
11 |
12 | _chart.colors = function(_) {
13 | if (!arguments.length) {
14 | return _colors
15 | }
16 | _colors = _
17 | return _chart
18 | }
19 |
20 | _chart.getColor = function(selected, all) {
21 | return typeof _colors === "string" ? _colors : _colors[0]
22 | }
23 |
24 | _chart.setDataAsync((group, callbacks) =>
25 | group
26 | .valueAsync()
27 | .then(data => {
28 | callbacks(null, data)
29 | })
30 | .catch(error => {
31 | callbacks(error)
32 | })
33 | )
34 |
35 | _chart._doRender = function(val) {
36 | const customFormatter = _chart.valueFormatter()
37 | let formattedValue = val
38 | if (customFormatter && customFormatter(val)) {
39 | formattedValue = customFormatter(val)
40 | } else {
41 | formattedValue = utils.formatValue(val)
42 | if (formattedValue === "-0") {
43 | formattedValue = 0
44 | }
45 | }
46 |
47 | const wrapper = _chart
48 | .root()
49 | .html("")
50 | .append("div")
51 | .attr("class", "number-chart-wrapper")
52 |
53 | const TEXT_PADDING_RATIO = 5
54 | const chartWidth = _chart.width()
55 | const chartHeight = _chart.height()
56 | const wrapperWidth = chartWidth - (chartWidth / 100) * TEXT_PADDING_RATIO
57 | const wrapperHeight = chartHeight - (chartHeight / 100) * TEXT_PADDING_RATIO
58 | const fontSize = utils.getFontSizeFromWidth(
59 | formattedValue,
60 | wrapperWidth,
61 | wrapperHeight
62 | )
63 | wrapper
64 | .append("span")
65 | .attr("class", "number-chart-number")
66 | .style("color", _chart.getColor)
67 | .style("font-size", fontSize + "px")
68 | .html(formattedValue)
69 |
70 | return _chart
71 | }
72 |
73 | _chart._doRedraw = function(val) {
74 | return _chart._doRender(val)
75 | }
76 |
77 | return _chart.anchor(parent, chartGroup)
78 | }
79 |
--------------------------------------------------------------------------------
/src/charts/number-chart.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Number Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a number chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const number = dc.numberChart(node)
9 | expect(number.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/pie-chart.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Pie Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a pie chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const pie = dc.pieChart(node)
9 | expect(pie.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/raster-chart.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 | import mapboxglMock from "../../test/mapbox-gl-mock"
4 |
5 | describe("Raster Chart", () => {
6 | describe("constructor", () => {
7 | it("should create a raster chart", () => {
8 | const node = window.document.createElement("DIV")
9 | node.setAttribute("id", "test")
10 | const raster = dc.rasterChart(node, false, null, mapboxglMock)
11 | expect(raster.anchor()).to.equal(node)
12 | })
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/src/charts/row-chart.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Row Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a row chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const row = dc.rowChart(node)
9 | expect(row.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/charts/scatter-plot.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Scatter Plot Chart", () => {
5 | describe("constructor", () => {
6 | it("should create a scatter plot chart", () => {
7 | const node = window.document.createElement("DIV")
8 | const scatter = dc.scatterPlot(node)
9 | expect(scatter.anchor()).to.equal(node)
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/constants/dates-and-times.js:
--------------------------------------------------------------------------------
1 | export const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
2 |
3 | export const MONTHS = [
4 | "Jan",
5 | "Feb",
6 | "Mar",
7 | "Apr",
8 | "May",
9 | "Jun",
10 | "Jul",
11 | "Aug",
12 | "Sep",
13 | "Oct",
14 | "Nov",
15 | "Dec"
16 | ]
17 |
18 | export const QUARTERS = ["Q1", "Q2", "Q3", "Q4"]
19 |
20 | export const HOURS = [
21 | "12AM",
22 | "1AM",
23 | "2AM",
24 | "3AM",
25 | "4AM",
26 | "5AM",
27 | "6AM",
28 | "7AM",
29 | "8AM",
30 | "9AM",
31 | "10AM",
32 | "11AM",
33 | "12PM",
34 | "1PM",
35 | "2PM",
36 | "3PM",
37 | "4PM",
38 | "5PM",
39 | "6PM",
40 | "7PM",
41 | "8PM",
42 | "9PM",
43 | "10PM",
44 | "11PM"
45 | ]
46 |
47 | export const MS_IN_SECONDS = 0.001
48 | export const SECOND = 1
49 | export const MIN_IN_SECONDS = 60
50 | export const HOUR_IN_SECONDS = 60 * MIN_IN_SECONDS
51 | export const DAY_IN_SECONDS = 24 * HOUR_IN_SECONDS
52 | export const WEEK_IN_SECONDS = 7 * DAY_IN_SECONDS
53 | export const MONTH_IN_SECONDS = 30 * DAY_IN_SECONDS
54 | export const QUARTER_IN_SECONDS = 3 * MONTH_IN_SECONDS
55 | export const YEAR_IN_SECONDS = 365 * DAY_IN_SECONDS
56 | export const DECADE_IN_SECONDS = 10 * YEAR_IN_SECONDS
57 | export const CENTURY_IN_SECONDS = 10 * DECADE_IN_SECONDS
58 |
59 | export const TIME_LABELS = [
60 | "millisecond",
61 | "second",
62 | "minute",
63 | "hour",
64 | "day",
65 | "week",
66 | "month",
67 | "quarter",
68 | "year",
69 | "decade"
70 | ]
71 |
72 | export const TIME_LABEL_TO_SECONDS = {
73 | century: CENTURY_IN_SECONDS,
74 | decade: DECADE_IN_SECONDS,
75 | year: YEAR_IN_SECONDS,
76 | quarter: QUARTER_IN_SECONDS,
77 | month: MONTH_IN_SECONDS,
78 | week: WEEK_IN_SECONDS,
79 | day: DAY_IN_SECONDS,
80 | hour: HOUR_IN_SECONDS,
81 | minute: MIN_IN_SECONDS,
82 | second: SECOND,
83 | millisecond: MS_IN_SECONDS
84 | }
85 |
--------------------------------------------------------------------------------
/src/constants/dc-constants.js:
--------------------------------------------------------------------------------
1 | export const SPINNER_DELAY = 1000
2 | export const IMAGE_SIZE_LIMIT = 5242880 // 5MB
3 |
--------------------------------------------------------------------------------
/src/constants/file-types.js:
--------------------------------------------------------------------------------
1 | export const IMAGE_EXTENSIONS = {
2 | JP2: ".jp2",
3 | TIF: ".tif",
4 | TIFF: ".tiff",
5 | APNG: ".apng",
6 | PNG: ".png",
7 | GIF: ".gif",
8 | JPEG: ".jpeg",
9 | JPG: ".jpg",
10 | WEBP: ".webp",
11 | AVIF: ".avif",
12 | JFIF: ".jfif",
13 | PJPEG: ".pjpeg",
14 | PJP: ".pjp",
15 | SVG: ".svg",
16 | BMP: ".bmp"
17 | }
18 |
--------------------------------------------------------------------------------
/src/constants/paused.js:
--------------------------------------------------------------------------------
1 | export var paused = false
2 |
3 | export function setPaused(status) {
4 | paused = status
5 | }
6 |
--------------------------------------------------------------------------------
/src/core/core.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Core", () => {
5 | describe("Properties", () => {
6 | it("should have sampledCount", () => {
7 | expect(dc.sampledCount()).to.equal(0)
8 | dc.incrementSampledCount()
9 | expect(dc.sampledCount()).to.equal(1)
10 | dc.decrementSampledCount()
11 | expect(dc.sampledCount()).to.equal(0)
12 | })
13 | it("should have logging", () => {
14 | expect(dc.logging()).to.equal(false)
15 | dc.logging(true)
16 | expect(dc.logging()).to.equal(true)
17 | dc.logging(false)
18 | expect(dc.logging()).to.equal(false)
19 | })
20 | it("should have refreshDisabled", () => {
21 | expect(dc.refreshDisabled()).to.equal(false)
22 | dc.disableRefresh()
23 | expect(dc.refreshDisabled()).to.equal(true)
24 | dc.enableRefresh()
25 | expect(dc.refreshDisabled()).to.equal(false)
26 | })
27 | it("should have globalTransitionDuration", () => {
28 | expect(dc.globalTransitionDuration()).to.equal(null)
29 | dc.globalTransitionDuration(50)
30 | expect(dc.globalTransitionDuration()).to.equal(50)
31 | dc.globalTransitionDuration(null)
32 | expect(dc.globalTransitionDuration()).to.equal(null)
33 | })
34 | it("should have disableTransitions", () => {
35 | expect(dc.disableTransitions()).to.equal(false)
36 | dc.disableTransitions(true)
37 | expect(dc.disableTransitions()).to.equal(true)
38 | dc.disableTransitions(false)
39 | expect(dc.disableTransitions()).to.equal(false)
40 | })
41 | })
42 |
43 | describe("chartRegistry", () => {
44 | it("should have the proper methods", () => {
45 | expect(typeof dc.chartRegistry.has).to.equal("function")
46 | expect(typeof dc.chartRegistry.register).to.equal("function")
47 | expect(typeof dc.chartRegistry.deregister).to.equal("function")
48 | expect(typeof dc.chartRegistry.clear).to.equal("function")
49 | expect(typeof dc.chartRegistry.list).to.equal("function")
50 | })
51 | })
52 | })
53 |
--------------------------------------------------------------------------------
/src/core/errors.js:
--------------------------------------------------------------------------------
1 | export function Exception(msg) {
2 | const _msg = msg || "Unexpected internal error"
3 |
4 | this.message = _msg
5 |
6 | this.toString = function() {
7 | return _msg
8 | }
9 | this.stack = new Error().stack
10 | }
11 |
12 | Exception.prototype = Object.create(Error.prototype)
13 | Exception.prototype.constructor = Exception
14 |
15 | export function InvalidStateException() {
16 | Exception.apply(this, arguments)
17 | }
18 |
19 | InvalidStateException.prototype = Object.create(Exception.prototype)
20 | InvalidStateException.prototype.constructor = InvalidStateException
21 |
22 | export function BadArgumentException() {
23 | Exception.apply(this, arguments)
24 | }
25 |
26 | BadArgumentException.prototype = Object.create(Exception.prototype)
27 | BadArgumentException.prototype.constructor = BadArgumentException
28 |
29 | // Used to cancel async operations that could resolve after a chart has been
30 | // destroyed
31 | export class DestroyedChartError extends Error {
32 | constructor(message) {
33 | super(message)
34 | this.name = "DestroyedChartError"
35 | this.message = message || "Chart was destroyed before operation completed"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/core/errors.unit.spec.js:
--------------------------------------------------------------------------------
1 | import * as dc from "../index"
2 | import { expect } from "chai"
3 |
4 | describe("Errors", () => {
5 | it("should export an Exception class", () => {
6 | expect(typeof dc.errors.Exception).to.equal("function")
7 | })
8 |
9 | it("should export an InvalidStateException class", () => {
10 | expect(typeof dc.errors.InvalidStateException).to.equal("function")
11 | })
12 |
13 | it("should export an BadArgumentException class", () => {
14 | expect(typeof dc.errors.BadArgumentException).to.equal("function")
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/src/core/events.js:
--------------------------------------------------------------------------------
1 | export const events = {
2 | current: null
3 | }
4 |
5 | /**
6 | * This function triggers a throttled event function with a specified delay (in milli-seconds). Events
7 | * that are triggered repetitively due to user interaction such brush dragging might flood the library
8 | * and invoke more renders than can be executed in time. Using this function to wrap your event
9 | * function allows the library to smooth out the rendering by throttling events and only responding to
10 | * the most recent event.
11 | * @name events.trigger
12 | * @memberof dc
13 | * @example
14 | * chart.on('renderlet', function(chart) {
15 | * // smooth the rendering through event throttling
16 | * dc.events.trigger(function(){
17 | * // focus some other chart to the range selected by user on this chart
18 | * someOtherChart.focus(chart.filter());
19 | * });
20 | * })
21 | * @param {Function} closure
22 | * @param {Number} [delay]
23 | */
24 | events.trigger = function(closure, delay) {
25 | if (!delay) {
26 | closure()
27 | return
28 | }
29 |
30 | events.current = closure
31 |
32 | setTimeout(() => {
33 | if (closure === events.current) {
34 | closure()
35 | }
36 | }, delay)
37 | }
38 |
--------------------------------------------------------------------------------
/src/core/events.unit.spec.js:
--------------------------------------------------------------------------------
1 | import * as dc from "../index"
2 | import { expect } from "chai"
3 |
4 | describe("Events", () => {
5 | it("should have all the necessary exports", () => {
6 | expect(typeof dc.events.trigger).to.equal("function")
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/src/core/filters.unit.spec.js:
--------------------------------------------------------------------------------
1 | import * as dc from "../index"
2 | import { expect } from "chai"
3 |
4 | describe("Filters", () => {
5 | it("should have all the necessary exports", () => {
6 | expect(typeof dc.filters.RangedFilter).to.equal("function")
7 | expect(typeof dc.filters.TwoDimensionalFilter).to.equal("function")
8 | expect(typeof dc.filters.RangedTwoDimensionalFilter).to.equal("function")
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | require("../charting.css")
2 | require("../scss/chart.scss")
3 | require("mapbox-gl/dist/mapbox-gl.css")
4 | require("legendables/src/styles.css")
5 | require("./mixins/d3.box.js")
6 |
7 | if (process.env.BABEL_ENV !== "test") {
8 | window.mapboxgl = require("mapbox-gl")
9 | }
10 |
11 | export * as d3 from "d3" // eslint-disable-line
12 | export * from "./core/core"
13 | export * from "./core/core-async"
14 | export * from "./core/events"
15 | export * from "./core/filters"
16 | export * from "./utils/utils"
17 | export * from "./utils/logger"
18 |
19 | import {
20 | BadArgumentException,
21 | Exception,
22 | InvalidStateException
23 | } from "./core/errors"
24 |
25 | export const errors = {
26 | Exception,
27 | InvalidStateException,
28 | BadArgumentException
29 | }
30 |
31 | export { default as bubbleOverlay } from "./charts/bubble-overlay"
32 | export { default as barChart } from "./charts/bar-chart"
33 | export { default as bubbleChart } from "./charts/bubble-chart"
34 | export { default as cloudChart } from "./charts/cloud-chart"
35 | export { default as compositeChart } from "./charts/composite-chart"
36 | export { default as dataCount } from "./charts/data-count"
37 | export { default as dataGrid } from "./charts/data-grid"
38 | export { default as geoChoroplethChart } from "./charts/geo-choropleth-chart"
39 | export { default as heatMap } from "./charts/heatmap"
40 | export { default as pieChart } from "./charts/pie-chart"
41 | export { default as lineChart } from "./charts/line-chart"
42 | export { default as numberChart } from "./charts/number-chart"
43 | export { default as rasterChart } from "./charts/raster-chart"
44 | export { default as rowChart } from "./charts/row-chart"
45 | export { default as scatterPlot } from "./charts/scatter-plot"
46 | export { default as heavyaiTable } from "./charts/heavyai-table"
47 | export { default as boxPlot } from "./charts/box-plot"
48 | export { default as countWidget } from "./charts/count-widget"
49 |
50 | export { default as asyncMixin } from "./mixins/async-mixin"
51 | export { default as baseMixin } from "./mixins/base-mixin"
52 | export { default as bubbleMixin } from "./mixins/bubble-mixin"
53 | export { default as capMixin } from "./mixins/cap-mixin"
54 | export { default as colorMixin } from "./mixins/color-mixin"
55 | export { default as coordinateGridMixin } from "./mixins/coordinate-grid-mixin"
56 | export { default as coordinateGridRasterMixin } from "./mixins/coordinate-grid-raster-mixin"
57 | export { default as stackMixin } from "./mixins/stack-mixin"
58 | export { default as marginMixin } from "./mixins/margin-mixin"
59 | export { default as mapMixin } from "./mixins/map-mixin"
60 | export { default as rasterLayerHeatmapMixin } from "./mixins/raster-layer-heatmap-mixin"
61 | export { default as rasterLayerPointMixin } from "./mixins/raster-layer-point-mixin"
62 | export { default as rasterLayerWindBarbMixin } from "./mixins/raster-layer-windbarb-mixin"
63 | export { default as rasterLayerMesh2dMixin } from "./mixins/raster-layer-mesh2d-mixin"
64 | export { default as rasterLayerPolyMixin } from "./mixins/raster-layer-poly-mixin"
65 | export { default as rasterLayer } from "./mixins/raster-layer"
66 | export { default as rasterMixin } from "./mixins/raster-mixin"
67 | export { default as scatterMixin } from "./mixins/scatter-mixin"
68 | export { default as spinnerMixin } from "./mixins/spinner-mixin"
69 |
70 | export { default as legendContinuous } from "./chart-addons/legend-continuous"
71 | export { default as legend } from "./chart-addons/legend"
72 | export { default as legendCont } from "./chart-addons/dc-legend-cont"
73 |
74 | export { default as lassoToolSetTypes } from "./mixins/ui/lasso-tool-set-types"
75 |
76 | export { default as parseFactsFromCustomSQL } from "./utils/custom-sql-parser"
77 |
--------------------------------------------------------------------------------
/src/mixins/async-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import asyncMixin from "./async-mixin"
3 |
4 | describe("Async Mixin", () => {
5 | let chart
6 |
7 | beforeEach(() => {
8 | chart = {
9 | on: () => () => null,
10 | data: () => null
11 | }
12 | })
13 |
14 | describe("constructor", () => {
15 | it("should construct async", () => {
16 | asyncMixin(chart)
17 | })
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/src/mixins/base-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Base Mixin", () => {
5 | describe("constructor", () => {
6 | it("should mixin a base chart", () => {
7 | dc.baseMixin({})
8 | })
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/mixins/bubble-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Bubble Mixin", () => {
5 | describe("constructor", () => {
6 | it("should create a chart", () => {
7 | dc.bubbleMixin({
8 | data: () => {},
9 | renderLabel: () => {},
10 | setDataAsync: () => {}
11 | })
12 | })
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/src/mixins/cap-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Cap Mixin", () => {
5 | describe("constructor", () => {
6 | it("should create a chart", () => {
7 | dc.capMixin({
8 | data: () => {},
9 | renderLabel: () => {},
10 | setDataAsync: () => {},
11 | _mandatoryAttributes: () => []
12 | })
13 | })
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/src/mixins/color-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import chai, { expect } from "chai"
2 | import spies from "chai-spies"
3 | import * as dc from "../index"
4 |
5 | chai.use(spies)
6 |
7 | describe("Color Mixin", () => {
8 | describe("constructor", () => {
9 | it("should create a chart", () => {
10 | dc.colorMixin({})
11 | })
12 | })
13 |
14 | describe("getColor", () => {
15 | let chart
16 |
17 | beforeEach(() => {
18 | chart = dc.colorMixin({})
19 | chart.colorAccessor(() => {})
20 | })
21 |
22 | it("should return grey if data is undefined", () => {
23 | expect(chart.getColor()).to.equal("#e2e2e2")
24 | })
25 | it("should return the value of getColor", () => {
26 | expect(chart.getColor({}, 1)).to.equal("#3182bd")
27 | })
28 | it("should return the middle color range value if getColor is undefined", () => {
29 | const range = () => ["GREEN", "WHITE", "ORANGE"]
30 | chart.colors(() => {})
31 | chart.colors = () => ({ range })
32 | expect(chart.getColor({}, 1)).to.equal("WHITE")
33 | })
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/src/mixins/coordinate-grid-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import chai, { expect } from "chai"
2 | import coordinateGridMixin from "./coordinate-grid-mixin"
3 | import spies from "chai-spies"
4 |
5 | chai.use(spies)
6 |
7 | describe("coordinateGridMixin", () => {
8 | let chart
9 | beforeEach(() => {
10 | const label = "arr_timestamp"
11 | chart = coordinateGridMixin({})
12 | chart.xAxisLabel = () => label
13 | })
14 |
15 | describe("popupTextAccessor", () => {
16 | it("should return the proper popup text", () => {
17 | const value = new Date(Date.UTC(2016, 9, 21))
18 | const arr = [{ datum: { data: { key0: [{ value }] } } }]
19 | expect(chart.popupTextAccessor(arr)()).to.equal("Oct 21, 2016 00:00:00")
20 | })
21 | })
22 |
23 | describe("getNumTicksForXAxis", () => {
24 | beforeEach(() => {
25 | chart.x = () => ({
26 | domain: () => [1, 4]
27 | })
28 | chart.group = () => ({
29 | binParams: () => [{ extract: true }]
30 | })
31 | })
32 | it("should handle extract case", () => {
33 | chart.effectiveWidth = () => 150
34 | expect(chart.getNumTicksForXAxis()).to.equal(3)
35 | })
36 | it("should handle non-extract case", () => {
37 | chart.group = () => ({
38 | binParams: () => [{ extract: false }]
39 | })
40 | chart.effectiveWidth = () => 50
41 | chart.xAxis = () => ({
42 | scale: () => ({
43 | ticks: () => 10
44 | })
45 | })
46 | expect(chart.getNumTicksForXAxis()).to.equal(10)
47 | })
48 | })
49 |
50 | describe("Range Focused", () => {
51 | it("should set range focused", () => {
52 | chart.rangeFocused(true)
53 | expect(chart.rangeFocused()).to.equal(true)
54 | })
55 | })
56 |
57 | describe("Range Focused", () => {
58 | it("should set range focused", () => {
59 | chart.rangeFocused(true)
60 | expect(chart.rangeFocused()).to.equal(true)
61 | })
62 | })
63 |
64 | describe("Range Input", () => {
65 | it("should set range input", () => {
66 | chart.rangeInput(true)
67 | expect(chart.rangeInput()).to.equal(true)
68 | })
69 | })
70 |
71 | describe("Bin Input", () => {
72 | it("should set bin input", () => {
73 | chart.binInput(true)
74 | expect(chart.binInput()).to.equal(true)
75 | })
76 | })
77 |
78 | describe("rescale method", () => {
79 | it("should set _resizing to be true", () => {
80 | expect(chart.rescale().resizing()).to.equal(true)
81 | })
82 | })
83 |
84 | describe("rangeChart method", () => {
85 | it("should", () => {
86 | const range = coordinateGridMixin({})
87 | chart.rangeChart(range)
88 | expect(chart.rangeChart()).to.equal(range)
89 | expect(range.focusChart()).to.equal(chart)
90 | })
91 | })
92 |
93 | describe("zoomScale method", () => {
94 | it("should", () => {
95 | const extent = [0, 100]
96 | expect(chart.zoomScale(extent)).to.equal(chart)
97 | expect(chart.zoomScale()).to.equal(extent)
98 | })
99 | })
100 |
101 | describe("mouseZoomable method", () => {
102 | it("should set and get mouseZoomable", () => {
103 | chart.mouseZoomable(true)
104 | expect(chart.mouseZoomable()).to.equal(true)
105 | })
106 | })
107 | })
108 |
--------------------------------------------------------------------------------
/src/mixins/coordinate-grid-raster-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import coordinateGridRasterMixin from "./coordinate-grid-raster-mixin"
3 |
4 | describe("coordinateGridRasterMixin", () => {
5 | let chart = {}
6 |
7 | describe("constructor", () => {
8 | it("should construct Coordinate Grid Raster Mixin", () => {
9 | coordinateGridRasterMixin(chart)
10 | })
11 | })
12 | describe("filters", () => {
13 | it("should a filters method", () => {
14 | expect(chart.filters().length).to.eq(0)
15 | })
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/src/mixins/dc-legend-mixin.js:
--------------------------------------------------------------------------------
1 | export default function legendMixin(legend) {
2 | legend._scrollPos = 0
3 | legend._expanded = true
4 | legend._heightRatio = 3
5 | legend._title = "Legend"
6 | legend._key = "key0"
7 |
8 | legend.legendType = function() {
9 | return "custom"
10 | }
11 |
12 | legend.render = function() {
13 | // Does not re-render if a custom cursor is on the screen
14 | if (document.getElementById("cursor") !== null) {
15 | return
16 | }
17 |
18 | legend
19 | .parent()
20 | .root()
21 | .select(".dc-legend")
22 | .remove()
23 |
24 | const wrapper = legend
25 | .parent()
26 | .root()
27 | .append("div")
28 | .attr("class", "dc-legend")
29 | .classed("collapsed", !legend._expanded)
30 |
31 | const header = wrapper
32 | .append("div")
33 | .attr("class", "dc-legend-header")
34 | .text(legend._expanded ? legend._title : "Legend")
35 | .on("click", () => {
36 | legend._expanded = !legend._expanded
37 | legend.render()
38 | })
39 |
40 | if (legend._expanded) {
41 | header.append("div").attr("class", "toggle-btn")
42 |
43 | const body = wrapper
44 | .append("div")
45 | .attr("class", "dc-legend-body")
46 | .style(
47 | "max-height",
48 | legend.parent().height() / legend._heightRatio + "px"
49 | )
50 | .on("scroll", () => {
51 | legend._scrollPos = body.node().scrollTop
52 | })
53 |
54 | const legendables = legend.legendables()
55 |
56 | const itemEnter = body
57 | .selectAll(".dc-legend-item")
58 | .data(legendables)
59 | .enter()
60 | .append("div")
61 | .attr("class", "dc-legend-item")
62 |
63 | itemEnter
64 | .append("div")
65 | .attr("class", "legend-item-color")
66 | .style("background", d => (d ? d.color : "#868686"))
67 |
68 | itemEnter
69 | .append("div")
70 | .attr("class", "legend-item-text")
71 | .text(d => d.name)
72 |
73 | const bodyNode = body.node()
74 | if (bodyNode) {
75 | // fix for #4196#issuecomment-376704328
76 | bodyNode.scrollTop = legend._scrollPos
77 | }
78 | }
79 | }
80 |
81 | legend.removeLegend = function() {
82 | legend
83 | .parent()
84 | .root()
85 | .select(".dc-legend")
86 | .remove()
87 | legend.parent().legend(null)
88 | }
89 |
90 | legend.legendables = function() {
91 | const colors = legend.parent().colors()
92 | return zip2(colors.domain(), colors.range()).map(data => ({
93 | name: data[0],
94 | color: data[1],
95 | chart: legend.parent()
96 | }))
97 | }
98 |
99 | legend.setTitle = function(title) {
100 | legend._title = title
101 | return legend
102 | }
103 |
104 | legend.setKey = function(key) {
105 | legend._key = key
106 | return legend
107 | }
108 |
109 | function zip2(list1, list2) {
110 | return (list1.length < list2.length ? list1 : list2).map((_, i) => [
111 | list1[i],
112 | list2[i]
113 | ])
114 | }
115 |
116 | return legend
117 | }
118 |
--------------------------------------------------------------------------------
/src/mixins/dc-legend-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import chai, { expect } from "chai"
2 | import spies from "chai-spies"
3 | import dcLegendMixin from "./dc-legend-mixin"
4 | import barChart from "../charts/bar-chart"
5 | import legend from "../chart-addons/legend"
6 |
7 | chai.use(spies)
8 |
9 | describe("dc legend mixin", () => {
10 | let _legend
11 | let chart
12 | beforeEach(() => {
13 | const node = window.document.createElement("DIV")
14 | chart = barChart(node)
15 | _legend = chart.legend(dcLegendMixin(legend())).legend()
16 | })
17 |
18 | describe("legend remove", () => {
19 | it("should set the parent to node", () => {
20 | _legend.removeLegend()
21 | expect(chart.legend()).to.equal(null)
22 | })
23 | })
24 |
25 | describe("legend setTitle", () => {
26 | it("should set title of the legend", () => {
27 | const testTitle = "Test Title"
28 | _legend.setTitle(testTitle)
29 | expect(chart.legend()._title).to.equal(testTitle)
30 | })
31 | })
32 |
33 | describe("legend setKey", () => {
34 | it("should set key of the legend", () => {
35 | const testKey = "key0"
36 | _legend.setKey(testKey)
37 | expect(chart.legend()._key).to.equal(testKey)
38 | })
39 | })
40 |
41 | describe("legendables", () => {
42 | it("should create a correct set of legends", () => {
43 | const domain = ["test 1", "test 2", "test 3"]
44 | const range = ["red", "green", "blue"]
45 | const expectedResult = [
46 | {
47 | color: "test 1",
48 | name: 0,
49 | chart: chart
50 | },
51 | {
52 | color: "test 2",
53 | name: 1,
54 | chart: chart
55 | }
56 | ]
57 | chart.colors(domain, range)
58 | expect(chart.legend().legendables()).to.deep.equal(expectedResult)
59 | })
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/src/mixins/elastic-dimension-mixin.js:
--------------------------------------------------------------------------------
1 | import { adjust, lensProp, set } from "ramda"
2 | import d3 from "d3"
3 |
4 | export default function elasticDimensionMixin(_chart) {
5 | const NON_INDEX = -1
6 | const _binEvents = ["updateBinBounds"]
7 | const _listeners = d3.dispatch.apply(d3, _binEvents)
8 | const _on = _chart.on.bind(_chart)
9 | let _dataAsync = _chart.getDataAsync()
10 |
11 | _chart.on = function(event, listener) {
12 | const baseEvent = event.includes(".")
13 | ? event.slice(0, event.indexOf("."))
14 | : event
15 | if (_binEvents.indexOf(baseEvent) === NON_INDEX) {
16 | _on(event, listener)
17 | } else {
18 | _listeners.on(event, listener)
19 | }
20 | return _chart
21 | }
22 |
23 | _chart._invokeBinBoundsListener = function(binBounds) {
24 | if (typeof binBounds !== "undefined") {
25 | _listeners.updateBinBounds(_chart, binBounds)
26 | }
27 | }
28 |
29 | function updateBinRange(group, callback) {
30 | if (
31 | !_chart.elasticX() ||
32 | !_chart.binParams()[0] ||
33 | (_chart.rangeChart() && _chart.rangeChart().filter())
34 | ) {
35 | return _dataAsync(group, callback)
36 | }
37 |
38 | group
39 | .getMinMaxWithFilters()
40 | .then(bounds => {
41 | if (!bounds) {
42 | return _dataAsync(group, callback)
43 | }
44 |
45 | _chart.binParams(
46 | adjust(
47 | set(lensProp("binBounds"), [bounds.min_val, bounds.max_val]),
48 | 0,
49 | _chart.binParams()
50 | )
51 | )
52 |
53 | if (_chart.focusChart() && _chart.filter()) {
54 | _chart
55 | .focusChart()
56 | ._invokeBinBoundsListener([bounds.min_val, bounds.max_val])
57 | } else {
58 | _chart._invokeBinBoundsListener([bounds.min_val, bounds.max_val])
59 | }
60 | _dataAsync(group, callback)
61 | })
62 | .catch(err => callback(err))
63 | }
64 |
65 | _chart.on("dataFetch.reBin", () => {
66 | if (_chart.elasticX() && _chart.getDataAsync() !== updateBinRange) {
67 | _dataAsync = _chart.getDataAsync()
68 | _chart.setDataAsync(updateBinRange)
69 | }
70 | })
71 |
72 | return _chart
73 | }
74 |
--------------------------------------------------------------------------------
/src/mixins/filter-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import chai, { expect } from "chai"
2 | import spies from "chai-spies"
3 | import FilterMixin from "./filter-mixin"
4 | import baseMixin from "./base-mixin"
5 |
6 | chai.use(spies)
7 |
8 | describe("Filter Mixin", () => {
9 | let chart
10 | beforeEach(() => {
11 | chart = FilterMixin(baseMixin({}))
12 | })
13 | describe("hasFilterHandler", () => {
14 | it("should handler cases where second argument if array of object collections", () => {
15 | const range = [new Date("7/1/2009"), new Date("6/1/2010")]
16 | const filters = [range]
17 | const filter = [[{ value: range[0] }, { value: range[1] }]]
18 | expect(chart.hasFilterHandler()(filters, filter))
19 | })
20 | })
21 | describe("filter method", () => {
22 | describe("clearFilter case", () => {
23 | it("should call resetFilterHandler", () => {
24 | const handler = chai.spy(() => [])
25 | chart.resetFilterHandler(handler)
26 | chart.filter(Symbol.for("clear"))
27 | expect(handler).to.have.been.called.with([])
28 | })
29 | it("should reset _filters", () => {
30 | chart.filter("Test")
31 | expect(chart.filter()).to.deep.equal("Test")
32 | chart.filter(Symbol.for("clear"))
33 | expect(chart.filter()).to.deep.equal(null)
34 | })
35 | })
36 | describe("empty array case", () => {
37 | it("should call resetFilterHandler", () => {
38 | const handler = chai.spy(() => [])
39 | chart.resetFilterHandler(handler)
40 | chart.filter([])
41 | expect(handler).to.have.been.called.with([])
42 | })
43 | it("should reset _filters", () => {
44 | chart.filter(["test"])
45 | expect(chart.filter()).to.deep.equal("test")
46 | chart.filter([])
47 | expect(chart.filter()).to.deep.equal(null)
48 | })
49 | })
50 | })
51 | describe("handleFilterClick function", () => {
52 | it("should do nothing if default was prevented", () => {
53 | const event = {
54 | defaultPrevented: true
55 | }
56 | chart.handleFilterClick(event)
57 | expect(chart.filter()).to.deep.equal(null)
58 | })
59 |
60 | xit("should set filter", () => {
61 | const event = {}
62 | const filter = ["filter"]
63 | chart.handleFilterClick(event, filter)
64 |
65 | expect(chart.filter()).to.deep.equal("filter")
66 | })
67 | })
68 | })
69 |
--------------------------------------------------------------------------------
/src/mixins/label-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import mixin from "./label-mixin"
2 | import { expect } from "chai"
3 |
4 | describe("label mixin", () => {
5 | let chart
6 |
7 | beforeEach(() => {
8 | chart = {
9 | on: () => () => null
10 | }
11 | chart = mixin(chart)
12 | })
13 | describe("measureLabelsOn", () => {
14 | it("should set and get _measureLabelsOn", () => {
15 | expect(chart.measureLabelsOn()).to.equal(false)
16 | chart.measureLabelsOn(true)
17 | expect(chart.measureLabelsOn()).to.equal(true)
18 | })
19 | })
20 | describe("getContainerWidth", () => {
21 | it("should return correct container width", () => {
22 | chart.effectiveWidth = () => 180
23 | chart.effectiveHeight = () => 160
24 | expect(chart.getAxisLabelContainerWidth("x", false)).to.equal(180)
25 | expect(chart.getAxisLabelContainerWidth("x", true)).to.equal(180 - 32)
26 | expect(chart.getAxisLabelContainerWidth("y", false)).to.equal(160)
27 | })
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/src/mixins/legend-mixin.js:
--------------------------------------------------------------------------------
1 | import d3 from "d3"
2 | import { override } from "../../src/core/core"
3 |
4 | const PERCENTAGE = 100.0
5 | const LOWER_THAN_START_RANGE = 1000
6 |
7 | function defaultFormatter(value) {
8 | const commafy = d3.format(",")
9 | let formattedValue = parseFloat(value).toFixed(2)
10 | if (value >= LOWER_THAN_START_RANGE) {
11 | formattedValue = Math.round(value)
12 | }
13 |
14 | return commafy(formattedValue)
15 | }
16 |
17 | // Finds the name of the color measure, so we can use it when finding the format of the color legend
18 | function getColorMeasureName(chart) {
19 | const groups = chart.group()
20 | if (!(groups && groups.reduce)) {
21 | return null
22 | }
23 | const measures = chart.group().reduce()
24 | if (!Array.isArray(measures)) {
25 | return null
26 | }
27 |
28 | const colorMeasure = measures.filter(x => x.name === "color")
29 | // This function should always return either a string or null, never "undefined", since an
30 | // "undefined" key to the valueFormatter sends us down a weird code path.
31 | return colorMeasure.length > 0 &&
32 | typeof colorMeasure[0].measureName !== "undefined"
33 | ? colorMeasure[0].measureName
34 | : null
35 | }
36 |
37 | export default function legendMixin(chart) {
38 | chart.legendablesContinuous = function() {
39 | const legends = []
40 | const colorDomain = chart.colors().domain()
41 |
42 | const colorDomainSize = colorDomain[1] - colorDomain[0]
43 | const colorRange = chart.colors().range()
44 | const numColors = colorRange.length
45 | const colorMeasureName = getColorMeasureName(chart)
46 |
47 | for (let c = 0; c < numColors; c++) {
48 | let startRange = (c / (numColors - 1)) * colorDomainSize + colorDomain[0]
49 | if (chart.isTargeting()) {
50 | startRange = "%" + (parseFloat(startRange) * PERCENTAGE).toFixed(2)
51 | } else if (chart.colorByExpr() === "count(*)") {
52 | startRange = parseInt(startRange) // eslint-disable-line radix
53 | } else {
54 | startRange = parseFloat(startRange)
55 | }
56 |
57 | let color = null
58 |
59 | if (colorDomainSize === 0) {
60 | color = colorRange[Math.floor(numColors / 2)]
61 | } else {
62 | color = colorRange[c]
63 | }
64 |
65 | const valueFormatter = chart.valueFormatter()
66 | let value = startRange
67 | if (!isNaN(value)) {
68 | value =
69 | (valueFormatter && valueFormatter(value, colorMeasureName)) ||
70 | defaultFormatter(value)
71 | }
72 |
73 | legends.push({
74 | color,
75 | value
76 | })
77 | }
78 |
79 | return legends
80 | }
81 |
82 | const legend_events = ["clearCustomContLegend", "setCustomContLegend"]
83 |
84 | const legend_listeners = d3.dispatch(...legend_events)
85 |
86 | override(chart, "on", (event, listener) => {
87 | const NON_INDEX = -1
88 | if (legend_events.indexOf(event) === NON_INDEX) {
89 | chart._on(event, listener)
90 | } else {
91 | legend_listeners.on(event, listener)
92 | }
93 |
94 | return chart
95 | })
96 |
97 | chart._invokeClearCustomContLegendListener = function() {
98 | legend_listeners.clearCustomContLegend(chart)
99 | }
100 |
101 | chart._invokeSetCustomContLegendListener = function(f) {
102 | legend_listeners.setCustomContLegend(chart, f)
103 | }
104 |
105 | return chart
106 | }
107 |
--------------------------------------------------------------------------------
/src/mixins/lock-axis-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import mixin from "./lock-axis-mixin"
2 | import { expect } from "chai"
3 |
4 | describe("lock axis mixin", () => {
5 | let chart
6 |
7 | beforeEach(() => {
8 | chart = {
9 | on: () => () => null
10 | }
11 | chart = mixin(chart)
12 | })
13 | describe("constructor", () => {
14 | it("should mixin a prepareLockAxis method", () => {
15 | expect(typeof chart.prepareLockAxis).to.equal("function")
16 | })
17 | it("should create an elasticX event", done => {
18 | chart.on("elasticX", function() {
19 | done()
20 | })
21 | chart._invokeelasticXListener()
22 | })
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/src/mixins/map-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 | import mapboxglMock from "../../test/mapbox-gl-mock"
4 |
5 | describe("Map Mixin", () => {
6 | describe("constructor", () => {
7 | it("should mixin a map chart", () => {
8 | const map = dc.mapMixin(dc.baseMixin({}), "test", mapboxglMock, false)
9 | expect(typeof map.init).to.equal("function")
10 | })
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/mixins/margin-mixin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Margin is a mixin that provides margin utility functions for both the Row Chart and Coordinate Grid
3 | * Charts.
4 | * @name marginMixin
5 | * @memberof dc
6 | * @mixin
7 | * @param {Object} _chart
8 | * @return {dc.marginMixin}
9 | */
10 | export default function marginMixin(_chart) {
11 | /* OVERRIDE ---------------------------------------------------------------- */
12 | let _margin = { top: 10, right: 50, bottom: 48, left: 60 }
13 | /* ------------------------------------------------------------------------- */
14 |
15 | /**
16 | * Get or set the margins for a particular coordinate grid chart instance. The margins is stored as
17 | * an associative Javascript array.
18 | * @name margins
19 | * @memberof dc.marginMixin
20 | * @instance
21 | * @example
22 | * var leftMargin = chart.margins().left; // 30 by default
23 | * chart.margins().left = 50;
24 | * leftMargin = chart.margins().left; // now 50
25 | * @param {{top: Number, right: Number, left: Number, bottom: Number}} [margins={top: 10, right: 50, bottom: 30, left: 30}]
26 | * @return {{top: Number, right: Number, left: Number, bottom: Number}}
27 | * @return {dc.marginMixin}
28 | */
29 | _chart.margins = function(margins) {
30 | if (!arguments.length) {
31 | return _margin
32 | }
33 | _margin = margins
34 | return _chart
35 | }
36 |
37 | _chart.effectiveWidth = function() {
38 | return _chart.width() - _chart.margins().left - _chart.margins().right
39 | }
40 |
41 | _chart.effectiveHeight = function() {
42 | return _chart.height() - _chart.margins().top - _chart.margins().bottom
43 | }
44 |
45 | return _chart
46 | }
47 |
--------------------------------------------------------------------------------
/src/mixins/margin-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Margin Mixin", () => {
5 | describe("constructor", () => {
6 | it("should mixin a margin chart", () => {
7 | dc.marginMixin({})
8 | })
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/mixins/multiple-key-label-mixin.js:
--------------------------------------------------------------------------------
1 | import { formatDataValue } from "../utils/formatting-helpers"
2 |
3 | const INDEX_NONE = -1
4 | const SHOULD_RENDER_LABELS = true
5 |
6 | function format(_value, _key, numberFormatter, dateFormatter) {
7 | let customFormatter = null
8 |
9 | let key = _key
10 | let value = _value
11 | let isExtract = false
12 |
13 | if (Array.isArray(_value) && _value[0]) {
14 | value = _value[0].value || _value[0]
15 | if (_value[0].isExtract) {
16 | key = null
17 | isExtract = true
18 | }
19 | }
20 |
21 | if (dateFormatter && value instanceof Date) {
22 | customFormatter = dateFormatter
23 | } else if (numberFormatter && typeof value === "number") {
24 | customFormatter = numberFormatter
25 | }
26 |
27 | return (
28 | (!isExtract && customFormatter && customFormatter(value, key)) ||
29 | formatDataValue(_value)
30 | )
31 | }
32 |
33 | export default function multipleKeysLabelMixin(_chart) {
34 | function label(d) {
35 | const numberFormatter = _chart && _chart.valueFormatter()
36 | const dateFormatter = _chart && _chart.dateFormatter()
37 | const dimensionNames = _chart.dimension().getDimensionName()
38 |
39 | if (dimensionNames.length === 1) {
40 | return format(d.key0, dimensionNames[0], numberFormatter, dateFormatter)
41 | }
42 |
43 | const keysStr = []
44 | let i = 0
45 | for (const key in d) {
46 | if (d.hasOwnProperty(key) && key.indexOf("key") > INDEX_NONE) {
47 | const formatted = format(
48 | d[key],
49 | dimensionNames[i],
50 | numberFormatter,
51 | dateFormatter
52 | )
53 | keysStr.push(formatted)
54 | }
55 | i++
56 | }
57 | return keysStr.join(" / ")
58 | }
59 |
60 | _chart.label(label, SHOULD_RENDER_LABELS)
61 | return _chart
62 | }
63 |
--------------------------------------------------------------------------------
/src/mixins/multiple-key-label-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import multipleKeyLabelMixin from "./multiple-key-label-mixin"
3 |
4 | describe("multipleKeyLabelMixin", () => {
5 | let chart
6 |
7 | beforeEach(() => {
8 | chart = {
9 | label: () => null
10 | }
11 | })
12 |
13 | describe("constructor", () => {
14 | it("should construct multipleKeyLabelMixin", () => {
15 | multipleKeyLabelMixin(chart)
16 | })
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Config/ConfigDefinitionInterface.js:
--------------------------------------------------------------------------------
1 | import PropertiesDefinitionInterface from "../PropertiesDefinitionInterface"
2 |
3 | export default class ConfigDefinitionInterface extends PropertiesDefinitionInterface {
4 | /**
5 | * @param {PropDescriptor} prop_descriptor
6 | * @param {VegaPropertyOutputState} vega_property_output_state
7 | */
8 | materializeProperty(prop_descriptor, vega_property_output_state) {
9 | const prop_name = prop_descriptor.prop_name
10 | if (typeof this[prop_name] !== "undefined") {
11 | const prop_value = this[prop_name]
12 | if (prop_value !== undefined) {
13 | if (!prop_descriptor.isValidMarkDefinition(prop_value)) {
14 | throw new TypeError(
15 | `Invalid value for config property '${prop_name}'`
16 | )
17 | }
18 | const vega_mark_property_object = {}
19 | const context = this
20 | prop_descriptor.vega_mark_prop_names.forEach(vega_mark_prop_name => {
21 | vega_mark_property_object[vega_mark_prop_name] = context[prop_name]
22 | })
23 | vega_property_output_state.addMarkProperty(
24 | prop_name,
25 | vega_mark_property_object
26 | )
27 | }
28 | return true
29 | }
30 | return false
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Config/ConfigDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import PropertiesDefinitionInterface from "../PropertiesDefinitionInterface"
2 | import MarkConfigDefinitionObject from "../Mark/MarkConfigDefinitionObject"
3 | import WindBarbConfigDefinitionObject from "../Mark/WindBarbConfigDefinitionObject"
4 | import Mesh2dConfigDefinitionObject from "../Mark/Mesh2dConfigDefinitionObject"
5 | import CrossSectionTerrainConfigDefinitionObject from "../Mark/CrossSectionTerrainConfigDefinitionObject"
6 |
7 | import assert from "assert"
8 |
9 | export default class ConfigDefinitionObject extends PropertiesDefinitionInterface {
10 | /**
11 | * @param {Object} definition_object
12 | * @param {RasterLayerContext} root_context
13 | */
14 | constructor(definition_object, root_context) {
15 | super(definition_object, null, root_context)
16 |
17 | this.configs_ = new Map()
18 |
19 | const sub_config_classes = [
20 | MarkConfigDefinitionObject,
21 | WindBarbConfigDefinitionObject,
22 | Mesh2dConfigDefinitionObject,
23 | CrossSectionTerrainConfigDefinitionObject
24 | ]
25 |
26 | sub_config_classes.forEach(sub_config_class => {
27 | const key = sub_config_class.key
28 | if (Object.hasOwn(definition_object, key)) {
29 | this.configs_.set(
30 | key,
31 | new sub_config_class(definition_object[key], {
32 | parent: this,
33 | prop_name: key
34 | })
35 | )
36 | } else {
37 | this.configs_.set(
38 | key,
39 | new sub_config_class(sub_config_class.defaults),
40 | { parent: this, prop_name: key }
41 | )
42 | }
43 | })
44 |
45 | this.general_mark_config_ = this.configs_.get(
46 | MarkConfigDefinitionObject.key
47 | )
48 | assert(this.general_mark_config_)
49 | assert(this.general_mark_config_ instanceof MarkConfigDefinitionObject)
50 | }
51 |
52 | /**
53 | * @param {PropDescriptor} prop_descriptor
54 | * @param {VegaPropertyOutputState} vega_property_output_state
55 | */
56 | materializeProperty(prop_descriptor, vega_property_output_state) {
57 | const layer_type = this.root_context.layer_type
58 | const mark_type_config = this.configs_.get(layer_type)
59 | if (
60 | mark_type_config &&
61 | typeof mark_type_config[prop_descriptor.prop_name] !== "undefined"
62 | ) {
63 | return mark_type_config.materializeProperty(
64 | prop_descriptor,
65 | vega_property_output_state
66 | )
67 | } else if (
68 | typeof this.general_mark_config_[prop_descriptor.prop_name] !==
69 | "undefined"
70 | ) {
71 | return this.general_mark_config_.materializeProperty(
72 | prop_descriptor,
73 | vega_property_output_state
74 | )
75 | }
76 | return false
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/EncodingDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import PropertiesDefinitionInterface from "../PropertiesDefinitionInterface"
2 | import assert from "assert"
3 | import FieldDefinitionObject from "./FieldDefinitionObject"
4 | import ValueDefinitionObject from "./ValueDefinitionObject"
5 |
6 | export default class EncodingDefinitionObject extends PropertiesDefinitionInterface {
7 | /**
8 | * @param {PropDescriptor} prop_descriptor
9 | * @param {VegaPropertyOutputState} vega_property_output_state
10 | */
11 | materializeProperty(prop_descriptor, vega_property_output_state) {
12 | assert(this.hasProperty(prop_descriptor.prop_name))
13 | const prop_definition = this.definition_object_[prop_descriptor.prop_name]
14 |
15 | if (typeof prop_definition !== "object") {
16 | throw new Error(
17 | `Invalid encoding definition for '${prop_descriptor.prop_name}' property.`
18 | )
19 | }
20 |
21 | const prop_name = prop_descriptor.prop_name
22 | const parent_info = { parent: this, prop_name }
23 |
24 | if (Object.hasOwn(prop_definition, "value")) {
25 | new ValueDefinitionObject(
26 | prop_definition,
27 | parent_info
28 | ).materializeProperty(prop_descriptor, vega_property_output_state)
29 | } else if (Object.hasOwn(prop_definition, "field")) {
30 | new FieldDefinitionObject(
31 | prop_definition,
32 | parent_info
33 | ).materializeProperty(prop_descriptor, vega_property_output_state)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Continuous/LinearScale.js:
--------------------------------------------------------------------------------
1 | import ContinuousScale from "./ContinuousScale"
2 | import ScaleType from "../Enums/ScaleType"
3 |
4 | export default class LinearScale extends ContinuousScale {
5 | /**
6 | * @param {Object} scale_definition_object
7 | * @param {ParentInfo} parent_info
8 | */
9 | constructor(scale_definition_object, parent_info) {
10 | super(scale_definition_object, ScaleType.kLinear, parent_info)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Continuous/LogScale.js:
--------------------------------------------------------------------------------
1 | import ContinuousScale from "./ContinuousScale"
2 | import ScaleType from "../Enums/ScaleType"
3 |
4 | export default class LogScale extends ContinuousScale {
5 | /**
6 | *
7 | * @param {Object} scale_definition_object
8 | * @param {ParentInfo} parent_info
9 | */
10 | constructor(scale_definition_object, parent_info) {
11 | super(scale_definition_object, ScaleType.kLog, parent_info)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Continuous/PowScale.js:
--------------------------------------------------------------------------------
1 | import ContinuousScale from "./ContinuousScale"
2 | import ScaleType from "../Enums/ScaleType"
3 |
4 | export default class PowScale extends ContinuousScale {
5 | /**
6 | *
7 | * @param {Object} scale_definition_object
8 | * @param {ParentInfo} parent_info
9 | */
10 | constructor(scale_definition_object, parent_info) {
11 | super(scale_definition_object, ScaleType.kPow, parent_info)
12 |
13 | this.exponent_ = 1
14 | if (Object.hasOwn(scale_definition_object, "exponent")) {
15 | if (typeof scale_definition_object.exponent !== "number") {
16 | this.error_message = `Invalid ${scale_definition_object.exponent} value for the 'exponent' property of a pow scale. It must be a number`
17 | }
18 | this.exponent_ = scale_definition_object.exponent
19 | }
20 | }
21 |
22 | /**
23 | *
24 | * @param {PropDescriptor} prop_descriptor
25 | * @param {Object} vega_scale_object
26 | */
27 | _materializeExtraVegaScaleProps(prop_descriptor, vega_scale_object) {
28 | super._materializeExtraVegaScaleProps(prop_descriptor, vega_scale_object)
29 | vega_scale_object.exponent = this.exponent_
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Continuous/SqrtScale.js:
--------------------------------------------------------------------------------
1 | import ContinuousScale from "./ContinuousScale"
2 | import ScaleType from "../Enums/ScaleType"
3 |
4 | export default class SqrtScale extends ContinuousScale {
5 | /**
6 | *
7 | * @param {Object} scale_definition_object
8 | * @param {ParentInfo} parent_info
9 | */
10 | constructor(scale_definition_object, parent_info) {
11 | super(scale_definition_object, ScaleType.kSqrt, parent_info)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Discrete/OrdinalScale.js:
--------------------------------------------------------------------------------
1 | import DiscreteScale from "./DiscreteScale"
2 | import ScaleType from "../Enums/ScaleType"
3 |
4 | export default class OrdinalScale extends DiscreteScale {
5 | /**
6 | *
7 | * @param {Object} scale_definition_object
8 | * @param {ParentInfo} parent_info
9 | */
10 | constructor(scale_definition_object, parent_info) {
11 | super(scale_definition_object, ScaleType.kOrdinal, parent_info)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Discretizing/DiscretizingScale.js:
--------------------------------------------------------------------------------
1 | import assert from "assert"
2 |
3 | import ScaleDefinitionObject from "../ScaleDefinitionObject"
4 | import FieldDefinitionObject from "../../FieldDefinitionObject"
5 | import ScaleType from "../Enums/ScaleType"
6 | import ExtentFlags from "../Enums/ExtentFlags"
7 | import MeasurementType from "../../../../PropDescriptor/Enums/MeasurementType"
8 | import ContinuousScale from "../Continuous/ContinuousScale"
9 |
10 | export default class DiscretizingScale extends ScaleDefinitionObject {
11 | /**
12 | *
13 | * @param {ScaleType} scale_type
14 | * @returns {boolean}
15 | */
16 | static isDescretizingScale(scale_type) {
17 | return (
18 | scale_type === ScaleType.kQuantize || scale_type === ScaleType.kThreshold
19 | )
20 | }
21 |
22 | /**
23 | * @param {ScaleType} scale_type
24 | * @param {MeasurementType} measurement_type
25 | * @returns {boolean}
26 | */
27 | static validateScaleMeasurement(scale_type, measurement_type) {
28 | assert(DiscretizingScale.isDescretizingScale(scale_type))
29 | if (measurement_type !== MeasurementType.kQuantitative) {
30 | throw new Error(
31 | `Discretizing scales can only be used with '${MeasurementType.kQuantitative}' field type encodings`
32 | )
33 | }
34 | }
35 |
36 | /**
37 | * @param {Object} scale_definition_object
38 | * @param {ScaleType} scale_type
39 | * @param {ParentInfo} parent_info
40 | */
41 | constructor(scale_definition_object, scale_type, parent_info) {
42 | assert(
43 | DiscretizingScale.isDescretizingScale(scale_type),
44 | `Invalid ${DiscretizingScale.name} scale type ${scale_type}`
45 | )
46 | super(scale_definition_object, scale_type, parent_info)
47 | }
48 |
49 | /**
50 | * @param {string} domain_keyword
51 | * @param {PropDescriptor} prop_descriptor
52 | * @param {VegaPropertyOutputState} vega_property_output_state
53 | */
54 | _materializeDomainFromKeyword(
55 | domain_keyword,
56 | prop_descriptor,
57 | vega_property_output_state
58 | ) {
59 | if (this.domain_ === "auto") {
60 | /**
61 | * @type {FieldDefinitionObject}
62 | */
63 | const parent = this.parent
64 | assert(parent instanceof FieldDefinitionObject)
65 |
66 | const {
67 | vega_xform_obj,
68 | scale_domain_ref
69 | } = ContinuousScale.buildExtentsVegaTransform(
70 | parent.output,
71 | this.root_context.layer_name,
72 | prop_descriptor.prop_name,
73 | // should equate to: [max(min, avg - 2*stddev), min(max, avg + 2*stddev)]
74 | ExtentFlags.kMin | ExtentFlags.kMax | ExtentFlags.kTwoSigma
75 | )
76 |
77 | vega_property_output_state.addVegaTransform(
78 | prop_descriptor.prop_name,
79 | vega_xform_obj
80 | )
81 |
82 | return scale_domain_ref
83 | }
84 | throw new Error(
85 | `'${domain_keyword}' is not a valid domain keyword for discretizing scale type ${this.type}`
86 | )
87 | }
88 |
89 | /**
90 | * @param {string} range_keyword
91 | * @param {PropDescriptor} prop_descriptor
92 | * @param {VegaPropertyOutputState} vega_property_output_state
93 | */
94 | _materializeRangeFromKeyword(
95 | range_keyword,
96 | prop_descriptor,
97 | // eslint-disable-next-line no-unused-vars
98 | vega_property_output_state
99 | ) {
100 | throw new Error(
101 | `'${range_keyword}' is not a valid range keyword for discretizing scale type ${this.type}`
102 | )
103 | }
104 |
105 | /**
106 | *
107 | * @param {PropDescriptor} prop_descriptor
108 | * @param {Object} vega_scale_object
109 | */
110 | // eslint-disable-next-line no-unused-vars
111 | _materializeExtraVegaScaleProps(prop_descriptor, vega_scale_object) {
112 | // no-op
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Discretizing/QuantizeScale.js:
--------------------------------------------------------------------------------
1 | import DiscretizingScale from "./DiscretizingScale"
2 | import ScaleType from "../Enums/ScaleType"
3 |
4 | export default class QuantizeScale extends DiscretizingScale {
5 | /**
6 | *
7 | * @param {Object} scale_definition_object
8 | * @param {ParentInfo} parent_info
9 | */
10 | constructor(scale_definition_object, parent_info) {
11 | super(scale_definition_object, ScaleType.kQuantize, parent_info)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Enums/AccumulatorType.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 |
3 | export default class AccumulatorType {
4 | /**
5 | * @private
6 | */
7 | static val_to_enum_map_ = {}
8 |
9 | static kMin = new AccumulatorType("min")
10 | static kMax = new AccumulatorType("max")
11 | static kDensity = new AccumulatorType("density")
12 | static kBlend = new AccumulatorType("blend")
13 | static kPct = new AccumulatorType("pct")
14 |
15 | /**
16 | * @param {string} accumulator_type
17 | */
18 | static getAccumulatorTypeFromString(accumulator_type) {
19 | const rtn_obj =
20 | AccumulatorType.val_to_enum_map_[accumulator_type.toLowerCase()]
21 | if (typeof rtn_obj === "undefined") {
22 | throw new Error(
23 | `Invalid accumulator type '${accumulator_type}'. It must be one of ${Object.keys(
24 | AccumulatorType.val_to_enum_map_
25 | )}`
26 | )
27 | }
28 | return rtn_obj
29 | }
30 |
31 | constructor(value) {
32 | this.value = value
33 | AccumulatorType.val_to_enum_map_[this.value] = this
34 | }
35 |
36 | valueOf() {
37 | return this.value
38 | }
39 |
40 | toString() {
41 | return this.value
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Enums/InterpolateType.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 |
3 | export default class InterpolateType {
4 | /**
5 | * @private
6 | */
7 | static val_to_enum_map_ = {}
8 |
9 | static kRgb = new InterpolateType("rgb")
10 | static kHsl = new InterpolateType("hsl")
11 | static kHslLong = new InterpolateType("hsl-long")
12 | static kLab = new InterpolateType("lab")
13 | static kHcl = new InterpolateType("hcl")
14 | static kHclLong = new InterpolateType("hcl-long")
15 | static kAuto = new InterpolateType("auto")
16 |
17 | /**
18 | * @param {string} interpolate_type
19 | */
20 | static getInterpolateTypeFromString(interpolate_type) {
21 | const rtn_obj =
22 | InterpolateType.val_to_enum_map_[interpolate_type.toLowerCase()]
23 | if (typeof rtn_obj === "undefined") {
24 | throw new Error(
25 | `Invalid interpolate type '${interpolate_type}'. It must be one of ${Object.keys(
26 | InterpolateType.val_to_enum_map_
27 | )}`
28 | )
29 | }
30 | return rtn_obj
31 | }
32 |
33 | constructor(value) {
34 | this.value = value
35 | InterpolateType.val_to_enum_map_[this.value] = this
36 | }
37 |
38 | valueOf() {
39 | return this.value
40 | }
41 |
42 | toString() {
43 | return this.value
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Enums/ScaleType.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 |
3 | export default class ScaleType {
4 | /**
5 | * @private
6 | */
7 | static val_to_enum_map_ = {}
8 |
9 | static kLinear = new ScaleType("linear")
10 | static kPow = new ScaleType("pow")
11 | static kSqrt = new ScaleType("sqrt")
12 | static kLog = new ScaleType("log")
13 | static kOrdinal = new ScaleType("ordinal")
14 | static kQuantize = new ScaleType("quantize")
15 | static kThreshold = new ScaleType("threshold")
16 |
17 | /**
18 | * @private
19 | */
20 | static kInternalPassthru_ = new ScaleType("internal-passthru")
21 |
22 | /**
23 | * @param {string} scale_type
24 | */
25 | static getScaleTypeFromString(scale_type) {
26 | const rtn_obj = ScaleType.val_to_enum_map_[scale_type.toLowerCase()]
27 | if (typeof rtn_obj === "undefined") {
28 | throw new Error(
29 | `Invalid scale type '${scale_type}'. It must be one of ${Object.keys(
30 | ScaleType.val_to_enum_map_
31 | )}`
32 | )
33 | }
34 | return rtn_obj
35 | }
36 |
37 | constructor(value) {
38 | this.value = value
39 | ScaleType.val_to_enum_map_[this.value] = this
40 | }
41 |
42 | valueOf() {
43 | return this.value
44 | }
45 |
46 | toString() {
47 | return this.value
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Other/InternalPassthruScale.js:
--------------------------------------------------------------------------------
1 | import ScaleDefinitionObject from "../ScaleDefinitionObject"
2 | import ScaleType from "../Enums/ScaleType"
3 |
4 | export default class InternalPassthruScale extends ScaleDefinitionObject {
5 | /**
6 | *
7 | * @param {Object} passthru_definition_object
8 | * @param {ParentInfo} parent_info
9 | */
10 | constructor(passthru_definition_object, parent_info) {
11 | super(passthru_definition_object, ScaleType.kInternalPassthru, parent_info)
12 | }
13 |
14 | /**
15 | * @param {PropDescriptor} prop_descriptor
16 | * @param {VegaPropertyOutputState} vega_property_output_state
17 | */
18 | // eslint-disable-next-line no-unused-vars
19 | materializeProperty(prop_descriptor, vega_property_output_state) {
20 | // no-op, just passes thru. It does not build any new scales.
21 | // Generally this is used in the event that the scale is built out
22 | // by some other means, but we still need to build out the:
23 | //
24 | // {
25 | // "scale": ...,
26 | // "field": ...
27 | // }
28 | //
29 | // struct
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/Scale/Utils.js:
--------------------------------------------------------------------------------
1 | import assert from "assert"
2 |
3 | import ScaleType from "./Enums/ScaleType"
4 | import LinearScale from "./Continuous/LinearScale"
5 | import PowScale from "./Continuous/PowScale"
6 | import SqrtScale from "./Continuous/SqrtScale"
7 | import LogScale from "./Continuous/LogScale"
8 | import OrdinalScale from "./Discrete/OrdinalScale"
9 | import QuantizeScale from "./Discretizing/QuantizeScale"
10 | import ThresholdScale from "./Discretizing/ThresholdScale"
11 | import InternalPassthruScale from "./Other/InternalPassthruScale"
12 |
13 | // only used for JSDoc type
14 | // eslint-disable-next-line no-unused-vars
15 | import ScaleDefinitionObject from "./ScaleDefinitionObject"
16 |
17 | /**
18 | *
19 | * @param {Object} scale_definition_object
20 | * @param {ParentInfo} parent_info
21 | * @param {Function} get_default_scale_name_callback
22 | * @param {Function} get_default_scale_type_callback
23 | * @param {Function} validate_scale_type_callback
24 | * @returns {ScaleDefinitionObject}
25 | */
26 | const constructScaleFromDefinition = (
27 | scale_definition_object,
28 | parent_info,
29 | get_default_scale_name_callback,
30 | get_default_scale_type_callback,
31 | validate_scale_type_callback
32 | ) => {
33 | let scale_type = ScaleType.kLinear
34 |
35 | if (Object.hasOwn(scale_definition_object, "type")) {
36 | if (typeof scale_definition_object.type !== "string") {
37 | throw new Error(
38 | "The 'type' property of a scale definition must be a string."
39 | )
40 | }
41 |
42 | scale_type = ScaleType.getScaleTypeFromString(scale_definition_object.type)
43 | } else {
44 | assert(get_default_scale_type_callback)
45 | scale_type = get_default_scale_type_callback()
46 | }
47 |
48 | validate_scale_type_callback(scale_type)
49 |
50 | const { name = get_default_scale_name_callback() } = scale_definition_object
51 | scale_definition_object.name = name
52 |
53 | /* eslint-disable no-use-before-define */
54 | switch (scale_type) {
55 | case ScaleType.kLinear:
56 | return new LinearScale(scale_definition_object, parent_info)
57 | case ScaleType.kPow:
58 | return new PowScale(scale_definition_object, parent_info)
59 | case ScaleType.kSqrt:
60 | return new SqrtScale(scale_definition_object, parent_info)
61 | case ScaleType.kLog:
62 | return new LogScale(scale_definition_object, parent_info)
63 | case ScaleType.kOrdinal:
64 | return new OrdinalScale(scale_definition_object, parent_info)
65 | case ScaleType.kQuantize:
66 | return new QuantizeScale(scale_definition_object, parent_info)
67 | case ScaleType.kThreshold:
68 | return new ThresholdScale(scale_definition_object, parent_info)
69 | case ScaleType.kInternalPassthru_:
70 | return new InternalPassthruScale(scale_definition_object, parent_info)
71 | default:
72 | assert(false, `unhandled scale type ${scale_type}`)
73 | }
74 | /* eslint-enable no-use-before-define */
75 | return null
76 | }
77 |
78 | export { constructScaleFromDefinition }
79 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Encoding/ValueDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import PropertiesDefinitionInterface from "../PropertiesDefinitionInterface"
2 | import assert from "assert"
3 |
4 | export default class ValueDefinitionObject extends PropertiesDefinitionInterface {
5 | /**
6 | * @param {Object} value_definition_object
7 | * @param {ParentInfo} parent_info
8 | */
9 | constructor(value_definition_object, parent_info) {
10 | assert(Boolean(parent_info))
11 | super(value_definition_object, parent_info)
12 |
13 | assert(typeof value_definition_object === "object")
14 | assert(Object.hasOwn(value_definition_object, "value"))
15 | if (Object.keys(value_definition_object).length !== 1) {
16 | this.error_message = `Value definitions must have only 1 property called value`
17 | // eslint-disable-next-line no-warning-comments
18 | // TODO(croot): not sure about this early return in
19 | // a constructor. The caller should check for the error_message
20 | // pretty much immediately after.
21 | return
22 | }
23 | this.value_ = value_definition_object.value
24 | }
25 |
26 | /**
27 | * @returns {string, number, boolean}
28 | */
29 | get value() {
30 | return this.value_
31 | }
32 |
33 | /**
34 | * @param {PropDescriptor} prop_descriptor
35 | * @param {VegaPropertyOutputState} vega_property_output_state
36 | */
37 | materializeProperty(prop_descriptor, vega_property_output_state) {
38 | if (this.hasError() || !prop_descriptor.isValidValueDefinition(this)) {
39 | throw new Error(
40 | `Invalid 'value' definition for property '${prop_descriptor.prop_name}'. ${this.error_message}`
41 | )
42 | }
43 | const vega_mark_property_object = {}
44 | const context = this
45 | prop_descriptor.vega_mark_prop_names.forEach(vega_mark_prop_name => {
46 | vega_mark_property_object[vega_mark_prop_name] = context.value
47 | })
48 | vega_property_output_state.addMarkProperty(
49 | prop_descriptor.prop_name,
50 | vega_mark_property_object
51 | )
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Legend/LegendDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import PropertiesDefinitionInterface from "../PropertiesDefinitionInterface"
2 |
3 | export default class LegendDefinitionObject extends PropertiesDefinitionInterface {
4 | /**
5 | * @param {Object} legend_definition_object
6 | * @param {ParentInfo} parent_info
7 | */
8 | constructor(legend_definition_object, parent_info) {
9 | super(legend_definition_object, parent_info, null)
10 |
11 | // NOTE: currently the legend definition only contains the title/open/locked properties, which are custom
12 | // properties to drive the legend in the 'legendables' package
13 | // Only the 'title' property aligns with vega-lite
14 |
15 | const {
16 | title = "Legend",
17 | open = true,
18 | locked = false
19 | } = legend_definition_object
20 |
21 | if (typeof title !== "string") {
22 | throw new TypeError(`Invalid 'title' property for legend definition`)
23 | }
24 |
25 | this.title_ = title
26 |
27 | if (typeof open !== "boolean") {
28 | throw new TypeError(`Invalid 'open' poperty for legend definition`)
29 | }
30 | this.open_ = open
31 |
32 | if (typeof locked !== "boolean") {
33 | throw new TypeError(`Inavlid 'locked' property for legend definition`)
34 | }
35 | this.locked_ = locked
36 | }
37 |
38 | /**
39 | * @type {string}
40 | */
41 | get title() {
42 | return this.title_
43 | }
44 |
45 | /**
46 | * @type {boolean}
47 | */
48 | get open() {
49 | return this.open_
50 | }
51 |
52 | /**
53 | * @type {boolean}
54 | */
55 | get locked() {
56 | return this.locked_
57 | }
58 |
59 | /**
60 | * @param {PropDescriptor} prop_descriptor
61 | * @param {VegaPropertyOutputState} vega_property_output_state
62 | */
63 | materializeProperty(prop_descriptor, vega_property_output_state) {
64 | vega_property_output_state.addLegendForProperty(prop_descriptor.prop_name, {
65 | title: this.title_,
66 | open: this.open_,
67 | locked: this.locked_
68 | })
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Mark/CrossSectionTerrainConfigDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import ConfigDefinitionInterface from "../Config/ConfigDefinitionInterface"
2 |
3 | export default class CrossSectionTerrainConfigDefinitionObject extends ConfigDefinitionInterface {
4 | // eslint-disable-next-line no-undef
5 | static key = "lines"
6 |
7 | // eslint-disable-next-line no-undef
8 | static defaults = {
9 | strokeColor: "black",
10 | strokeWidth: 1,
11 | fillBelowLine: 1
12 | }
13 |
14 | /**
15 | * @param {Object} definition_object
16 | * @param {ParentInfo} parent_info
17 | */
18 | constructor(definition_object, parent_info) {
19 | super(definition_object, parent_info)
20 |
21 | this.strokeColor_ =
22 | CrossSectionTerrainConfigDefinitionObject.defaults.strokeColor
23 | if (Object.hasOwn(definition_object, "strokeColor")) {
24 | this.strokeColor_ = definition_object.strokeColor
25 | }
26 | this.strokeWidth_ =
27 | CrossSectionTerrainConfigDefinitionObject.defaults.strokeWidth
28 | if (Object.hasOwn(definition_object, "strokeWidth")) {
29 | this.strokeWidth_ = definition_object.strokeWidth
30 | }
31 | this.fillBelowLine_ =
32 | CrossSectionTerrainConfigDefinitionObject.defaults.fillBelowLine
33 | if (Object.hasOwn(definition_object, "fillBelowLine")) {
34 | this.fillBelowLine_ = definition_object.fillBelowLine
35 | }
36 | }
37 |
38 | get color() {
39 | return this.color
40 | }
41 |
42 | get fill() {
43 | return this.fill_
44 | }
45 |
46 | get strokeColor() {
47 | return this.strokeColor_
48 | }
49 |
50 | get strokeWidth() {
51 | return this.strokeWidth_
52 | }
53 |
54 | get fillBelowLine() {
55 | return this.fillBelowLine_
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Mark/MarkConfigDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import ConfigDefinitionInterface from "../Config/ConfigDefinitionInterface"
2 |
3 | export default class MarkConfigDefinitionObject extends ConfigDefinitionInterface {
4 | // eslint-disable-next-line no-undef
5 | static key = "mark"
6 |
7 | // eslint-disable-next-line no-undef
8 | static defaults = {
9 | color: "#4682b4",
10 | opacity: 1,
11 | fillOpacity: 1,
12 | strokeOpacity: 1,
13 | strokeJoin: "miter"
14 | }
15 |
16 | /**
17 | * @param {Object} definition_object
18 | * @param {ParentInfo} parent_info
19 | */
20 | constructor(definition_object, parent_info) {
21 | super(definition_object, parent_info)
22 | this.width_ = MarkConfigDefinitionObject.defaults.width
23 | if (Object.hasOwn(definition_object, "width")) {
24 | this.width_ = definition_object.width
25 | }
26 | this.height_ = MarkConfigDefinitionObject.defaults.height
27 | if (Object.hasOwn(definition_object, "height")) {
28 | this.height_ = definition_object.height
29 | }
30 | this.color_ = MarkConfigDefinitionObject.defaults.color
31 | if (Object.hasOwn(definition_object, "color")) {
32 | this.color_ = definition_object.color
33 | }
34 | this.fill_ = MarkConfigDefinitionObject.defaults.fill
35 | if (Object.hasOwn(definition_object, "fill")) {
36 | this.fill_ = definition_object.fill
37 | }
38 | this.stroke_ = MarkConfigDefinitionObject.defaults.stroke
39 | if (Object.hasOwn(definition_object, "stroke")) {
40 | this.stroke_ = definition_object.stroke
41 | }
42 | this.opacity_ = MarkConfigDefinitionObject.defaults.opacity
43 | if (Object.hasOwn(definition_object, "opacity")) {
44 | this.opacity_ = definition_object.opacity
45 | }
46 | this.fillOpacity_ = MarkConfigDefinitionObject.defaults.fillOpacity
47 | if (Object.hasOwn(definition_object, "fillOpacity")) {
48 | this.fillOpacity_ = definition_object.fillOpacity
49 | }
50 | this.strokeOpacity_ = MarkConfigDefinitionObject.defaults.strokeOpacity
51 | if (Object.hasOwn(definition_object, "strokeOpacity")) {
52 | this.strokeOpacity_ = definition_object.strokeOpacity
53 | }
54 | // eslint-disable-next-line no-warning-comments
55 | // TODO(croot): make stroke join an enum
56 | this.strokeJoin_ = MarkConfigDefinitionObject.defaults.strokeJoin
57 | if (Object.hasOwn(definition_object, "strokeJoin")) {
58 | this.strokeJoin_ = definition_object.strokeJoin
59 | }
60 | this.strokeMiterLimit_ =
61 | MarkConfigDefinitionObject.defaults.strokeMiterLimit
62 | if (Object.hasOwn(definition_object, "strokeMiterLimit")) {
63 | this.strokeMiterLimit_ = definition_object.strokeMiterLimit
64 | }
65 | this.strokeWidth_ = MarkConfigDefinitionObject.defaults.strokeWidth
66 | if (Object.hasOwn(definition_object, "strokeWidth")) {
67 | this.strokeWidth_ = definition_object.strokeWidth
68 | }
69 | }
70 |
71 | get width() {
72 | return this.width_
73 | }
74 |
75 | get height() {
76 | return this.height_
77 | }
78 |
79 | get color() {
80 | return this.color_
81 | }
82 |
83 | get fill() {
84 | return this.fill_
85 | }
86 |
87 | get stroke() {
88 | return this.stroke_
89 | }
90 |
91 | get opacity() {
92 | return this.opacity_
93 | }
94 |
95 | get fillOpacity() {
96 | return this.fillOpacity_
97 | }
98 |
99 | get strokeOpacity() {
100 | return this.strokeOpacity_
101 | }
102 |
103 | get strokeJoin() {
104 | return this.strokeJoin_
105 | }
106 |
107 | get strokeMiterLimit() {
108 | return this.strokeMiterLimit_
109 | }
110 |
111 | get strokeWidth() {
112 | return this.strokeWidth_
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Mark/MarkDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import PropertiesDefinitionInterface from "../PropertiesDefinitionInterface"
2 | import assert from "assert"
3 |
4 | export default class MarkDefinitionObject extends PropertiesDefinitionInterface {
5 | /**
6 | * @param {PropDescriptor} prop_descriptor
7 | * @param {VegaPropertyOutputState} vega_property_output_state
8 | */
9 | materializeProperty(prop_descriptor, vega_property_output_state) {
10 | assert(this.hasProperty(prop_descriptor.prop_name))
11 | const prop_definition = this.definition_object_[prop_descriptor.prop_name]
12 | if (prop_descriptor.isValidMarkDefinition(prop_definition)) {
13 | const vega_mark_property_object = {}
14 | const materialized_vega_object = prop_descriptor.materializeMarkDefinitionForVega(
15 | prop_definition
16 | )
17 | prop_descriptor.vega_mark_prop_names.forEach(vega_mark_prop_name => {
18 | vega_mark_property_object[
19 | vega_mark_prop_name
20 | ] = materialized_vega_object
21 | })
22 | vega_property_output_state.addMarkProperty(
23 | prop_descriptor.prop_name,
24 | vega_mark_property_object
25 | )
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Mark/Mesh2dConfigDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import ConfigDefinitionInterface from "../Config/ConfigDefinitionInterface"
2 |
3 | export default class Mesh2dConfigDefinitionObject extends ConfigDefinitionInterface {
4 | // eslint-disable-next-line no-undef
5 | static key = "mesh2d"
6 |
7 | // eslint-disable-next-line no-undef
8 | static defaults = {
9 | color: "#4682b4",
10 | opacity: 1,
11 | fillOpacity: 1
12 | }
13 |
14 | /**
15 | * @param {Object} definition_object
16 | * @param {ParentInfo} parent_info
17 | */
18 | constructor(definition_object, parent_info) {
19 | super(definition_object, parent_info)
20 |
21 | this.color_ = Mesh2dConfigDefinitionObject.defaults.color
22 | if (Object.hasOwn(definition_object, "color")) {
23 | this.color_ = definition_object.color
24 | }
25 | this.fill_ = Mesh2dConfigDefinitionObject.defaults.fill
26 | if (Object.hasOwn(definition_object, "fill")) {
27 | this.fill_ = definition_object.fill
28 | }
29 | this.opacity_ = Mesh2dConfigDefinitionObject.defaults.opacity
30 | if (Object.hasOwn(definition_object, "opacity")) {
31 | this.opacity_ = definition_object.opacity
32 | }
33 | this.fillOpacity_ = Mesh2dConfigDefinitionObject.defaults.fillOpacity
34 | if (Object.hasOwn(definition_object, "fillOpacity")) {
35 | this.fillOpacity_ = definition_object.fillOpacity
36 | }
37 | }
38 |
39 | get color() {
40 | return this.color
41 | }
42 |
43 | get fill() {
44 | return this.fill_
45 | }
46 |
47 | get opacity() {
48 | return this.opacity_
49 | }
50 |
51 | get fillOpacity() {
52 | return this.fillOpacity_
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Mark/WindBarbConfigDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import MarkConfigDefinitionObject from "./MarkConfigDefinitionObject"
2 |
3 | export default class WindBarbConfigDefinitionObject extends MarkConfigDefinitionObject {
4 | // eslint-disable-next-line no-undef
5 | static key = "windbarb"
6 |
7 | // eslint-disable-next-line no-undef
8 | static defaults = Object.assign(
9 | {
10 | size: 25
11 | },
12 | MarkConfigDefinitionObject.defaults
13 | )
14 |
15 | /**
16 | * @param {Object} definition_object
17 | * @param {ParentInfo} parent_info
18 | */
19 | constructor(definition_object, parent_info) {
20 | super(definition_object, parent_info)
21 |
22 | this.size_ = 25
23 | if (Object.hasOwn(definition_object, "size")) {
24 | this.size_ = definition_object.size
25 | }
26 |
27 | this.quantizeDirection_ =
28 | WindBarbConfigDefinitionObject.defaults.quantizeDirection
29 | if (Object.hasOwn(definition_object, "quantizeDirection")) {
30 | this.quantizeDirection_ = definition_object.quantizeDirection
31 | }
32 |
33 | this.anchorScale_ = WindBarbConfigDefinitionObject.defaults.anchorScale
34 | if (Object.hasOwn(definition_object, "anchorScale")) {
35 | this.anchorScale_ = definition_object.anchorScale
36 | }
37 | }
38 |
39 | get size() {
40 | return this.size_
41 | }
42 |
43 | get quantizeDirection() {
44 | return this.quantizeDirection_
45 | }
46 |
47 | get anchorScale() {
48 | return this.anchorScale_
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/PropertiesDefinitionInterface.js:
--------------------------------------------------------------------------------
1 | import assert from "assert"
2 |
3 | import PropertyDefinition from "./PropertyDefinition"
4 |
5 | /**
6 | * @typedef ParentInfo
7 | * @property {PropertiesDefinitionInterface} parent
8 | * @property {string} prop_name
9 | */
10 |
11 | export default class PropertiesDefinitionInterface {
12 | /**
13 | * @param {Object} definition_object
14 | * @param {ParentInfo} [parent_info=null]
15 | * @param {RasterLayerContext} [root_context = null]
16 | */
17 | constructor(definition_object, parent_info = null, root_context = null) {
18 | assert(typeof definition_object === "object")
19 | assert(parent_info === null || root_context === null)
20 | this.parent_info_ = parent_info
21 | this.root_context_ = root_context
22 | this.definition_object_ = definition_object
23 | this.error_msg_ = ""
24 | }
25 |
26 | /**
27 | * @type {string}
28 | */
29 | get error_message() {
30 | return this.error_msg_
31 | }
32 |
33 | set error_message(error_msg) {
34 | this.error_msg_ = error_msg
35 | }
36 |
37 | /**
38 | * @returns {boolean}
39 | */
40 | hasError() {
41 | return this.error_message.length > 0
42 | }
43 |
44 | /**
45 | * @returns {PropertiesDefinitionInterface}
46 | */
47 | get parent() {
48 | return this.parent_info_ ? this.parent_info_.parent : null
49 | }
50 |
51 | /**
52 | * @type {RasterLayerContext}
53 | */
54 | get root_context() {
55 | if (this.root_context_) {
56 | return this.root_context_
57 | }
58 | assert(this.parent_info_)
59 | return this.parent_info_.parent.root_context
60 | }
61 |
62 | /**
63 | *
64 | * @param {string} prop_name
65 | * @returns boolean
66 | */
67 | hasProperty(prop_name) {
68 | return Object.hasOwn(this.definition_object_, prop_name)
69 | }
70 |
71 | /**
72 | * @returns {PropertyDefinition}
73 | */
74 | getPropertyDefinition(prop_name) {
75 | if (!this.hasProperty(prop_name)) {
76 | return null
77 | }
78 | return new PropertyDefinition(prop_name, this.definition_object_[prop_name])
79 | }
80 |
81 | /**
82 | * @param {VegaPropertyOutputState} vega_property_output_state
83 | */
84 | // eslint-disable-next-line no-unused-vars
85 | materialize(vega_property_output_state) {
86 | assert(false, `Needs to be overwritten by a derived class`)
87 | }
88 |
89 | /**
90 | * @param {PropDescriptor} prop_descriptor
91 | * @param {VegaPropertyOutputState} vega_property_output_state
92 | */
93 | // eslint-disable-next-line no-unused-vars
94 | materializeProperty(prop_descriptor, vega_property_output_state) {
95 | assert(false, `Needs to be overwritten by a derived class`)
96 | }
97 |
98 | /**
99 | * After all the vega properties/state have been materialized, there is one last pass/opportunity
100 | * for definition objects to realign with the prebuilt state for whatever purpose. Note:
101 | * realignment is done in a FIFO queue.
102 | * @param {Map} prop_descriptors
103 | * @param {VegaPropertyOutputState} vega_property_output_state
104 | */
105 | // eslint-disable-next-line no-unused-vars
106 | realign(prop_descriptors, vega_property_output_state) {}
107 | }
108 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/PropertyDefinition.js:
--------------------------------------------------------------------------------
1 | export default class PropertyDefinition {
2 | /**
3 | *
4 | * @param {string} prop_name
5 | * @param {Object} prop_definition
6 | */
7 | constructor(prop_name, prop_definition) {
8 | this._prop_name = prop_name
9 | this._prop_definition = prop_definition
10 | }
11 |
12 | /**
13 | * @returns {string}
14 | */
15 | get prop_name() {
16 | return this._prop_name
17 | }
18 |
19 | /**
20 | * @returns {object}
21 | */
22 | get prop_definition() {
23 | return this._prop_definition
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Transform/TransformDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import PropertiesDefinitionInterface from "../PropertiesDefinitionInterface"
2 | import SampleDefinitionObject from "../Transform/Transforms/SampleDefinitionObject"
3 | import LimitDefinitionObject from "../Transform/Transforms/LimitDefinitionObject"
4 | import RasterMesh2dDefinitionObject from "../Transform/Transforms/RasterMesh2dDefinitionObject"
5 | import CrossSection2dDefinitionObject from "../Transform/Transforms/CrossSection2dDefinitionObject"
6 | import CrossSectionTerrainDefinitionObject from "../Transform/Transforms/CrossSectionTerrainDefinitionObject"
7 |
8 | export default class TransformDefinitionObject extends PropertiesDefinitionInterface {
9 | /**
10 | * @param {Object} definition_object
11 | * @param {RasterLayerContext} root_context
12 | */
13 | constructor(definition_object, root_context) {
14 | super(definition_object, null, root_context)
15 |
16 | if (!Array.isArray(definition_object)) {
17 | throw new Error(
18 | `Invalid transform definition. It must be an array of objects`
19 | )
20 | }
21 |
22 | /**
23 | * @type {PropertiesDefinitionInterface[]}
24 | */
25 | this.transforms_ = []
26 | definition_object.forEach((xform_definition, index) => {
27 | if (typeof xform_definition !== "object") {
28 | throw new Error(
29 | `Invalid transform definition at index ${index}. It must be an object but is of type ${typeof xform_definition}`
30 | )
31 | }
32 | if (Object.hasOwn(xform_definition, SampleDefinitionObject.key)) {
33 | this.transforms_.push(
34 | new SampleDefinitionObject(xform_definition, {
35 | parent: this,
36 | prop_name: `${index}/${SampleDefinitionObject.key}`
37 | })
38 | )
39 | } else if (Object.hasOwn(xform_definition, LimitDefinitionObject.key)) {
40 | this.transforms_.push(
41 | new LimitDefinitionObject(xform_definition, {
42 | parent: this,
43 | prop_name: `${index}/${LimitDefinitionObject.key}`
44 | })
45 | )
46 | } else if (
47 | Object.hasOwn(xform_definition, RasterMesh2dDefinitionObject.key)
48 | ) {
49 | this.transforms_.push(
50 | new RasterMesh2dDefinitionObject(xform_definition, {
51 | parent: this,
52 | prop_name: `${index}/${RasterMesh2dDefinitionObject.key}`
53 | })
54 | )
55 | } else if (
56 | Object.hasOwn(xform_definition, CrossSection2dDefinitionObject.key)
57 | ) {
58 | this.transforms_.push(
59 | new CrossSection2dDefinitionObject(xform_definition, {
60 | parent: this,
61 | prop_name: `${index}/${CrossSection2dDefinitionObject.key}`
62 | })
63 | )
64 | } else if (
65 | Object.hasOwn(xform_definition, CrossSectionTerrainDefinitionObject.key)
66 | ) {
67 | this.transforms_.push(
68 | new CrossSectionTerrainDefinitionObject(xform_definition, {
69 | parent: this,
70 | prop_name: `${index}/${CrossSectionTerrainDefinitionObject.key}`
71 | })
72 | )
73 | } else {
74 | throw new Error(
75 | `Invalid transform object ${JSON.stringify(
76 | xform_definition
77 | )} at index ${index}`
78 | )
79 | }
80 | })
81 | }
82 |
83 | /**
84 | * @param {VegaPropertyOutputState} vega_property_output_state
85 | */
86 | materialize(vega_property_output_state) {
87 | this.transforms_.forEach(transform => {
88 | transform.materialize(vega_property_output_state)
89 | })
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Transform/Transforms/LimitDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import PropertiesDefinitionInterface from "../../PropertiesDefinitionInterface"
2 | import assert from "assert"
3 |
4 | export default class LimitDefinitionObject extends PropertiesDefinitionInterface {
5 | // eslint-disable-next-line no-undef
6 | static key = "limit"
7 |
8 | /**
9 | * @param {Object} definition_object
10 | * @param {ParentInfo} parent_info
11 | */
12 | constructor(definition_object, parent_info) {
13 | super(definition_object, parent_info)
14 | assert(Object.hasOwn(definition_object, LimitDefinitionObject.key))
15 |
16 | /**
17 | * @type {number}
18 | */
19 | this.limit_ = 0
20 | if (typeof definition_object[LimitDefinitionObject.key] !== "number") {
21 | throw new Error(
22 | `Invalid limit transform definition. The '${LimitDefinitionObject.key}' property must be a number`
23 | )
24 | }
25 | this.limit_ = definition_object[LimitDefinitionObject.key]
26 | // eslint-disable-next-line no-warning-comments
27 | // TODO(croot): validate limit ranges?
28 |
29 | /**
30 | * @type {number}
31 | */
32 | this.offset_ = 0
33 | if (Object.hasOwn(definition_object, "offset")) {
34 | if (typeof definition_object.offset !== "number") {
35 | throw new Error(
36 | `Invalid limit transform definition. The 'offset' property must be a number`
37 | )
38 | }
39 | this.offset_ = definition_object.offset
40 | // eslint-disable-next-line no-warning-comments
41 | // TODO(croot): validate tableSize?
42 | }
43 | }
44 |
45 | /**
46 | * @type {number}
47 | */
48 | get limit() {
49 | return this.limit_
50 | }
51 |
52 | /**
53 | * @type {number}
54 | */
55 | get offset() {
56 | return this.offset_
57 | }
58 |
59 | /**
60 | * @param {VegaPropertyOutputState} vega_property_output_state
61 | */
62 | materialize(vega_property_output_state) {
63 | const layer_name = this.root_context.layer_name
64 | const transform_obj = {
65 | type: "limit",
66 | row: this.limit_
67 | }
68 | if (this.offset_) {
69 | transform_obj.offset = this.offset_
70 | }
71 | vega_property_output_state.addSqlParserTransform(
72 | `${layer_name}_${LimitDefinitionObject.key}`,
73 | transform_obj
74 | )
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/Definitions/Transform/Transforms/SampleDefinitionObject.js:
--------------------------------------------------------------------------------
1 | import PropertiesDefinitionInterface from "../../PropertiesDefinitionInterface"
2 | import assert from "assert"
3 |
4 | export default class SampleDefinitionObject extends PropertiesDefinitionInterface {
5 | // eslint-disable-next-line no-undef
6 | static key = "sample"
7 |
8 | /**
9 | * @param {Object} definition_object
10 | * @param {ParentInfo} parent_info
11 | */
12 | constructor(definition_object, parent_info) {
13 | super(definition_object, parent_info)
14 | assert(Object.hasOwn(definition_object, SampleDefinitionObject.key))
15 |
16 | /**
17 | * @type {number}
18 | */
19 | this.sample_ = 0
20 | if (typeof definition_object[SampleDefinitionObject.key] !== "number") {
21 | throw new Error(
22 | `Invalid sample transform definition. The '${SampleDefinitionObject.key}' property must be a number`
23 | )
24 | }
25 | this.sample_ = definition_object[SampleDefinitionObject.key]
26 | // eslint-disable-next-line no-warning-comments
27 | // TODO(croot): validate sample ranges?
28 |
29 | if (!Object.hasOwn(definition_object, "tableSize")) {
30 | throw new Error(
31 | `A 'tableSize' property is required for sample transforms`
32 | )
33 | }
34 |
35 | if (typeof definition_object.tableSize !== "number") {
36 | throw new Error(
37 | `Invalid sample transform definition. The 'tableSize' property must be a number`
38 | )
39 | }
40 | this.table_size_ = definition_object.tableSize
41 | // eslint-disable-next-line no-warning-comments
42 | // TODO(croot): validate tableSize?
43 | }
44 |
45 | /**
46 | * @type {number}
47 | */
48 | get sample_size() {
49 | return this.sample_
50 | }
51 |
52 | /**
53 | * @type {number}
54 | */
55 | get table_size() {
56 | return this.table_size_
57 | }
58 |
59 | /**
60 | * @param {VegaPropertyOutputState} vega_property_output_state
61 | */
62 | materialize(vega_property_output_state) {
63 | const layer_name = this.root_context.layer_name
64 | const table_size = this.root_context.last_filtered_size || this.table_size_
65 | vega_property_output_state.addSqlParserTransform(
66 | `${layer_name}_${SampleDefinitionObject.key}`,
67 | {
68 | type: "sample",
69 | method: "multiplicative",
70 | size: table_size,
71 | limit: this.sample_,
72 | sampleTable: this.root_context.table_name
73 | }
74 | )
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/BaseTypes/BoolPropDescriptor.js:
--------------------------------------------------------------------------------
1 | import { default as PropDescriptor, MeasurementType } from "../PropDescriptor"
2 |
3 | export default class BoolPropDescriptor extends PropDescriptor {
4 | /**
5 | * @param {boolean} prop_definition
6 | */
7 | isValidMarkDefinition(prop_definition) {
8 | if (typeof prop_definition !== "boolean") {
9 | throw new Error(`Invalid value ${prop_definition}. It must be a boolean`)
10 | }
11 | return true
12 | }
13 |
14 | /**
15 | * @returns {MeasurementType}
16 | */
17 | getDefaultMeasurementType() {
18 | return MeasurementType.nominal
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/BaseTypes/NumericPropDescriptor.js:
--------------------------------------------------------------------------------
1 | import {
2 | default as PropDescriptor,
3 | PropLocation,
4 | MeasurementType
5 | } from "../PropDescriptor"
6 |
7 | export { PropDescriptor, PropLocation, MeasurementType }
8 | export default class NumericPropDescriptor extends PropDescriptor {
9 | /**
10 | * @param {string} prop_name
11 | * @param {string} [vega_prop_name=null]
12 | * @param {PropLocation} [prop_location=PropLocation.kEncodingPlusMarkDef]
13 | * @param {PropDescriptor} [fallback_prop=null]
14 | * @param {boolean} [can_have_scale_definition=true]
15 | * @param {Function} [validate_value_callback=null]
16 | */
17 | constructor(
18 | prop_name,
19 | vega_prop_name = null,
20 | prop_location = PropLocation.kEncodingPlusMarkDef,
21 | fallback_prop = null,
22 | can_have_scale_definition = true,
23 | validate_value_callback = null
24 | ) {
25 | super(
26 | prop_name,
27 | vega_prop_name,
28 | prop_location,
29 | fallback_prop,
30 | can_have_scale_definition
31 | )
32 | this.validate_value_callback_ = validate_value_callback
33 | }
34 |
35 | /**
36 | * @param {(string|number|boolean)} prop_definition
37 | */
38 | isValidMarkDefinition(prop_definition) {
39 | if (typeof prop_definition !== "number") {
40 | throw new Error(`Invalid value ${prop_definition}. It must be a number`)
41 | } else if (
42 | this.validate_value_callback_ &&
43 | !this.validate_value_callback_(prop_definition)
44 | ) {
45 | throw new Error(`Invalid value ${prop_definition}`)
46 | }
47 | return true
48 | }
49 |
50 | /**
51 | * @returns {MeasurementType}
52 | */
53 | getDefaultMeasurementType() {
54 | return MeasurementType.kQuantitative
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/BaseTypes/NumericPropValidators.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns true if value >= 0
3 | * @param {number} value
4 | * @returns {boolean}
5 | */
6 | const is_gte_zero = value => value >= 0
7 |
8 | /**
9 | * Returns true if 0 <= value <= 1
10 | * @param {number} value
11 | * @returns {boolean}
12 | */
13 | const is_zero_to_one = value => value >= 0 && value <= 1
14 |
15 | export { is_gte_zero, is_zero_to_one }
16 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/BaseTypes/StringPropDescriptor.js:
--------------------------------------------------------------------------------
1 | import { default as PropDescriptor, MeasurementType } from "../PropDescriptor"
2 |
3 | export default class StringPropDescriptor extends PropDescriptor {
4 | /**
5 | * @param {boolean} prop_definition
6 | */
7 | isValidMarkDefinition(prop_definition) {
8 | if (typeof prop_definition !== "string") {
9 | throw new Error(`Invalid value ${prop_definition}. It must be a string`)
10 | }
11 | return true
12 | }
13 |
14 | /**
15 | * @returns {MeasurementType}
16 | */
17 | getDefaultMeasurementType() {
18 | return MeasurementType.nominal
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/CommonChannelDescriptors.js:
--------------------------------------------------------------------------------
1 | export { default as ColorChannelDescriptor } from "./CommonChannels/ColorChannelDescriptor"
2 | export { default as GeographicChannelDescriptor } from "./CommonChannels/GeographicChannelDescriptor"
3 | export { default as OpacityChannelDescriptor } from "./CommonChannels/OpacityChannelDescriptor"
4 | export { default as PositionChannelDescriptor } from "./CommonChannels/PositionChannelDescriptor"
5 | export { default as SizeChannelDescriptor } from "./CommonChannels/SizeChannelDescriptor"
6 | export {
7 | default as PropDescriptor,
8 | PropLocation,
9 | MeasurementType
10 | } from "./PropDescriptor"
11 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/CommonChannels/ColorChannelDescriptor.js:
--------------------------------------------------------------------------------
1 | import {
2 | default as PropDescriptor,
3 | PropLocation,
4 | MeasurementType
5 | } from "../PropDescriptor"
6 |
7 | export default class ColorChannelDescriptor extends PropDescriptor {
8 | /**
9 | * @param {string} prop_name
10 | * @param {string} [vega_prop_name=null]
11 | * @param {ColorChannelDescriptor} [color_channel_fallback=null]
12 | */
13 | constructor(prop_name, vega_prop_name = null, color_channel_fallback = null) {
14 | super(
15 | prop_name,
16 | vega_prop_name,
17 | PropLocation.kEncodingPlusMarkDef,
18 | color_channel_fallback,
19 | true
20 | )
21 | }
22 |
23 | /**
24 | * @param {MeasurementType} measurement_type
25 | */
26 | validateMeasurementType(measurement_type) {
27 | switch (measurement_type) {
28 | case MeasurementType.kQuantitative:
29 | case MeasurementType.kOrdinal:
30 | case MeasurementType.kNominal:
31 | break
32 | default:
33 | throw new Error(
34 | `The measurement type ${measurement_type} is an invalid measurement type for color channel '${
35 | this.prop_name
36 | }'. Color channels only accept ${[
37 | `${MeasurementType.kQuantitative}`,
38 | `${MeasurementType.kOrdinal}`,
39 | `${MeasurementType.kNominal}`
40 | ]} measurement types.`
41 | )
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/CommonChannels/GeographicChannelDescriptor.js:
--------------------------------------------------------------------------------
1 | import assert from "assert"
2 |
3 | import {
4 | default as PropDescriptor,
5 | PropLocation,
6 | MeasurementType
7 | } from "../PropDescriptor"
8 |
9 | // jsdoc imports only
10 | // eslint-disable-next-line no-unused-vars
11 | import RasterLayerContext from "../../RasterLayerContext"
12 |
13 | export default class GeographicChannelDescriptor extends PropDescriptor {
14 | /**
15 | * @param {string} prop_name
16 | * @param {string} [vega_prop_name=null]
17 | */
18 | constructor(prop_name, vega_prop_name = null) {
19 | super(prop_name, vega_prop_name, PropLocation.kEncodingOnly, null, false)
20 | }
21 |
22 | /**
23 | * Validates the property descriptor with the active context
24 | * Should throw an error if validation fails
25 | * @param {RasterLayerContext} raster_layer_context
26 | */
27 | validateContext(raster_layer_context) {
28 | if (!raster_layer_context.chart.useGeoTypes()) {
29 | throw new Error(
30 | `Cannot use the Geographic property '${this.prop_name}' with the raster chart as the raster chart is not configured to use geographic data`
31 | )
32 | }
33 | }
34 |
35 | isValidMarkDefinition() {
36 | assert(false, "This should never be called")
37 | }
38 |
39 | /**
40 | * @param {ValueDefinitionObject} value_definition_object
41 | * @returns {boolean}
42 | */
43 | isValidValueDefinition(value_definition_object) {
44 | // position channels currently do not support value defs, only fields.
45 | value_definition_object.error_message = `Geographic channels do not currently support value definitions.`
46 | return false
47 | }
48 |
49 | /**
50 | * @param {MeasurementType} measurement_type
51 | */
52 | validateMeasurementType(measurement_type) {
53 | if (measurement_type !== MeasurementType.kQuantitative) {
54 | throw new Error(
55 | `The measurement type ${measurement_type} is an invalid measurement type for geographic channel '${this.prop_name}'. Geographic channels only accept ${MeasurementType.kQuantitative} measurement types.`
56 | )
57 | }
58 | }
59 |
60 | /**
61 | * @returns {MeasurementType}
62 | */
63 | getDefaultMeasurementType() {
64 | return MeasurementType.kQuantitative
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/CommonChannels/OpacityChannelDescriptor.js:
--------------------------------------------------------------------------------
1 | import {
2 | default as NumericPropDescriptor,
3 | PropLocation
4 | } from "../BaseTypes/NumericPropDescriptor"
5 | import { is_zero_to_one } from "../BaseTypes/NumericPropValidators"
6 |
7 | export default class OpacityChannelDescriptor extends NumericPropDescriptor {
8 | /**
9 | * @param {string} prop_name
10 | * @param {string} [vega_prop_name=null]
11 | * @param {PropLocation} [prop_location=PropLocation.kEncodingPlusMarkDef]
12 | * @param {PropDescriptor} [fallback_prop=null]
13 | * @param {boolean} [can_have_scale_definition=true]
14 | */
15 | constructor(
16 | prop_name,
17 | vega_prop_name = null,
18 | prop_location = PropLocation.kEncodingPlusMarkDef,
19 | fallback_prop = null,
20 | can_have_scale_definition = true
21 | ) {
22 | super(
23 | prop_name,
24 | vega_prop_name,
25 | prop_location,
26 | fallback_prop,
27 | can_have_scale_definition,
28 | is_zero_to_one
29 | )
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/CommonChannels/PositionChannelDescriptor.js:
--------------------------------------------------------------------------------
1 | import {
2 | default as PropDescriptor,
3 | PropLocation,
4 | MeasurementType
5 | } from "../PropDescriptor"
6 | import assert from "assert"
7 |
8 | export default class PositionChannelDescriptor extends PropDescriptor {
9 | /**
10 | * @param {string} prop_name
11 | * @param {string} [vega_prop_name=null]
12 | */
13 | constructor(prop_name, vega_prop_name = null) {
14 | super(prop_name, vega_prop_name, PropLocation.kEncodingOnly, null, true)
15 | }
16 |
17 | isValidMarkDefinition() {
18 | assert(false, "This should never be called")
19 | }
20 |
21 | /**
22 | * @param {ValueDefinitionObject} value_definition_object
23 | * @returns {boolean}
24 | */
25 | isValidValueDefinition(value_definition_object) {
26 | // position channels currently do not support value defs, only fields.
27 | value_definition_object.error_message = `Position channels do not currently support value definitions.`
28 | return false
29 | }
30 |
31 | /**
32 | * @param {MeasurementType} measurement_type
33 | */
34 | validateMeasurementType(measurement_type) {
35 | if (measurement_type !== MeasurementType.kQuantitative) {
36 | throw new Error(
37 | `The measurement type ${measurement_type} is an invalid measurement type for position channel '${this.prop_name}'. Position channels only accept ${MeasurementType.kQuantitative} measurement types.`
38 | )
39 | }
40 | }
41 |
42 | /**
43 | * @returns {MeasurementType}
44 | */
45 | getDefaultMeasurementType() {
46 | return MeasurementType.kQuantitative
47 | }
48 |
49 | /**
50 | * @param {ParentInfo} parent_info
51 | * @returns {Object}
52 | */
53 | buildDefaultScaleDefinition(parent_info) {
54 | const chart = parent_info.parent.root_context.chart
55 | const layer = parent_info.parent.root_context.layer
56 | let scale_function_name = `_get${this.prop_name}ScaleName`
57 |
58 | // added check for get..ScaleName on layer level, as cross section terrain
59 | // needs to create a separate y scale from cross section when multi-layered
60 | if (
61 | typeof chart[scale_function_name] !== "function" &&
62 | typeof layer[scale_function_name] !== "function"
63 | ) {
64 | scale_function_name = `_get${this.prop_name.toUpperCase()}ScaleName`
65 | assert(
66 | typeof chart[scale_function_name] === "function" ||
67 | typeof layer[scale_function_name] === "function"
68 | )
69 | }
70 |
71 | if (typeof layer[scale_function_name] === "function") {
72 | return { name: layer[scale_function_name](), type: "internal-passthru" }
73 | } else {
74 | return { name: chart[scale_function_name](), type: "internal-passthru" }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/CommonChannels/SizeChannelDescriptor.js:
--------------------------------------------------------------------------------
1 | import {
2 | default as NumericPropDescriptor,
3 | PropLocation
4 | } from "../BaseTypes/NumericPropDescriptor"
5 | import { is_gte_zero } from "../BaseTypes/NumericPropValidators"
6 |
7 | export default class SizeChannelDescriptor extends NumericPropDescriptor {
8 | /**
9 | * @param {string} prop_name
10 | * @param {string} [vega_prop_name=null]
11 | * @param {PropLocation} [prop_location=PropLocation.kEncodingPlusMarkDef]
12 | * @param {PropDescriptor} [fallback_prop=null]
13 | * @param {boolean} [can_have_scale_definition=true]
14 | */
15 | constructor(
16 | prop_name,
17 | vega_prop_name = null,
18 | prop_location = PropLocation.kEncodingPlusMarkDef,
19 | fallback_prop = null,
20 | can_have_scale_definition = true
21 | ) {
22 | super(
23 | prop_name,
24 | vega_prop_name,
25 | prop_location,
26 | fallback_prop,
27 | can_have_scale_definition,
28 | is_gte_zero
29 | )
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/Enums/MeasurementType.js:
--------------------------------------------------------------------------------
1 | // NOTE: need to disable no-undef due to an issue with the rather outdated
2 | // nature of the babel-eslint parser. The parser does to properly account
3 | // for static class members
4 |
5 | /* eslint-disable no-undef */
6 |
7 | export default class MeasurementType {
8 | static val_to_enum_map_ = {}
9 |
10 | static kQuantitative = new MeasurementType("quantitative")
11 | static kTemporal = new MeasurementType("temporal")
12 | static kOrdinal = new MeasurementType("ordinal")
13 | static kNominal = new MeasurementType("nominal")
14 |
15 | /**
16 | *
17 | * @param {string} measurement_type
18 | */
19 | static getMeasurementTypeFromString(measurement_type) {
20 | const rtn_obj =
21 | MeasurementType.val_to_enum_map_[measurement_type.toLowerCase()]
22 | if (typeof rtn_obj === "undefined") {
23 | throw new Error(
24 | `Invalid measurement type string '${measurement_type}'. It must be one of ${Object.keys(
25 | MeasurementType.val_to_enum_map_
26 | )}`
27 | )
28 | }
29 | return rtn_obj
30 | }
31 |
32 | constructor(value) {
33 | this.value = value
34 | MeasurementType.val_to_enum_map_[this.value] = this
35 | }
36 |
37 | valueOf() {
38 | return this.value
39 | }
40 |
41 | toString() {
42 | return this.value
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/PropDescriptor/Enums/PropLocation.js:
--------------------------------------------------------------------------------
1 | export default class PropLocation {
2 | // NOTE: the below no-undef disabling is due to the outdated nature of
3 | // the babel-eslint package and its apparent inappropriate handling of.
4 | // static member variables
5 |
6 | // eslint-disable-next-line no-undef
7 | static kEncodingOnly = new PropLocation(1 << 0)
8 |
9 | // eslint-disable-next-line no-undef
10 | static kMarkDefOnly = new PropLocation(1 << 1)
11 |
12 | // eslint-disable-next-line no-undef
13 | static kEncodingPlusMarkDef = new PropLocation(
14 | PropLocation.kEncodingOnly | PropLocation.kMarkDefOnly
15 | )
16 |
17 | constructor(value) {
18 | this.value = value
19 | }
20 |
21 | valueOf() {
22 | return this.value
23 | }
24 |
25 | toString() {
26 | return `${this.value}`
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/mixins/render-vega-lite/RasterLayerContext.js:
--------------------------------------------------------------------------------
1 | import assert from "assert"
2 |
3 | export default class RasterLayerContext {
4 | /**
5 | * @param {Object} chart
6 | * @param {string} table_name
7 | * @param {string} layer_type
8 | * @param {Object} layer
9 | * @param {string} layer_name
10 | * @param {(number|null)} [last_filtered_size=null]
11 | */
12 | constructor(
13 | chart,
14 | table_name,
15 | layer_type,
16 | layer,
17 | layer_name,
18 | last_filtered_size = null
19 | ) {
20 | assert(Boolean(chart))
21 | assert(typeof chart === "object")
22 |
23 | /**
24 | * @type {Object}
25 | */
26 | this.chart_ = chart
27 |
28 | assert(typeof table_name === "string")
29 | /**
30 | * @type {string}
31 | */
32 | this.table_name_ = table_name
33 |
34 | assert(typeof layer_type === "string")
35 | /**
36 | * @type {string}
37 | */
38 | this.layer_type_ = layer_type
39 |
40 | assert(Boolean(layer))
41 | assert(typeof layer === "object")
42 | /**
43 | * @type {Object}
44 | */
45 | this.layer_ = layer
46 |
47 | assert(typeof layer_name === "string")
48 | /**
49 | * @type {string}
50 | */
51 | this.layer_name_ = layer_name
52 |
53 | this.last_filtered_size_ = last_filtered_size
54 | }
55 |
56 | /**
57 | * @type {Object}
58 | */
59 | get chart() {
60 | return this.chart_
61 | }
62 |
63 | /**
64 | * @type {string}
65 | */
66 | get table_name() {
67 | return this.table_name_
68 | }
69 |
70 | /**
71 | * @type {string}
72 | */
73 | get layer_type() {
74 | return this.layer_type_
75 | }
76 |
77 | /**
78 | * @type {Object}
79 | */
80 | get layer() {
81 | return this.layer_
82 | }
83 |
84 | /**
85 | * @type {string}
86 | */
87 | get layer_name() {
88 | return this.layer_name_
89 | }
90 |
91 | /**
92 | * @type {(number|null)}
93 | */
94 | get last_filtered_size() {
95 | return this.last_filtered_size_
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/mixins/scatter-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 | import mapboxglMock from "../../test/mapbox-gl-mock"
4 |
5 | describe("Scatter Mixin", () => {
6 | describe("constructor", () => {
7 | it("should mixin a scatter", () => {
8 | dc.scatterMixin({}, mapboxglMock, false)
9 | })
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/src/mixins/spinner-mixin.js:
--------------------------------------------------------------------------------
1 | import { chartRegistry } from "../core/core"
2 | import d3 from "d3"
3 | import { SPINNER_DELAY } from "../constants/dc-constants"
4 |
5 | export const areAnySpinnersShowing = () =>
6 | chartRegistry.list().some(chart => chart.isSpinnerShowing())
7 | export const setCursorSpinner = () =>
8 | d3.select("body").classed("waiting", areAnySpinnersShowing())
9 |
10 | export default function spinnerMixin(_chart) {
11 | let _spinnerDelay = SPINNER_DELAY
12 | let _spinnerTimeout = null
13 | let _spinnerIsVisible = false
14 |
15 | _chart.isSpinnerShowing = () => _spinnerIsVisible
16 |
17 | let _dataFetchRequestCallback = () => {
18 | const anchor = _chart.anchor()
19 | const selectedAnchor = d3.select(anchor)
20 |
21 | selectedAnchor.classed("chart-loading-overlay", true)
22 |
23 | const loadingWidget = selectedAnchor
24 | .append("div")
25 | .classed("loading-widget-dc", true)
26 |
27 | loadingWidget.append("div").classed("main-loading-icon", true)
28 | }
29 |
30 | let _dataFetchSuccessfulCallback = () => {
31 | const anchor = _chart.anchor()
32 |
33 | const selectedAnchor = d3.select(anchor)
34 |
35 | selectedAnchor.classed("chart-loading-overlay", false)
36 |
37 | selectedAnchor
38 | .selectAll(function() {
39 | return [...this.childNodes].filter(
40 | node => node.className === "loading-widget-dc"
41 | )
42 | })
43 | .remove()
44 |
45 | d3.select("body").classed("waiting", areAnySpinnersShowing())
46 | }
47 |
48 | _chart.spinnerDelay = function(delay) {
49 | if (!arguments.length) {
50 | return _spinnerDelay
51 | }
52 |
53 | _spinnerDelay = delay
54 | return _chart
55 | }
56 |
57 | _chart.dataFetchSuccessfulCallback = function(func) {
58 | if (!arguments.length) {
59 | return _dataFetchSuccessfulCallback
60 | }
61 |
62 | _dataFetchSuccessfulCallback = func
63 | return _chart
64 | }
65 |
66 | _chart.dataFetchRequestCallback = function(func) {
67 | if (!arguments.length) {
68 | return _dataFetchRequestCallback
69 | }
70 |
71 | _dataFetchRequestCallback = func
72 | return _chart
73 | }
74 |
75 | function initSpinner() {
76 | if (_spinnerTimeout) {
77 | window.clearTimeout(_spinnerTimeout)
78 | }
79 |
80 | _spinnerTimeout = window.setTimeout(() => {
81 | _spinnerIsVisible = true
82 | _dataFetchRequestCallback()
83 | setCursorSpinner()
84 | }, _spinnerDelay)
85 | }
86 |
87 | function tearDownSpinner() {
88 | if (_spinnerIsVisible) {
89 | _spinnerIsVisible = false
90 | _dataFetchSuccessfulCallback()
91 | setCursorSpinner()
92 | }
93 | window.clearTimeout(_spinnerTimeout)
94 | }
95 |
96 | _chart.on("dataFetch.spinner", initSpinner)
97 |
98 | _chart.on("postRedraw.spinner", tearDownSpinner)
99 | _chart.on("postRender.spinner", tearDownSpinner)
100 |
101 | _chart.on("dataError.spinner", () => {
102 | console.log(_chart.__dcFlag__, ": error")
103 |
104 | tearDownSpinner()
105 | })
106 |
107 | return _chart
108 | }
109 |
--------------------------------------------------------------------------------
/src/mixins/spinner-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 | import { SPINNER_DELAY } from "../constants/dc-constants"
4 |
5 | describe("Spinner Mixin", () => {
6 | let chart
7 | let anchor
8 | beforeEach(() => {
9 | chart = dc.spinnerMixin(dc.asyncMixin(dc.baseMixin({})))
10 | anchor = window.document.createElement("DIV")
11 | chart.anchor(anchor)
12 | })
13 |
14 | it("default spinner delay should be the SPINNER_DELAY", () => {
15 | expect(chart.spinnerDelay()).to.equal(SPINNER_DELAY)
16 | })
17 |
18 | it("should be able to set the spinner delay", () => {
19 | chart.spinnerDelay(100)
20 | expect(chart.spinnerDelay()).to.equal(100)
21 | })
22 |
23 | it("should wait the minimum spinner delay before adding the overlay", done => {
24 | chart.spinnerDelay(10)
25 | chart._invokeDataFetchListener()
26 |
27 | expect(chart.anchor().className.includes("chart-loading-overlay")).to.equal(
28 | false
29 | )
30 |
31 | setTimeout(() => {
32 | expect(
33 | chart.anchor().className.includes("chart-loading-overlay")
34 | ).to.equal(true)
35 | expect(
36 | chart.anchor().childNodes[0].className.includes("loading-widget-dc")
37 | ).to.equal(true)
38 | done()
39 | }, chart.spinnerDelay() + 5)
40 | })
41 |
42 | it("should debounce the dataFetch and only add one spinner", done => {
43 | chart.spinnerDelay(10)
44 | chart._invokeDataFetchListener()
45 | chart._invokeDataFetchListener()
46 | chart._invokeDataFetchListener()
47 | chart._invokeDataFetchListener()
48 | chart._invokeDataFetchListener()
49 | chart._invokeDataFetchListener()
50 | chart._invokeDataFetchListener()
51 | chart._invokeDataFetchListener()
52 |
53 | expect(chart.anchor().className.includes("chart-loading-overlay")).to.equal(
54 | false
55 | )
56 |
57 | setTimeout(() => {
58 | expect(
59 | chart.anchor().className.includes("chart-loading-overlay")
60 | ).to.equal(true)
61 | expect(chart.anchor().childNodes.length).to.equal(1)
62 | done()
63 | }, chart.spinnerDelay() + 5)
64 | })
65 |
66 | it("should remove spinner once postRedraw is called", done => {
67 | chart.spinnerDelay(10)
68 | chart._invokeDataFetchListener()
69 | expect(chart.anchor().className.includes("chart-loading-overlay")).to.equal(
70 | false
71 | )
72 |
73 | setTimeout(() => {
74 | expect(
75 | chart.anchor().className.includes("chart-loading-overlay")
76 | ).to.equal(true)
77 | chart._activateRenderlets("postRedraw")
78 | expect(
79 | chart.anchor().className.includes("chart-loading-overlay")
80 | ).to.equal(false)
81 | expect(chart.anchor().childNodes.length).to.equal(0)
82 | done()
83 | }, chart.spinnerDelay() + 5)
84 | })
85 |
86 | it("can override the dataFetch request and success callback", done => {
87 | let cbVariable = null
88 |
89 | const callbackRequest = () => (cbVariable = "request")
90 | const callbackSuccess = () => (cbVariable = "success")
91 |
92 | chart.spinnerDelay(10)
93 |
94 | chart.dataFetchRequestCallback(callbackRequest)
95 | chart.dataFetchSuccessfulCallback(callbackSuccess)
96 |
97 | expect(chart.dataFetchRequestCallback()).to.equal(callbackRequest)
98 | expect(chart.dataFetchSuccessfulCallback()).to.equal(callbackSuccess)
99 |
100 | chart._invokeDataFetchListener()
101 |
102 | expect(cbVariable).to.equal(null)
103 |
104 | setTimeout(() => {
105 | expect(cbVariable).to.equal("request")
106 | chart._activateRenderlets("postRedraw")
107 | expect(cbVariable).to.equal("success")
108 | done()
109 | }, chart.spinnerDelay() + 5)
110 | })
111 |
112 | it("should clear the spinner if there is an error", () => {
113 | chart._invokeDataErrorListener()
114 |
115 | expect(chart.isSpinnerShowing()).to.equal(false)
116 | })
117 | })
118 |
--------------------------------------------------------------------------------
/src/mixins/stack-mixin.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Stack Mixin", () => {
5 | describe("constructor", () => {
6 | it("should mixin a stack chart", () => {
7 | dc.stackMixin(dc.colorMixin(dc.baseMixin({})))
8 | })
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/mixins/ui/lasso-event-constants.js:
--------------------------------------------------------------------------------
1 | export const LassoShapeEventConstants = {
2 | /**
3 | * triggered from an individual shape when a draggable edit
4 | * event starts on that shape
5 | */
6 | LASSO_SHAPE_EDIT_BEGIN: "lasso:shape:edit:begin",
7 |
8 | /**
9 | * triggered from an individual shape when a draggable edit
10 | * event completes on that shape
11 | */
12 | LASSO_SHAPE_EDIT_END: "lasso:shape:edit:end"
13 | }
14 |
15 | export const LassoGlobalEventConstants = {
16 | /**
17 | * triggered when a new lasso tool is activated by pressing its
18 | * corresponding button in the lasso tool set ui
19 | */
20 | LASSO_TOOL_TYPE_ACTIVATED: "lasso:tool:activated",
21 |
22 | /**
23 | * triggered when a lasso tool is deactivated. This can happen
24 | * when a shape finishes drawing, when a user cancels a drawing
25 | * (via esc key) or when the user clicks on its corresponding button
26 | * in the lasso tool set ui to manually deactivate it.
27 | */
28 | LASSO_TOOL_TYPE_DEACTIVATED: "lasso:tool:deactivated",
29 |
30 | /**
31 | * triggered when a new shape is being drawn via a lasso tool
32 | */
33 | LASSO_TOOL_DRAW_STARTED: "lasso:tool:draw:started",
34 |
35 | /**
36 | * triggered when a new shape draw ends for some reason,
37 | * either the shape is successfully completed, or cancelled
38 | */
39 | LASSO_TOOL_DRAW_ENDED: "lasso:tool:draw:ended",
40 |
41 | /**
42 | * triggered when a new lasso shape is created
43 | */
44 | LASSO_SHAPE_CREATE: "lasso:shape:create",
45 |
46 | /**
47 | * triggered when a lasso shape is destroyed
48 | */
49 | LASSO_SHAPE_DESTROY: "lasso:shape:destroy",
50 |
51 | /**
52 | * triggered when a draggable edit event starts.
53 | * The draggable event could be applied to one or many
54 | * shapes
55 | */
56 | LASSO_SHAPE_EDITS_BEGIN: "lasso:shape:edits:begin",
57 |
58 | /**
59 | * triggered when a draggable edit event completes.
60 | * The draggable event could be applied to one or many
61 | * shapes
62 | */
63 | LASSO_SHAPE_EDITS_END: "lasso:shape:edits:end"
64 | }
65 |
--------------------------------------------------------------------------------
/src/mixins/ui/lasso-tool-set-types.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Enum class describing the various lasso draw tools accessible via the lasso-tool-ui.
3 | * This is a bit-flag enum that allows users to define a custom tool set to use
4 | * with the lasso draw tool interface.
5 | * For example, calling:
6 | *
7 | * pointmap_chart.addDrawControl(LassoToolSetTypes.kCircle | LassoToolSetTypes.kPolyLine)
8 | *
9 | * will enable only the circle and polyline tools.
10 | *
11 | * Calling:
12 | *
13 | * pointmap_chart.addDrawControl(LassoToolSetTypes.kStandard)
14 | *
15 | * will enable the standard set of tools, currently circle, polyline, and lasso
16 | */
17 | export default class LassoToolSetTypes {
18 | // NOTE: the no-undef eslint disabling below is due to the outdated nature
19 | // of the babel-eslint package and its apparent inappropriate handling of
20 | // static member variables
21 |
22 | /**
23 | * Bitflag identifier for the Circle draw tool
24 | * @type {Number}
25 | */
26 | // eslint-disable-next-line no-undef
27 | static kCircle = new LassoToolSetTypes(1 << 0, "circle")
28 |
29 | /**
30 | * Bitflag identifier for the Polyline draw tool
31 | * @type {Number}
32 | */
33 | // eslint-disable-next-line no-undef
34 | static kPolyLine = new LassoToolSetTypes(1 << 1, "polyline")
35 |
36 | /**
37 | * Bitflag identifier for the Lasso draw tool
38 | * @type {Number}
39 | */
40 | // eslint-disable-next-line no-undef
41 | static kLasso = new LassoToolSetTypes(1 << 2, "lasso")
42 |
43 | /**
44 | * Bitflag identifier for the CrossSection draw tool
45 | * @type {Number}
46 | */
47 | // eslint-disable-next-line no-undef
48 | static kCrossSection = new LassoToolSetTypes(1 << 3, "cross section")
49 |
50 | // eslint-disable-next-line no-undef
51 | static kAll = new LassoToolSetTypes(
52 | LassoToolSetTypes.kCircle |
53 | LassoToolSetTypes.kPolyLine |
54 | LassoToolSetTypes.kLasso |
55 | LassoToolSetTypes.kCrossSection,
56 | "all"
57 | )
58 |
59 | // eslint-disable-next-line no-undef
60 | static kStandard = new LassoToolSetTypes(
61 | LassoToolSetTypes.kCircle |
62 | LassoToolSetTypes.kPolyLine |
63 | LassoToolSetTypes.kLasso,
64 | "standard"
65 | )
66 |
67 | constructor(value, description = null) {
68 | this.value = value
69 | this.description = description
70 | }
71 |
72 | valueOf() {
73 | return this.value
74 | }
75 |
76 | toString() {
77 | return `${this.description !== null ? this.description : this.value}`
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/utils/color-helpers.js:
--------------------------------------------------------------------------------
1 | export const ALL_OTHERS_LABEL = "All Others"
2 | export const ALL_OTHERS_COLOR = "#888888"
3 |
4 | export function maybeUpdateDomainRange(
5 | chart,
6 | data,
7 | dataAccessor,
8 | domain,
9 | range,
10 | wrapKey = false
11 | ) {
12 | const dataArr = data.map(dataAccessor)
13 | const domainRangeMap = new Map(domain.map((key, i) => [key, range[i]]))
14 | const dataMap = new Map(
15 | dataArr.map((key, i) => [
16 | key,
17 | domainRangeMap.get(key) ??
18 | chart.getColor(wrapKey ? { key0: key } : key, i)
19 | ])
20 | )
21 | chart.customDomain([...dataMap.keys()])
22 | chart.customRange([...dataMap.values()])
23 | }
24 |
25 | export function maybeUpdateAllOthers(chart, data, domain, range) {
26 | const dataHasAllOthers = data.map(d => d.data.key0).includes(ALL_OTHERS_LABEL)
27 | const domainHasAllOthers = domain.includes(ALL_OTHERS_LABEL)
28 | if (dataHasAllOthers && !domainHasAllOthers) {
29 | domain.push(ALL_OTHERS_LABEL)
30 | range.push(ALL_OTHERS_COLOR)
31 | } else if (domainHasAllOthers && !dataHasAllOthers) {
32 | const index = domain.indexOf(ALL_OTHERS_LABEL)
33 | domain.splice(index, 1)
34 | range.splice(index, 1)
35 | }
36 |
37 | chart.customDomain(domain)
38 | chart.customRange(range)
39 | }
40 |
41 | const cyrb53 = (str, seed = 0) => {
42 | let h1 = 0xdeadbeef ^ seed
43 | let h2 = 0x41c6ce57 ^ seed
44 | for (let i = 0, ch; i < str.length; i++) {
45 | ch = str.charCodeAt(i)
46 | h1 = Math.imul(h1 ^ ch, 2654435761)
47 | h2 = Math.imul(h2 ^ ch, 1597334677)
48 | }
49 |
50 | h1 =
51 | Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^
52 | Math.imul(h2 ^ (h2 >>> 13), 3266489909)
53 |
54 | h2 =
55 | Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^
56 | Math.imul(h1 ^ (h1 >>> 13), 3266489909)
57 |
58 | return 4294967296 * (2097151 & h2) + (h1 >>> 0)
59 | }
60 |
61 | export const determineColorByValue = (value, colors) => {
62 | if (typeof value === "string") {
63 | const hash = cyrb53(value)
64 | const colorIndex = hash % colors.length
65 | return colors[colorIndex]
66 | }
67 | const colorIndex = value % colors.length
68 | return colors[colorIndex]
69 | }
70 |
71 | export function buildHashedColor(field, range, paletteLength, customColors) {
72 | if (customColors?.domain?.length > 0 && customColors?.range?.length > 0) {
73 | const domain = customColors.domain
74 | // build SQL CASE statement
75 | let sql = `CASE `
76 | for (let i = 0; i < domain.length; i++) {
77 | sql += `WHEN ${field} = '${domain[i]}' THEN ${range.indexOf(
78 | customColors.range[i]
79 | )} `
80 | }
81 | sql += `ELSE MOD(HASH(${field}), ${paletteLength}) END`
82 | return sql
83 | } else {
84 | return `MOD(HASH(${field}), ${paletteLength})`
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/utils/logger.js:
--------------------------------------------------------------------------------
1 | export const logger = {}
2 |
3 | logger.enableDebugLog = false
4 |
5 | /* istanbul ignore next */
6 | logger.warn = function(msg) {
7 | if (console) {
8 | if (console.warn) {
9 | console.warn(msg)
10 | } else if (console.log) {
11 | console.log(msg)
12 | }
13 | }
14 |
15 | return logger
16 | }
17 |
18 | /* istanbul ignore next */
19 | logger.debug = function(msg) {
20 | if (logger.enableDebugLog && console) {
21 | if (console.debug) {
22 | console.debug(msg)
23 | } else if (console.log) {
24 | console.log(msg)
25 | }
26 | }
27 |
28 | return logger
29 | }
30 |
31 | /* istanbul ignore next */
32 | logger.deprecate = function(fn, msg) {
33 | // Allow logging of deprecation
34 | let warned = false
35 | function deprecated() {
36 | if (!warned) {
37 | logger.warn(msg)
38 | warned = true
39 | }
40 | return fn.apply(this, arguments)
41 | }
42 | return deprecated
43 | }
44 |
--------------------------------------------------------------------------------
/src/utils/logger.unit.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from "chai"
2 | import * as dc from "../index"
3 |
4 | describe("Logger", () => {
5 | it("should have all the necessary exports", () => {
6 | expect(typeof dc.logger.warn).to.equal("function")
7 | expect(typeof dc.logger.debug).to.equal("function")
8 | expect(typeof dc.logger.deprecate).to.equal("function")
9 | expect(dc.logger.enableDebugLog).to.equal(false)
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/src/utils/mapbox-ported-functions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * As part of the initiative to upgrade our Mapbox dependency, these
3 | * functions have been copied from our forked version of Mapbox so that Mapbox
4 | * can be updated independently
5 | * - See [FE-8035]
6 | */
7 |
8 | import UnitBezier from "@mapbox/unitbezier"
9 |
10 | /**
11 | * Given given (x, y), (x1, y1) control points for a bezier curve,
12 | * return a function that interpolates along that curve.
13 | *
14 | * @param p1x control point 1 x coordinate
15 | * @param p1y control point 1 y coordinate
16 | * @param p2x control point 2 x coordinate
17 | * @param p2y control point 2 y coordinate
18 | * @private
19 | */
20 | export function bezier(p1x, p1y, p2x, p2y) {
21 | const bezier = new UnitBezier(p1x, p1y, p2x, p2y)
22 | return function(t) {
23 | return bezier.solve(t)
24 | }
25 | }
26 | /**
27 | * Provides a function that outputs milliseconds: either performance.now()
28 | * or a fallback to Date.now()
29 | */
30 | const Now = (function() {
31 | if (window.performance && window.performance.now) {
32 | return window.performance.now.bind(window.performance)
33 | } else {
34 | return Date.now.bind(Date)
35 | }
36 | })()
37 |
38 | const frame = function(fn) {
39 | const frameFn =
40 | window.requestAnimationFrame ||
41 | window.mozRequestAnimationFrame ||
42 | window.webkitRequestAnimationFrame ||
43 | window.msRequestAnimationFrame
44 | return frameFn(fn)
45 | }
46 |
47 | export const timed = function(fn, dur, ctx) {
48 | if (!dur) {
49 | fn.call(ctx, 1)
50 | return null
51 | }
52 |
53 | let abort = false
54 | const start = Now()
55 |
56 | function tick(now) {
57 | if (abort) {
58 | return
59 | }
60 | now = Now()
61 |
62 | if (now >= start + dur) {
63 | fn.call(ctx, 1)
64 | } else {
65 | fn.call(ctx, (now - start) / dur)
66 | frame(tick)
67 | }
68 | }
69 |
70 | frame(tick)
71 |
72 | return function() {
73 | abort = true
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/utils/multiple-key-accessors.js:
--------------------------------------------------------------------------------
1 | import { isArrayOfObjects } from "../../src/utils/formatting-helpers"
2 |
3 | const INDEX_NONE = -1
4 | const identity = a => a
5 |
6 | function normalize(data) {
7 | if (isArrayOfObjects(data)) {
8 | return data.map(d => d.value)
9 | } else {
10 | return data
11 | }
12 | }
13 |
14 | function getMinOfRange(d) {
15 | if (Array.isArray(d)) {
16 | return d[0]
17 | } else {
18 | return d
19 | }
20 | }
21 |
22 | function createAccessor(transform = identity) {
23 | return function multipleKeyAccessor(d) {
24 | let filteredKeys = []
25 | for (const key in d) {
26 | if (d.hasOwnProperty(key) && key.indexOf("key") > INDEX_NONE) {
27 | filteredKeys.push(transform(normalize(d[key])))
28 | }
29 | }
30 | if (filteredKeys.length === 1) {
31 | filteredKeys = filteredKeys[0]
32 | }
33 | return filteredKeys
34 | }
35 | }
36 |
37 | export const multipleKeysAccessorForCap = createAccessor()
38 | export const multipleKeysAccessorForStack = createAccessor(getMinOfRange)
39 |
--------------------------------------------------------------------------------
/src/utils/multiple-key-accessors.unit.spec.js:
--------------------------------------------------------------------------------
1 | // /* eslint-disable */
2 |
3 | import {
4 | multipleKeysAccessorForCap,
5 | multipleKeysAccessorForStack
6 | } from "./multiple-key-accessors"
7 | import { expect } from "chai"
8 |
9 | describe("Multiple Key Accessors", () => {
10 | describe("exports", () => {
11 | it("should have all the necessary exports", () => {
12 | expect(typeof multipleKeysAccessorForCap).to.equal("function")
13 | expect(typeof multipleKeysAccessorForStack).to.equal("function")
14 | })
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/src/utils/utils-lasso.js:
--------------------------------------------------------------------------------
1 | import wellknown from "wellknown"
2 |
3 | const coordinates = index => features =>
4 | features
5 | .map(feature => feature.geometry.coordinates[0].map(c => c[index]))
6 | .reduce((accum, coords) => accum.concat(coords), [])
7 |
8 | const LONGITUDE_INDEX = 0
9 | const LATITUDE_INDEX = 1
10 |
11 | const longitudes = coordinates(LONGITUDE_INDEX)
12 | const latitudes = coordinates(LATITUDE_INDEX)
13 |
14 | function convertFeaturesToUnlikelyStmt(features, px, py) {
15 | const lons = longitudes(features)
16 | const lats = latitudes(features)
17 | const left = Math.max(...lons)
18 | const right = Math.min(...lons)
19 | const top = Math.min(...lats)
20 | const bottom = Math.max(...lats)
21 | return `UNLIKELY( ${px} >= ${right} AND ${px} <= ${left} AND ${py} >= ${top} AND ${py} <= ${bottom})`
22 | }
23 |
24 | function convertFeatureToCircleStmt({ geometry: { radius, center } }, px, py) {
25 | const lat2 = center[1]
26 | const lon2 = center[0]
27 | const meters = radius * 1000
28 | return `ST_DWithin(CAST(ST_SetSRID(ST_Point(${lon2}, ${lat2}), 4326) as GEOGRAPHY), CAST(ST_SetSRID(ST_Point(${px}, ${py}), 4326) as GEOGRAPHY), ${meters})`
29 | }
30 |
31 | function convertFeatureToContainsStmt({ geometry }, px, py) {
32 | return `ST_Contains('${wellknown.stringify(
33 | geometry
34 | )}', ST_Point(${px}, ${py}))`
35 | }
36 |
37 | export function convertGeojsonToSql(features, px, py) {
38 | let sql = ""
39 | const polyStmts = []
40 | const circleStmts = []
41 |
42 | features.forEach(feature => {
43 | if (feature.properties.circle) {
44 | circleStmts.push(convertFeatureToCircleStmt(feature, px, py))
45 | } else {
46 | polyStmts.push(convertFeatureToContainsStmt(feature, px, py))
47 | }
48 | })
49 |
50 | if (polyStmts.length) {
51 | sql = sql + `(${polyStmts.join(" OR ")})`
52 | }
53 |
54 | if (circleStmts.length) {
55 | if (polyStmts.length) {
56 | sql = sql + ` OR (${circleStmts.join(" OR ")})`
57 | } else {
58 | sql = sql + `(${circleStmts.join(" OR ")})`
59 | }
60 |
61 | sql = `(${sql})`
62 | }
63 |
64 | if (polyStmts.length) {
65 | const unlikelyStmt = convertFeaturesToUnlikelyStmt(features, px, py)
66 | return `(${unlikelyStmt}) AND ${sql}`
67 | } else {
68 | return sql
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/utils/utils-latlon.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | import * as Draw from "@heavyai/draw/dist/draw"
4 |
5 | /**
6 | * Calculates the distance in meters between two lon/lat coordinates
7 | * @param {number} fromlon Longitude to start from
8 | * @param {number} fromlat Latitude to start from
9 | * @param {number} tolon Longitude to end at
10 | * @param {number} tolat Latitude to end at
11 | * @return {number} Distance in meters from two lon/lat coords
12 | */
13 | export function distance_in_meters(fromlon, fromlat, tolon, tolat) {
14 | const latitudeArc = (fromlat - tolat) * Draw.Math.DEG_TO_RAD
15 | const longitudeArc = (fromlon - tolon) * Draw.Math.DEG_TO_RAD
16 | let latitudeH = Math.sin(latitudeArc * 0.5)
17 | latitudeH = latitudeH * latitudeH
18 | let lontitudeH = Math.sin(longitudeArc * 0.5)
19 | lontitudeH = lontitudeH * lontitudeH
20 | const tmp =
21 | Math.cos(fromlat * Draw.Math.DEG_TO_RAD) *
22 | Math.cos(tolat * Draw.Math.DEG_TO_RAD)
23 | return (
24 | 6372797.560856 * (2.0 * Math.asin(Math.sqrt(latitudeH + tmp * lontitudeH)))
25 | )
26 | }
27 |
28 | /**
29 | * Converts mercator x coordinate to longitude
30 | * @param {number} x X coordinate in mercator projected space
31 | * @return {number} Longitude
32 | */
33 | export function conv900913To4326X(x) {
34 | return x / 111319.490778
35 | }
36 |
37 | /**
38 | * Converts mercator y coordinate to latitude
39 | * @param {number} y Y coordinate in mercator projected space
40 | * @return {number} Latitude
41 | */
42 | export function conv900913To4326Y(y) {
43 | return (
44 | 57.295779513 * (2 * Math.atan(Math.exp(y / 6378136.99911)) - 1.570796327)
45 | )
46 | }
47 |
48 | /**
49 | * Converts 2d point in mercator projected space to a lon/lat coordinate
50 | * @param {Point2d} out 2d point to store the converted lat/lon coordinate
51 | * @param {Point2d} coord 2d point in mercator projected space to convert
52 | * @return {Point2d} Point referred to by the out arg
53 | */
54 | export function conv900913To4326(out, coord) {
55 | return Draw.Point2d.set(
56 | out,
57 | conv900913To4326X(coord[0]),
58 | conv900913To4326Y(coord[1])
59 | )
60 | }
61 |
62 | /**
63 | * Converts a longitude coordinate to an x coordinate in mercator projected space
64 | * @param {number} x Longitude
65 | * @return {number} X coordinate in mercator projected space
66 | */
67 | export function conv4326To900913X(x) {
68 | return x * 111319.490778
69 | }
70 |
71 | /**
72 | * Converts a latitude coordinate to a y coordinate in mercator projected space
73 | * @param {number} x Latitude
74 | * @return {number} Y coordinate in mercator projected space
75 | */
76 | export function conv4326To900913Y(y) {
77 | return 6378136.99911 * Math.log(Math.tan(0.00872664626 * y + 0.785398163397))
78 | }
79 |
80 | /**
81 | * Converts 2d lon/lat point to a point in mercator projected space
82 | * @param {Point2d} out 2d point to store the converted mercator coordinate
83 | * @param {Point2d} coord 2d point in lon/lat to convert
84 | * @return {Point2d} Point referred to by the out arg
85 | */
86 | export function conv4326To900913(out, coord) {
87 | return Draw.Point2d.set(
88 | out,
89 | conv4326To900913X(coord[0]),
90 | conv4326To900913Y(coord[1])
91 | )
92 | }
93 |
--------------------------------------------------------------------------------
/src/utils/utils-vega.unit.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | import * as VegaUtils from "./utils-vega"
4 | import { expect } from "chai"
5 |
6 | describe("Vega Utils", () => {
7 | describe("All Utils", () => {
8 | it("should have all the necessary exports", () => {
9 | expect(typeof VegaUtils.notNull).to.equal("function")
10 | expect(typeof VegaUtils.createVegaAttrMixin).to.equal("function")
11 | expect(typeof VegaUtils.createRasterLayerGetterSetter).to.equal(
12 | "function"
13 | )
14 | })
15 | })
16 |
17 | describe("notNull", () => {
18 | it("should return false if value is null", () => {
19 | expect(VegaUtils.notNull(undefined)).to.be.false
20 | })
21 |
22 | it("should return false when a value is undefined", () => {
23 | expect(VegaUtils.notNull(undefined)).to.be.false
24 | })
25 |
26 | it("should return value if value is within min and max", () => {
27 | expect(VegaUtils.notNull(0)).to.be.true
28 | })
29 |
30 | it("should return value if value is within min and max", () => {
31 | expect(VegaUtils.notNull(false)).to.be.true
32 | })
33 |
34 | it("should return value if value is within min and max", () => {
35 | expect(VegaUtils.notNull("null")).to.be.true
36 | })
37 |
38 | it("should return value if value is within min and max", () => {
39 | expect(VegaUtils.notNull(5)).to.be.true
40 | })
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/test/.mocharc.yml:
--------------------------------------------------------------------------------
1 | require:
2 | - '@babel/core'
3 | - './test/register-babel.js'
4 | - './test/config.js'
5 | - './test/setup-unit-tests.js'
6 |
--------------------------------------------------------------------------------
/test/config.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = "test"
2 | process.env.JUNIT_REPORT_PATH = "mocha-report.xml"
3 | process.env.JUNIT_REPORT_STACK = 1
4 |
--------------------------------------------------------------------------------
/test/mapbox-gl-mock.js:
--------------------------------------------------------------------------------
1 | const mapboxgl = {}
2 |
3 | function Evented() {}
4 |
5 | const LngLatBounds = {
6 | convert: () => {}
7 | }
8 |
9 | mapboxgl.Evented = Evented
10 | mapboxgl.LngLatBounds = LngLatBounds
11 |
12 | export default mapboxgl
13 |
--------------------------------------------------------------------------------
/test/register-babel.js:
--------------------------------------------------------------------------------
1 | require("@babel/register")({
2 | ignore: [/node_modules[\\/](?!legendables)/]
3 | })
4 |
--------------------------------------------------------------------------------
/test/setup-unit-tests.js:
--------------------------------------------------------------------------------
1 | const jsdom = require("jsdom")
2 | const { JSDOM } = jsdom
3 | var atob = require("atob")
4 | var url = require("url")
5 | var fs = require("fs")
6 |
7 | var exposedProperties = ['window', 'navigator', 'document'];
8 |
9 | global.window = new JSDOM(
10 | ``,
11 | {
12 | resources: "usable",
13 | runScripts: "dangerously",
14 | url: "http://localhost"
15 | }
16 | ).window
17 | global.document = global.window.document
18 | global.window.atob = atob
19 | global.window.URL = url
20 | global.window.TCopyParams = () => {}
21 | global.window.TDatumType = {"SMALLINT":0,"INT":1,"BIGINT":2,"FLOAT":3,"DECIMAL":4,"DOUBLE":5,"STR":6,"TIME":7,"TIMESTAMP":8,"DATE":9,"BOOL":10,"INTERVAL_DAY_TIME":11,"INTERVAL_YEAR_MONTH":12}
22 | global.window.TEncodingType = {"NONE":0,"FIXED":1,"RL":2,"DIFF":3,"DICT":4,"SPARSE":5}
23 |
24 | Object.keys(document.defaultView).forEach((property) => {
25 | if (typeof global[property] === 'undefined') {
26 | exposedProperties.push(property);
27 | global[property] = document.defaultView[property];
28 | }
29 | });
30 |
31 | global.navigator = {
32 | userAgent: 'node.js'
33 | };
34 |
35 | console.warn = (a) => {}
36 | console.error = (a) => {}
37 |
38 | propagateToGlobal(global.window)
39 |
40 | function propagateToGlobal (window) {
41 | for (let key in window) {
42 | if (!window.hasOwnProperty(key)) continue
43 | if (key in global) continue
44 | global[key] = window[key]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | var jsdom = require("jsdom").jsdom
2 |
3 | var exposedProperties = ["window", "navigator", "document"]
4 |
5 | global.document = jsdom("")
6 | global.window = document.defaultView
7 |
8 | Object.keys(document.defaultView).forEach(property => {
9 | if (typeof global[property] === "undefined") {
10 | exposedProperties.push(property)
11 | global[property] = document.defaultView[property]
12 | }
13 | })
14 |
15 | global.navigator = {
16 | userAgent: "node.js"
17 | }
18 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require("webpack");
2 | const ExtractTextPlugin = require("extract-text-webpack-plugin");
3 | const path = require('path')
4 |
5 | module.exports = {
6 | context: __dirname,
7 | entry: {
8 | "charting": "./index.js"
9 | },
10 | output: {
11 | path: path.join(__dirname, "/dist"),
12 | filename: "[name].js",
13 | libraryTarget: "umd",
14 | library: "charting"
15 | },
16 | externals: {
17 | "d3": "d3",
18 | "crossfilter": {
19 | "commonjs": "crossfilter",
20 | "commonjs2": "crossfilter",
21 | "amd": "crossfilter",
22 | }
23 | },
24 | module: {
25 | loaders: [
26 | {
27 | test: /\.js?$/,
28 | exclude: /node_modules\/(?!@mapbox-controls\/ruler)/,
29 | use: "babel-loader"
30 | },
31 | {
32 | test: /\.css$/,
33 | use: ExtractTextPlugin.extract({
34 | fallback: "style-loader",
35 | use: "css-loader"
36 | })
37 | },
38 | {
39 | test: /\.scss$/,
40 | use: ExtractTextPlugin.extract({
41 | fallback: "style-loader",
42 | use: ["css-loader", "sass-loader"]
43 | })
44 | }
45 | ]
46 | },
47 | plugins: [
48 | new webpack.DefinePlugin({
49 | "process.env": {
50 | NODE_ENV: JSON.stringify("production")
51 | }
52 | }),
53 | new ExtractTextPlugin("charting.css"),
54 | ]
55 | };
56 |
--------------------------------------------------------------------------------