├── .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 | ![example1](https://cloud.githubusercontent.com/assets/2932405/25641647/1acce1f2-2f4a-11e7-87d4-a4e80cb262f5.gif) 10 | 11 | #### Tweets Dataset: Brushing on timeline and hovering on Pointmap datapoint which displays row information 12 | 13 | ![example2](https://user-images.githubusercontent.com/4845281/28191946-21bb7ec0-67e8-11e7-855e-8922939d1241.gif) 14 | 15 | #### Tweets Dataset: Using draw-js tool on pointmap to select specific areas on a map 16 | 17 | ![example5](https://user-images.githubusercontent.com/4845281/28191947-21bd2ad6-67e8-11e7-9c8d-a5ddcd0f07fc.gif) 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 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
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 | --------------------------------------------------------------------------------