├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── hexbins │ ├── advanced.html │ ├── basic.html │ ├── colors_discrete.html │ ├── colors_divergent.html │ ├── custom_scale.html │ ├── events.html │ ├── multi_layers.html │ └── realtime.html └── pings │ ├── advanced.html │ ├── basic.html │ ├── efficient.html │ └── high.volume.html ├── index.d.ts ├── package-lock.json ├── package.json ├── rollup.config.js └── src └── js ├── hexbin └── HexbinLayer.js ├── index.js └── ping └── PingLayer.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | 8 | indent_style = tab 9 | tab_width = 4 10 | 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.html] 15 | tab_width = 4 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "sourceType": "module" 4 | }, 5 | 6 | "env": { 7 | "es6": true, 8 | "browser": true 9 | }, 10 | 11 | "extends": "eslint:recommended", 12 | 13 | "rules": { 14 | "no-unused-vars": [ "error", { "args": "none" } ], 15 | 16 | "no-mixed-spaces-and-tabs": [ "error", "smart-tabs" ], 17 | "array-bracket-spacing": [ "error", "always" ], 18 | "block-spacing": [ "error", "always" ], 19 | "brace-style": [ "error", "stroustrup", { "allowSingleLine": true } ], 20 | "comma-spacing": [ "error" ], 21 | "space-before-blocks": [ "error", "always" ] 22 | }, 23 | 24 | "globals": { 25 | "console": true, 26 | "require": true, 27 | "window": true, 28 | 29 | "d3": true, 30 | "d3_hexbin": true, 31 | "L": true, 32 | 33 | "after": true, 34 | "afterEach": true, 35 | "before": true, 36 | "beforeEach": true, 37 | "context": true, 38 | "describe": true, 39 | "it": true, 40 | "should": true 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################### 2 | # Project Specific 3 | ################### 4 | dist/ 5 | 6 | ################### 7 | # Editors 8 | ################### 9 | 10 | # Eclipse 11 | .project 12 | .settings 13 | .classpath 14 | 15 | # IntelliJ 16 | .idea/ 17 | *.iml 18 | *.iws 19 | 20 | # Net Beans 21 | nbactions.xml 22 | nb-configuration.xml 23 | 24 | # VS 25 | .vscode 26 | 27 | # Vi, etc 28 | *.swp 29 | *.tmp 30 | *.bak 31 | 32 | # General 33 | *~ 34 | 35 | 36 | ################### 37 | # Build 38 | ################### 39 | 40 | # Node 41 | node_modules/ 42 | npm-debug.log 43 | *.tgz 44 | yarn.lock 45 | 46 | # Common 47 | bin/ 48 | tmp/ 49 | 50 | # Apple 51 | *~.nib 52 | 53 | 54 | ################### 55 | # OS 56 | ################### 57 | 58 | # MacOS 59 | .DS_Store 60 | 61 | 62 | ################### 63 | # Tests 64 | ################### 65 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8.9" 4 | cache: yarn 5 | script: npm run build 6 | sudo: false 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2007-2018 Asymmetrik Ltd, a Maryland Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @asymmetrik/leaflet-d3 2 | 3 | [![Build Status][travis-image]][travis-url] 4 | 5 | > Leaflet D3 6 | > Provides a collection of [D3.js](http://d3js.org) based visualization plugins for [Leaflet](http://leafletjs.com/). 7 | > Now supports D3 v7 8 | 9 | ## Table of Contents 10 | - [Install](#install) 11 | - [Usage](#usage) 12 | - [Hexbins API](#hexbins-api) 13 | - [Pings API](#pings-api) 14 | - [Changelog](#changelog) 15 | - [Contribute](#contribute) 16 | - [License](#license) 17 | - [Credits](#credits) 18 | 19 | 20 | ## Install 21 | Install the package and its peer dependencies via npm: 22 | ``` 23 | npm install d3 d3-hexbin leaflet 24 | ``` 25 | 26 | If you want to grab the source files directly without using npm, or you want to run the examples, you can build the dist files directly. 27 | Simply check out the repository, and then build it with the following commands: 28 | ``` 29 | git clone git@github.com:Asymmetrik/leaflet-d3.git 30 | cd leaflet-d3 31 | npm install 32 | npm run build 33 | ``` 34 | 35 | ## Usage 36 | 37 | ### Hexbins 38 | Create dynamic hexbin-based heatmaps on Leaflet maps. 39 | This plugin is based on [the work of Steven Hall](http://www.delimited.io/blog/2013/12/1/hexbins-with-d3-and-leaflet-maps). 40 | The primary difference is that this plugin leverages the data-binding power of d3 to allow you to dynamically update the data and visualize the transitions. 41 | 42 | map with hexbins 43 | 44 | Live Demo: [JSFiddle](http://jsfiddle.net/acjnbu8t/embedded/result/) 45 | 46 | To use, simply declare a hexbin layer and add it to your map. 47 | You can then add data to the layer. 48 | 49 | ```js 50 | // Create the hexbin layer and set all of the accessor functions 51 | var hexLayer = L.hexbinLayer().addTo(map); 52 | 53 | // Set the data (can be set multiple times) 54 | hexLayer.data([[lng1, lat1], [lng2, lat2], ... [lngN, latN]]); 55 | 56 | ``` 57 | 58 | #### Styling 59 | 60 | You will likely want to apply your own styles to the hexbin hexagons themselves. To do so, use the ```hexbin-hexagon``` class. 61 | See the following example: 62 | 63 | ```css 64 | .hexbin-hexagon { 65 | stroke: #000; 66 | stroke-width: .5px; 67 | } 68 | ``` 69 | 70 | #### Tooltips 71 | 72 | You have a couple options when it comes to providing tooltips for your users. 73 | First, you can register for the appropriate hover events and manually access/manipulate the dom to show/hide tooltips. 74 | Second, you can leverage the built-in hover handlers, which try to encapsulate a lot of this behavior. 75 | 76 | ``` 77 | var hexLayer = L.hexbinLayer() 78 | .hoverHandler(L.HexbinHoverHandler.tooltip()); 79 | ``` 80 | 81 | This handler, combined with CSS, can be used to show a tooltip and highlight the hovered hexbin. 82 | In the following example, we change the stroke color and show a tooltip. 83 | 84 | ``` 85 | .hexbin-container:hover .hexbin-hexagon { 86 | transition: 200ms; 87 | stroke: orange; 88 | stroke-width: 1px; 89 | stroke-opacity: 1; 90 | } 91 | 92 | .hexbin-tooltip { 93 | padding: 8px; 94 | border-radius: 4px; 95 | border: 1px solid black; 96 | background-color: white; 97 | } 98 | ``` 99 | 100 | There's more documentation on how to customize the behavior of the hover handlers in the API docs below. 101 | 102 | 103 | #### Special Notes 104 | 105 | **Applying Durations:** 106 | If your data is transforming faster than the transition duration, you may encounter unexpected behavior. 107 | This is an artifact of how transitions interact with and cancel each other. 108 | You should reduce the transition duration or eliminate it entirely if you are going to be using this plugin in a realtime manner. 109 | 110 | **Color Scales:** 111 | To use a polylinear color scale, simply provide more than two colors in the range. The domain cardinality will be adjusted automatically. 112 | A minimum of two values is required in the color range, but a single-color range is possible by using `['blue', 'blue']` for example. 113 | See the examples to see how diverging and discrete color scales can be used. 114 | 115 | 116 | ### Pings 117 | Create realtime animated drops/pings/blips on a map. 118 | This plugin can be used to indicate a transient event, such as a real-time occurrance of an event at a specific geographical location. 119 | 120 | map with pings 121 | 122 | **Live Demo:** [JSFiddle](http://jsfiddle.net/reblace/7jfhLgnq/embedded/result/) 123 | 124 | To use, simply declare a ping layer and add it to your map. 125 | You can then add data by calling the ping() method. 126 | 127 | ```js 128 | // Create the ping layer and add it to the map 129 | var pingLayer = L.pingLayer().addTo(map); 130 | 131 | // Submit data so that it shows up as a ping with an optional per-ping css class 132 | pingLayer.ping([ 38.991709, -76.886109 ], 'myCustomCssClass'); 133 | 134 | ``` 135 | 136 | #### Styling 137 | 138 | You will likely want to apply your own styles to the pings themselves. To do so, use the ```ping``` class. 139 | See the following example: 140 | 141 | ```css 142 | .ping { 143 | fill: steelblue; 144 | stroke: #222; 145 | stroke-width: .5px; 146 | } 147 | ``` 148 | 149 | 150 | ## Hexbins API 151 | 152 | ### L.hexbinLayer(options?: {}): L.HexbinLayer 153 | Create a Leaflet map layer for visualizing data using colored/sized hexbin-based bins. 154 | 155 | ```js 156 | var hexLayer = L.hexbinLayer().addTo(map); 157 | ``` 158 | 159 | ### options 160 | Set of options for customizing the appearance/behavior of the hexbin layer. 161 | 162 | Example: 163 | 164 | ```js 165 | var options = { 166 | radius : 12, 167 | opacity: 0.5, 168 | duration: 200, 169 | 170 | colorScaleExtent: [ 1, undefined ], 171 | radiusScaleExtent: [ 1, undefined ], 172 | colorDomain: null, 173 | radiusDomain: null, 174 | colorRange: [ '#f7fbff', '#08306b' ], 175 | radiusRange: [ 5, 12 ], 176 | 177 | pointerEvents: 'all' 178 | }; 179 | ``` 180 | 181 | #### radius 182 | Default: 12 - Sets the radius on the hexbin layer (see below for details). 183 | 184 | #### opacity 185 | Default: 0.6 - Sets the opacity on the hexbin layer (see below for details). 186 | 187 | #### duration 188 | Default: 200 - Sets the transition duration for the hexbin layer (see below for details). 189 | 190 | #### colorScaleExtent 191 | Default: [ 1, undefined ] - Sets the extent of the color scale for the hexbin layer (see below for details). 192 | 193 | #### radiusScaleExtent 194 | Default: [ 1, undefined ] - This is the same exact configuration option as ```colorScaleExtent```, only applied to the radius extent. 195 | 196 | #### colorDomain 197 | Default: null - This is used to override the default behavior, which is to derive the color domain from the data. 198 | Normally, you can tweak the generation of the color domain using the colorScaleExtent option. 199 | However, if you want to set a completely custom domain, you can provide it as an array of values with this option. 200 | The array of values will be passed directly into the domain of the color scale before rendering. 201 | 202 | #### radiusDomain 203 | Default: null - This is used to override the default behavior, which is to derive the radius domain from the data. 204 | Normally, you can tweak the generation of the radius domain using the radiusScaleExtent option. 205 | However, if you want to set a completely custom domain, you can provide it as an array of values with this option. 206 | The array of values will be passed directly into the domain of the radius scale before rendering. 207 | 208 | #### colorRange 209 | Default: [ '#f7fbff', '#08306b' ] - Sets the range of the color scale used to fill the hexbins on the layer. 210 | 211 | #### radiusRange 212 | Default: [ 4, 12 ] - Sets the range of the radius scale used to size the hexbins on the layer. 213 | 214 | #### pointerEvents 215 | Default: 'all' - This value is passed directly to an element-level css style for ```pointer-events```. 216 | 217 | You should only modify this config option if you want to change the mouse event behavior on hexbins. 218 | This will modify when the events are propagated based on the visibility state and/or part of the hexbin being hovered. 219 | 220 | 221 | ### L.HexbinLayer 222 | 223 | #### hexbinLayer.data(value?: any[]) 224 | Setter/getter for the data bound to the hexbin layer. 225 | The default data schema for the hexin layer is: 226 | 227 | ```[ [ lng1, lat1 ], [ lng2, lat2 ], [ lng3, lat3 ]... [ lngN, latN ] ]``` 228 | 229 | Where the hexbin size is fixed at the radius and the color is linearly scaled and based on the bin count (the number of points contained in the bin). 230 | 231 | 232 | #### hexbinLayer.redraw() 233 | Triggers a redraw of the hexbin layer. 234 | You should only need to use this function if you are modifying several aspects of the layer. 235 | The only function in the API that automatically redraws is ```.data()``` 236 | 237 | 238 | #### hexbinLayer.radius(value?: number) 239 | Setter/getter for the radius configuration option. 240 | Radius of the hexagon grid cells in pixels. 241 | 242 | This value should be a positive number. 243 | This radius controls the radius of the hexagons used to bin the data but not necessarily to draw each individual hexbin. 244 | 245 | 246 | #### hexbinLayer.opacity(value?: number) 247 | Setter/getter for the opacity configuration option. 248 | The opacity of the visible hexagons. 249 | 250 | This value should be a number between 0 and 1. 251 | Since we are transitioning opacity as part of d3 rendering the hexbins, this is not currently controlled by CSS. 252 | Future iterations may make this purely CSS based with the transitions applied to groups. 253 | 254 | 255 | #### hexbinLayer.duration(value?: number) 256 | Setter/getter for the durations configuration option. The millisecond duration of d3 transitions between states. 257 | This value should be a non-negative number. 258 | A value of 0 means that transitions will be instantaneous. 259 | 260 | *Note:* The transition duration should be set to a value lower than the minimum refresh interval of your data. 261 | What this means is that if you are going to be updating the data for the hexbin layer every 100 ms, you should keep this duration at a value of less than 100ms. 262 | The consequence of not doing this is that hexbins will not fully complete their transitions in between changes. 263 | 264 | 265 | #### hexbinLayer.colorScaleExtent(value?: [ number, number ]) 266 | Setter/getter for the colorScaleExtent configuration option. 267 | This is used to override the derived extent of the color values and is specified as a tuple of the form [ min: number, max: number ]. 268 | A value of ```undefined`` for either min or max indicates that the derived value should be retained. 269 | 270 | What this means is we derive the color value extent of the data (the min/max values in the data array) as an array of the form [ min, max ]. 271 | Once we have that tuple, we override those values with the values provided by this config option. 272 | For example, if the derived min/max is [5, 102] and ```colorScaleExtent``` is [ 1, undefined ], the resulting extent used as the domain of the colorScale will be [ 1, 102 ]. 273 | This setting is useful when you want to be explicit about the domain of values for the hexbins. 274 | For example, when you want consistent color scales between multiple maps or within the same map. 275 | 276 | 277 | #### hexbinLayer.radiusScaleExtent(value?: [ number, number ]) 278 | Setter/getter for the radiusScaleExtent configuration option. 279 | This is the same exact configuration option as ```colorScaleExtent```, only applied to the radius extent. 280 | 281 | 282 | #### hexbinLayer.colorRange(value?: [ number, number ]) 283 | Setter/getter for the colorRange configuration option. 284 | This value is used to specify the range of the color scale used to determine the fill colors of the hexbins. 285 | 286 | There are a lot of different ways you can specify the color range. 287 | One option is monotone: ```[ '#f7fbff', '#f7fbff' ]```. 288 | The most common option is a linear transition between two colors: ```[ '#f7fbff', '#08306b' ]``` 289 | You can also create divering and discrete color scales by providing more than two values (see the examples for details). 290 | 291 | 292 | #### hexbinLayer.radiusRange(value?: [ number, number ]) 293 | Setter/getter for the radiusRange configuration option. 294 | This value is used to specify the range of the radius scale used to size the drawn hexbins. 295 | This is relevant if you are providing a custom ```radiusValue``` function and want to specify the minimum and maximum drawn hexbin size. 296 | 297 | *Note:* Overriding this value will have no effect unless you provide a custom ```radiusValue``` function. 298 | 299 | 300 | #### hexbinLayer.colorScale(value?: d3.scale) 301 | Default: d3.scaleLinear - Setter/getter for the d3 scale used to map the color of each hexbin from the color value. 302 | If you override the scale, the color range will be ignored. 303 | 304 | 305 | #### hexbinLayer.radiusScale(value?: d3.scale) 306 | Default: d3.scaleLinear - Setter/getter for the d3 scale used to map the radius of each hexbin from the radius value. 307 | If you override the scale, the radius range will be ignored. 308 | 309 | 310 | #### hexbinLayer.lng(value?: function(d, i) {}) 311 | Default: function(d) { return d[0]; } - Setter/getter for the function used to derive the value of the longitude for each object in the data array. 312 | 313 | 314 | #### hexbinLayer.lat(value?: function(d, i) {}) 315 | Default: function(d) { return d[1]; } - Setter/getter for the function used to derive the value of the latitude for each object in the data array. 316 | 317 | 318 | #### hexbinLayer.colorValue(value?: function(d, i) {}) 319 | Default: function(d) { return d.length; } - Setter/getter for the function used to derive the value of the color for each object in the data array. 320 | 321 | 322 | #### hexbinLayer.radiusValue(value?: function(d, i) {}) 323 | Default: function(d) { return 1; } - Setter/getter for the function used to derive the value of the radius for each object in the data array. 324 | 325 | 326 | #### hexbinLayer.fill(value?: function(d, i) {}) 327 | Setter/getter for the function used to derive the fill for each hexbin given the object generated by the d3 hexbin layout. 328 | The default fill function will simply map to the colorScale, but use 'none' when there is an undefined or null value. 329 | 330 | 331 | #### hexbinLayer.dispatch() 332 | Getter for the d3 dispatch object that exposes mouse events for the hexbins. 333 | 334 | Example: 335 | ```js 336 | hexLayer.dispatch() 337 | .on('mouseover', function(d, i) { 338 | console.log({ type: 'mouseover', event: d, index: i, context: this }); 339 | }) 340 | .on('mouseout', function(d, i) { 341 | console.log({ type: 'mouseout', event: d, index: i, context: this }); 342 | }) 343 | .on('click', function(d, i) { 344 | console.log({ type: 'click', event: d, index: i, context: this }); 345 | }); 346 | ``` 347 | 348 | #### hexbinLayer.hoverHandler() 349 | Default: None - Setter/getter for the hover behavior. 350 | 351 | Hover handlers help you customize hexbin hover behavior. 352 | Examples include growing the size of the hexbin in various ways or showing a tooltip. 353 | If the hover handlers don't do what you want, you can always handle events yourself or implement your own hover handler. 354 | For more details, see the section on hover handlers below. 355 | 356 | 357 | ### L.HexbinHoverHandler 358 | All handlers are under the ```L.HexbinHoverHandler``` namespace. 359 | 360 | Handlers are created as follows: 361 | 362 | ```js 363 | L.HexbinHoverHandler.ResizeFill(options) 364 | ``` 365 | 366 | Here's a basic example: 367 | ```js 368 | // Create a hexbin layer where hexbins that are hovered will grow from their 369 | // current radius up to the maximum radius (in this case 11) 370 | var hexLayer = L.hexbinLayer({ duration: 400, radiusRange: [ 5, 11 ] }) 371 | .radiusValue(function(d) { return d.length; }) 372 | .hoverHandler(L.HexbinHoverHandler.resizeFill()); 373 | ``` 374 | 375 | You can combine hover handlers with CSS to achieve interesting effects. 376 | The above hover handler combined with the below CSS will grow hexbins on hover using a smooth animation 377 | and will change the stroke to orange. 378 | 379 | ```css 380 | .hexbin-container:hover .hexbin-hexagon { 381 | transition: 200ms; 382 | stroke: orange; 383 | stroke-width: 1px; 384 | stroke-opacity: 1; 385 | } 386 | ``` 387 | 388 | 389 | There are several provided hover handlers and they each may take options. They are described below. 390 | 391 | #### HoverHandler.tooltip(options) 392 | Shows a basic tooltip centered above the hexbin. 393 | 394 | Options: 395 | * **tooltipContent** - function(d) { return 'tooltip content here'; } 396 | 397 | 398 | #### HoverHandler.resizeFill() 399 | Resize the hovered hexagon to fill the current hexbin. 400 | 401 | No options required. 402 | 403 | #### HoverHandler.resizeScale(options) 404 | Resize the hovered hexagon by scaling it to be a percentage larger than the maximum drawn hexagon radius. 405 | 406 | Options: 407 | * **radiusScale** - provides the scale factor by which to increase the radius of the hexbin. Example: ```radiusScale: 0.5``` will result in a hovered hexbin radius that is 50% larger than the maximum hexagon radius. 408 | 409 | #### HoverHandler.compound(options) 410 | Combine multiple hover handlers. 411 | 412 | Options: 413 | * **handlers** - Array of hover handlers to combine. 414 | 415 | #### Customizing your own Hover Handler 416 | If you want to implement (or contribute) your own hover handler, the interface is pretty simple: 417 | 418 | ```js 419 | L.HexbinHoverHandler.myHoverHandler = function() { 420 | 421 | // return the handler instance 422 | return { 423 | mouseover: function (hexLayer, data) { 424 | // hexLayer - reference to the L.HexbinLayer instance 425 | // data - reference to the data bound to the hovered hexbin 426 | // this - D3 wrapped DOM element for hovered hexbin 427 | }, 428 | mouseout: function (hexLayer, data) {} 429 | }; 430 | 431 | }; 432 | ``` 433 | 434 | ## Pings API 435 | 436 | ### L.pingLayer(options?: {}): L.PingLayer 437 | Create a Leaflet map layer for visualizing transient event data using animated expanding circles. 438 | 439 | ```js 440 | var pingLayer = L.pingLayer().addTo(map); 441 | ``` 442 | 443 | ### options 444 | Set of options for customizing the appearance/behavior of the ping layer. 445 | 446 | Example: 447 | 448 | ```js 449 | var options = { 450 | duration: 800, 451 | fps : 32, 452 | opacityRange: [ 1, 0 ], 453 | radiusRange: [ 5, 12 ] 454 | }; 455 | ``` 456 | 457 | #### duration 458 | Default: 800 - Sets the transition duration for the ping layer (see below for details). 459 | 460 | #### fps 461 | Default: 32 - Sets the target framerate for the ping animation (see below for details). 462 | 463 | #### opacityRange 464 | Default: [ 1, 0 ] - Sets the range of the opacity scale used to fade out the pings as they age (see below for details). 465 | 466 | #### radiusRange 467 | Default: [ 3, 15 ] - Sets the range of the radius scale used to size the pings as they age (see below for details). 468 | 469 | 470 | ### L.PingLayer 471 | 472 | #### pingLayer.ping(value: {}) 473 | Submit a ping to the layer. 474 | The default data schema for the ping layer is: 475 | 476 | ```[ [ lng1, lat1 ], [ lng2, lat2 ], [ lng3, lat3 ]... [ lngN, latN ] ]``` 477 | 478 | Where the ping radius scale factor is fixed at 1. 479 | 480 | 481 | #### pingLayer.duration(value?: number) 482 | Setter/getter for the durations configuration option. 483 | The millisecond duration of each ping's lifecycle. 484 | 485 | This value should be a non-negative number. 486 | The pings will grow from their initial size/opacity to their final size/opacity over this period of time. 487 | After this duration, the pings are removed from the map. 488 | 489 | 490 | #### pingLayer.fps(value?: number) 491 | Setter/getter for the fps configuration option. 492 | The animation loop will limit DOM changes to this frequency in order to reduce the impact to the CPU. 493 | 494 | 495 | #### pingLayer.radiusRange(value?: [ number, number ]) 496 | Setter/getter for the radiusRange configuration option. 497 | The start/end radius applied during each ping's lifecycle. 498 | 499 | This value should be a tuple of size two. 500 | The pings will start at a pixel radius equal to the first number in the tuple and animate to the second number in the tuple. 501 | 502 | 503 | #### pingLayer.opacityRange(value?: [ number, number ]) 504 | Setter/getter for the opacityRange configuration option. 505 | The start/end opacity state applied during each ping's lifecycle. 506 | 507 | This value should be a tuple of size two. 508 | The pings will start at an opacity equal to the first number in the tuple and animate to the second number in the tuple. 509 | 510 | 511 | #### pingLayer.radiusScale(value?: d3.scale) 512 | Default: d3.scalePow().exponent(0.35) - Setter/getter for the scale used to determine the radius during the ping lifecycle. 513 | If you override the scale, the radius range will be ignored. 514 | 515 | 516 | #### pingLayer.opacityScale(value?: d3.scale) 517 | Default: d3.scaleLinear() - Setter/getter for the scale used to determine the opacity during the ping lifecycle. 518 | If you override the scale, the opacity range will be ignored. 519 | 520 | #### pingLayer.radiusScaleFactor(function(d) {}) 521 | Default: function(d) { return 1; } - Setter/getter for the scale factor applied to the radius of each ping. 522 | This can be used to differentiate different events by size. 523 | 524 | 525 | #### pingLayer.lng(value?: function(d) {}) 526 | Default: function(d) { return d[0]; } - Setter/getter for the function used to derive the value of the longitude for each object in the data array. 527 | 528 | 529 | #### pingLayer.lat(value?: function(d) {}) 530 | Default: function(d) { return d[1]; } - Setter/getter for the function used to derive the value of the latitude for each object in the data array. 531 | 532 | 533 | #### pingLayer.data() 534 | Getter for the set of currently alive pings. 535 | 536 | 537 | #### pingLayer.getActualFps() 538 | Getter for the actual fps (based on the actual time between the last two animation frames). 539 | 540 | 541 | ## Changelog 542 | 543 | ### Version 6.x 544 | - Upgrade to d3 v7 545 | 546 | ### Version 5.x 547 | Skipped 548 | 549 | ### Version 4.x 550 | 551 | #### 4.4.0 552 | - Minor version updates for d3 and leaflet. 553 | 554 | #### 4.2.0 555 | - Corrected the algorithm that filters out hexbins to avoid drawing those that fall outside of the visible bounds of the map. 556 | 557 | #### 4.1.0 558 | - Added Hexbin Layer options for colorDomain and radiusDomain. See README docs for details. 559 | 560 | #### 4.0.0 561 | - D3 v5: We now support D3 v5. Only minor changes were required. 562 | - Fix for Leaflet > 1.2 Mixins Deprecation Warning: Updated the events include reference to remove the warning about using L.Mixins.Events 563 | - Fixes for Hover Handlers Data and Tooltip positions: See Issue #45 and #50, thanks @ninio for finding these. 564 | - Migrated to npm run based build: Not really an external facing thing, but matters if you're trying to build the library. 565 | 566 | 567 | ### Version 3.x 568 | 569 | #### Dropping Support for Leaflet 0.7.x 570 | We added some functionality that made it hard to backward support Leaflet 0.7.x. 571 | You can continue to use our 2.x release for Leaflet 0.7.x support, but it's unlikely that branch will see any further development/releases. 572 | This is the primary reason for increasing the major version as this is the only change that is not backwards compatible. 573 | 574 | #### Hexbins/Pings now Zoom with Map Before Redrawing 575 | We switched the HebinLayer to extend the built-in Leaflet SVG layer. 576 | This provides a bunch of advantages, including that the SVG layer is zoom-transformed with the other layers on the map. 577 | This allowed us to leave the hexbins on the map until the zoom animation is complete, at which point the hexbin grid is recalculated and redrawn. 578 | 579 | #### Automatic Filtering of Off-Map Hexbins 580 | We updated the Hexbin layer code to automatically filter out points that fall outside of the visible bounds of the map. 581 | This _dramatically_ improves performance at high zoom levels where we used to draw A LOT of paths off the map for no reason. 582 | 583 | #### Built-in Support for Tooltips and Other Hover Events 584 | While you could always manually manage a tooltip or some kind of hexbin hover event, we've added some code to make it easier. 585 | Now, you can pretty easily enable some built-in tooltip/highlight behavior or implement your own. 586 | See the API docs on HoverHandlers and the advanced hexbin example for details. 587 | 588 | 589 | 590 | ### Version 2.x 591 | 592 | #### Lots of API changes 593 | Read through the API changes. 594 | A lot of things were moved from config options to being configurable via chained function call. 595 | 596 | #### You can now bind data to the radius of Hexbins! 597 | You can now provide a radius value function to map a dimension of your data to the size of each hexbin. 598 | 599 | #### We changed the Hexbin event dispatch 600 | For Hexbins, we've changed the way that events are handled. Previously, you provided callback methods. 601 | Now, we expose a d3 dispatch object: 602 | 603 | ```js 604 | ... 605 | var hexLayer = L.hexbinLayer(options).addTo(map); 606 | 607 | // Set up events 608 | hexLayer.dispatch() 609 | .on('mouseover', function(d, i) { }) 610 | .on('mouseout', function(d, i) { }) 611 | .on('click', function(d, i) { }); 612 | ``` 613 | 614 | #### Pings now track the map when panning! 615 | We've changed pings so that they track the map when as it pans. 616 | The pan changes are applied immediately even when manually setting a low fps. 617 | 618 | #### You can now size pings independently using a scale factor callback function 619 | We've added a configurable option (pingLayer.radiusScaleFactor(...)) to provide a function that returns a data element-specific scale factor for the ping radius. 620 | 621 | 622 | ## Contribute 623 | PRs accepted. If you are part of Asymmetrik, please make contributions on feature branches off of the ```develop``` branch. If you are outside of Asymmetrik, please fork our repo to make contributions. 624 | 625 | 626 | ## License 627 | See LICENSE in repository for details. 628 | 629 | 630 | ## Credits 631 | The hexbin portion of this plugin was based on [the work of Steven Hall](http://www.delimited.io/blog/2013/12/1/hexbins-with-d3-and-leaflet-maps). Check out his other awesome work at [Delimited](http://www.delimited.io/) 632 | 633 | D3.js was created by the legendary [Mike Bostock](https://github.com/mbostock). 634 | 635 | [Leaflet](http://leafletjs.com/) is maintained by [lots of cool people](https://github.com/Leaflet/Leaflet/graphs/contributors). 636 | 637 | 638 | [travis-url]: https://travis-ci.org/Asymmetrik/leaflet-d3/ 639 | [travis-image]: https://travis-ci.org/Asymmetrik/leaflet-d3.svg 640 | -------------------------------------------------------------------------------- /examples/hexbins/advanced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 HexBin Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 33 | 34 | 35 | 36 |

Leaflet d3 Hexbin Example

37 |

Demonstrates advanced functionality

38 | 39 | 40 | 41 |
42 |
43 | 44 | 45 | 46 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /examples/hexbins/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 HexBin Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 |

Leaflet d3 Hexbin Example

24 |

Demonstrates basic functionality

25 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /examples/hexbins/colors_discrete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 HexBin Layer - Discrete Colors 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 |

Leaflet d3 Hexbin Example With Discrete Colors

24 |

red: 1-2, orange: 3-4, yellow: 5-6, green: 7-8, blue: 9+

25 | 26 | 27 |
28 | 29 | 30 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /examples/hexbins/colors_divergent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 HexBin Layer - Divergent Color Scale 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 | 29 | 30 |

Leaflet d3 Hexbin Example With Divergent Color Scale

31 |
32 |
33 | 34 | 35 |
36 | 37 | 38 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /examples/hexbins/custom_scale.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 HexBin Layer - Discrete Colors 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 |

Leaflet d3 Hexbin Example With Custom Color Scale

24 | 25 | 26 |
27 | 28 | 29 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /examples/hexbins/events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 HexBin Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 |

Leaflet d3 Hexbin Example with Event Handling

24 |

Demonstrates use of events

25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 | 33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 |
41 | 42 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /examples/hexbins/multi_layers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 HexBin Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 |

Leaflet d3 Hexbin Example

24 |

Demonstrates using multiple layers

25 | 26 | 27 |
28 |
29 | 30 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /examples/hexbins/realtime.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 HexBin Layer - Continuous Modification 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 |

Leaflet d3 Hexbin Example

24 |

Demonstrates continuously changing data

25 | 26 | 27 |
28 | 29 | 30 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /examples/pings/advanced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 Ping Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /examples/pings/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 Ping Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 |
23 | 24 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/pings/efficient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 Ping Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 |
23 | 24 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/pings/high.volume.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet d3 Ping Layer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 | fps:
23 | count:
24 |
25 | 26 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as L from 'leaflet'; 2 | 3 | declare module 'leaflet' { 4 | 5 | namespace internal { 6 | 7 | type SimpleFn = () => T; 8 | type ObjectFn = (d: any) => T; 9 | type ObjectIndexFn = (d: any, i: number) => T; 10 | type UnionFn = SimpleFn | ObjectFn | ObjectIndexFn; 11 | 12 | type SimpleCallback = SimpleFn; 13 | type ObjectCallback = ObjectFn; 14 | type ObjectContextCallback = (d: any, t: any) => void; 15 | type DoubleObjectContextCallback = (d: any, t: any, th: any) => void; 16 | type UnionCallback = SimpleCallback | ObjectCallback | ObjectContextCallback | DoubleObjectContextCallback; 17 | 18 | } 19 | 20 | /* 21 | * Hexbins 22 | */ 23 | interface HexbinLayer extends L.Layer { 24 | radius(): number; 25 | radius(v: number): this; 26 | 27 | opacity(): number; 28 | opacity(v: number): this; 29 | 30 | duration(): number; 31 | duration(v: number): this; 32 | 33 | colorScaleExtent(): [ number, number ]; 34 | colorScaleExtent(v: [ number, number ]): this; 35 | 36 | radiusScaleExtent(): [ number, number ]; 37 | radiusScaleExtent(v: [ number, number ]): this; 38 | 39 | colorRange(): string[]; 40 | colorRange(v: string[]): this; 41 | 42 | radiusRange(): number[]; 43 | radiusRange(v: number[]): this; 44 | 45 | colorScale(): any; 46 | colorScale(v: any): this; 47 | 48 | radiusScale(): any; 49 | radiusScale(v: any): this; 50 | 51 | lng(): internal.ObjectFn; 52 | lng(v: internal.ObjectFn): this; 53 | 54 | lat(): internal.ObjectFn; 55 | lat(v: internal.ObjectFn): this; 56 | 57 | colorValue(): internal.ObjectFn; 58 | colorValue(v: internal.ObjectFn): this; 59 | 60 | radiusValue(): internal.ObjectFn; 61 | radiusValue(v: internal.ObjectFn): this; 62 | 63 | fill(): internal.ObjectFn; 64 | fill(v: internal.ObjectFn): this; 65 | 66 | data(): any[]; 67 | data(v: any[]): this; 68 | 69 | dispatch(): any; 70 | 71 | hoverHandler(): HexbinHoverHandler; 72 | hoverHandler(v: HexbinHoverHandler): this; 73 | 74 | getLatLngs(): any[]; 75 | toGeoJSON(): any[]; 76 | 77 | redraw(): void; 78 | } 79 | 80 | interface HexbinLayerConfig { 81 | radius?: number, 82 | opacity?: number, 83 | duration?: number, 84 | 85 | colorScaleExtent?: [ number, number ], 86 | radiusScaleExtent?: [ number, number ], 87 | colorRange?: string[], 88 | radiusRange?: [ number, number ], 89 | 90 | pointerEvents?: string 91 | } 92 | 93 | interface HexbinHoverHandler { 94 | mouseover(hexLayer: HexbinLayer, data: any): void; 95 | mouseout(hexLayer: HexbinLayer, data: any): void; 96 | } 97 | 98 | namespace HexbinHoverHandler { 99 | 100 | interface TooltipHoverHandler extends HexbinHoverHandler {} 101 | interface TooltipOptions { 102 | tooltipContent: (d: any) => string; 103 | } 104 | function tooltip(v: TooltipOptions): TooltipHoverHandler; 105 | 106 | interface ResizeFillHoverHandler extends HexbinHoverHandler {} 107 | function resizeFill(): ResizeFillHoverHandler; 108 | 109 | interface ResizeScaleHoverHandler extends HexbinHoverHandler {} 110 | interface ResizeScaleOptions { 111 | radiusScale: number; 112 | } 113 | function resizeScale(v: ResizeScaleOptions): ResizeScaleHoverHandler; 114 | 115 | interface CompoundHoverHandler extends HexbinHoverHandler {} 116 | interface CompoundOptions { 117 | handlers: HexbinHoverHandler[]; 118 | } 119 | function compound(v: CompoundOptions): CompoundHoverHandler; 120 | 121 | interface NoneHoverHandler extends HexbinHoverHandler {} 122 | function none(): NoneHoverHandler; 123 | } 124 | 125 | function hexbinLayer(config?: HexbinLayerConfig): HexbinLayer; 126 | 127 | 128 | 129 | /* 130 | * Pings 131 | */ 132 | interface PingLayer extends L.Layer { 133 | duration(): number; 134 | duration(v: number): this; 135 | 136 | fps(): number; 137 | fps(v: number): this; 138 | 139 | lng(): internal.ObjectFn; 140 | lng(v: internal.ObjectFn): this; 141 | 142 | lat(): internal.ObjectFn; 143 | lat(v: internal.ObjectFn): this; 144 | 145 | radiusRange(): number[]; 146 | radiusRange(v: number[]): this; 147 | 148 | opacityRange(): number[]; 149 | opacityRange(v: number[]): this; 150 | 151 | radiusScale(): any; 152 | radiusScale(v: any): this; 153 | 154 | opacityScale(): any; 155 | opacityScale(v: any): this; 156 | 157 | radiusScaleFactor(): number; 158 | radiusScaleFactor(v: number): this; 159 | 160 | ping(data: any, cssClass?: string): this; 161 | 162 | getActualFps(): number; 163 | data(): any[]; 164 | 165 | } 166 | 167 | interface PingLayerConfig { 168 | duration?: number, 169 | fps?: number, 170 | opacityRange?: number[], 171 | radiusRange?: number[] 172 | } 173 | 174 | function pingLayer(config?: PingLayerConfig): PingLayer; 175 | 176 | } 177 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@asymmetrik/leaflet-d3", 3 | "version": "6.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@eslint/eslintrc": { 8 | "version": "1.1.0", 9 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", 10 | "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", 11 | "dev": true, 12 | "requires": { 13 | "ajv": "^6.12.4", 14 | "debug": "^4.3.2", 15 | "espree": "^9.3.1", 16 | "globals": "^13.9.0", 17 | "ignore": "^4.0.6", 18 | "import-fresh": "^3.2.1", 19 | "js-yaml": "^4.1.0", 20 | "minimatch": "^3.0.4", 21 | "strip-json-comments": "^3.1.1" 22 | }, 23 | "dependencies": { 24 | "ignore": { 25 | "version": "4.0.6", 26 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 27 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 28 | "dev": true 29 | } 30 | } 31 | }, 32 | "@humanwhocodes/config-array": { 33 | "version": "0.9.3", 34 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", 35 | "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==", 36 | "dev": true, 37 | "requires": { 38 | "@humanwhocodes/object-schema": "^1.2.1", 39 | "debug": "^4.1.1", 40 | "minimatch": "^3.0.4" 41 | } 42 | }, 43 | "@humanwhocodes/object-schema": { 44 | "version": "1.2.1", 45 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", 46 | "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", 47 | "dev": true 48 | }, 49 | "@types/d3": { 50 | "version": "7.1.0", 51 | "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.1.0.tgz", 52 | "integrity": "sha512-gYWvgeGjEl+zmF8c+U1RNIKqe7sfQwIXeLXO5Os72TjDjCEtgpvGBvZ8dXlAuSS1m6B90Y1Uo6Bm36OGR/OtCA==", 53 | "dev": true, 54 | "requires": { 55 | "@types/d3-array": "*", 56 | "@types/d3-axis": "*", 57 | "@types/d3-brush": "*", 58 | "@types/d3-chord": "*", 59 | "@types/d3-color": "*", 60 | "@types/d3-contour": "*", 61 | "@types/d3-delaunay": "*", 62 | "@types/d3-dispatch": "*", 63 | "@types/d3-drag": "*", 64 | "@types/d3-dsv": "*", 65 | "@types/d3-ease": "*", 66 | "@types/d3-fetch": "*", 67 | "@types/d3-force": "*", 68 | "@types/d3-format": "*", 69 | "@types/d3-geo": "*", 70 | "@types/d3-hierarchy": "*", 71 | "@types/d3-interpolate": "*", 72 | "@types/d3-path": "*", 73 | "@types/d3-polygon": "*", 74 | "@types/d3-quadtree": "*", 75 | "@types/d3-random": "*", 76 | "@types/d3-scale": "*", 77 | "@types/d3-scale-chromatic": "*", 78 | "@types/d3-selection": "*", 79 | "@types/d3-shape": "*", 80 | "@types/d3-time": "*", 81 | "@types/d3-time-format": "*", 82 | "@types/d3-timer": "*", 83 | "@types/d3-transition": "*", 84 | "@types/d3-zoom": "*" 85 | } 86 | }, 87 | "@types/d3-array": { 88 | "version": "3.0.2", 89 | "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.2.tgz", 90 | "integrity": "sha512-5mjGjz6XOXKOCdTajXTZ/pMsg236RdiwKPrRPWAEf/2S/+PzwY+LLYShUpeysWaMvsdS7LArh6GdUefoxpchsQ==", 91 | "dev": true 92 | }, 93 | "@types/d3-axis": { 94 | "version": "3.0.1", 95 | "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz", 96 | "integrity": "sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==", 97 | "dev": true, 98 | "requires": { 99 | "@types/d3-selection": "*" 100 | } 101 | }, 102 | "@types/d3-brush": { 103 | "version": "3.0.1", 104 | "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz", 105 | "integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==", 106 | "dev": true, 107 | "requires": { 108 | "@types/d3-selection": "*" 109 | } 110 | }, 111 | "@types/d3-chord": { 112 | "version": "3.0.1", 113 | "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz", 114 | "integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==", 115 | "dev": true 116 | }, 117 | "@types/d3-color": { 118 | "version": "3.0.2", 119 | "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.0.2.tgz", 120 | "integrity": "sha512-WVx6zBiz4sWlboCy7TCgjeyHpNjMsoF36yaagny1uXfbadc9f+5BeBf7U+lRmQqY3EHbGQpP8UdW8AC+cywSwQ==", 121 | "dev": true 122 | }, 123 | "@types/d3-contour": { 124 | "version": "3.0.1", 125 | "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz", 126 | "integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==", 127 | "dev": true, 128 | "requires": { 129 | "@types/d3-array": "*", 130 | "@types/geojson": "*" 131 | } 132 | }, 133 | "@types/d3-delaunay": { 134 | "version": "6.0.0", 135 | "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.0.tgz", 136 | "integrity": "sha512-iGm7ZaGLq11RK3e69VeMM6Oqj2SjKUB9Qhcyd1zIcqn2uE8w9GFB445yCY46NOQO3ByaNyktX1DK+Etz7ZaX+w==", 137 | "dev": true 138 | }, 139 | "@types/d3-dispatch": { 140 | "version": "3.0.1", 141 | "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz", 142 | "integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==", 143 | "dev": true 144 | }, 145 | "@types/d3-drag": { 146 | "version": "3.0.1", 147 | "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz", 148 | "integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==", 149 | "dev": true, 150 | "requires": { 151 | "@types/d3-selection": "*" 152 | } 153 | }, 154 | "@types/d3-dsv": { 155 | "version": "3.0.0", 156 | "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz", 157 | "integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==", 158 | "dev": true 159 | }, 160 | "@types/d3-ease": { 161 | "version": "3.0.0", 162 | "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", 163 | "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", 164 | "dev": true 165 | }, 166 | "@types/d3-fetch": { 167 | "version": "3.0.1", 168 | "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz", 169 | "integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==", 170 | "dev": true, 171 | "requires": { 172 | "@types/d3-dsv": "*" 173 | } 174 | }, 175 | "@types/d3-force": { 176 | "version": "3.0.3", 177 | "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz", 178 | "integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==", 179 | "dev": true 180 | }, 181 | "@types/d3-format": { 182 | "version": "3.0.1", 183 | "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", 184 | "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", 185 | "dev": true 186 | }, 187 | "@types/d3-geo": { 188 | "version": "3.0.2", 189 | "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz", 190 | "integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==", 191 | "dev": true, 192 | "requires": { 193 | "@types/geojson": "*" 194 | } 195 | }, 196 | "@types/d3-hierarchy": { 197 | "version": "3.0.2", 198 | "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.0.2.tgz", 199 | "integrity": "sha512-+krnrWOZ+aQB6v+E+jEkmkAx9HvsNAD+1LCD0vlBY3t+HwjKnsBFbpVLx6WWzDzCIuiTWdAxXMEnGnVXpB09qQ==", 200 | "dev": true 201 | }, 202 | "@types/d3-interpolate": { 203 | "version": "3.0.1", 204 | "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", 205 | "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", 206 | "dev": true, 207 | "requires": { 208 | "@types/d3-color": "*" 209 | } 210 | }, 211 | "@types/d3-path": { 212 | "version": "3.0.0", 213 | "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", 214 | "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", 215 | "dev": true 216 | }, 217 | "@types/d3-polygon": { 218 | "version": "3.0.0", 219 | "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", 220 | "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", 221 | "dev": true 222 | }, 223 | "@types/d3-quadtree": { 224 | "version": "3.0.2", 225 | "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", 226 | "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", 227 | "dev": true 228 | }, 229 | "@types/d3-random": { 230 | "version": "3.0.1", 231 | "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", 232 | "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", 233 | "dev": true 234 | }, 235 | "@types/d3-scale": { 236 | "version": "4.0.2", 237 | "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", 238 | "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", 239 | "dev": true, 240 | "requires": { 241 | "@types/d3-time": "*" 242 | } 243 | }, 244 | "@types/d3-scale-chromatic": { 245 | "version": "3.0.0", 246 | "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", 247 | "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", 248 | "dev": true 249 | }, 250 | "@types/d3-selection": { 251 | "version": "3.0.2", 252 | "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.2.tgz", 253 | "integrity": "sha512-d29EDd0iUBrRoKhPndhDY6U/PYxOWqgIZwKTooy2UkBfU7TNZNpRho0yLWPxlatQrFWk2mnTu71IZQ4+LRgKlQ==", 254 | "dev": true 255 | }, 256 | "@types/d3-shape": { 257 | "version": "3.0.2", 258 | "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.0.2.tgz", 259 | "integrity": "sha512-5+ButCmIfNX8id5seZ7jKj3igdcxx+S9IDBiT35fQGTLZUfkFgTv+oBH34xgeoWDKpWcMITSzBILWQtBoN5Piw==", 260 | "dev": true, 261 | "requires": { 262 | "@types/d3-path": "*" 263 | } 264 | }, 265 | "@types/d3-time": { 266 | "version": "3.0.0", 267 | "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", 268 | "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", 269 | "dev": true 270 | }, 271 | "@types/d3-time-format": { 272 | "version": "4.0.0", 273 | "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", 274 | "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", 275 | "dev": true 276 | }, 277 | "@types/d3-timer": { 278 | "version": "3.0.0", 279 | "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", 280 | "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", 281 | "dev": true 282 | }, 283 | "@types/d3-transition": { 284 | "version": "3.0.1", 285 | "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.1.tgz", 286 | "integrity": "sha512-Sv4qEI9uq3bnZwlOANvYK853zvpdKEm1yz9rcc8ZTsxvRklcs9Fx4YFuGA3gXoQN/c/1T6QkVNjhaRO/cWj94g==", 287 | "dev": true, 288 | "requires": { 289 | "@types/d3-selection": "*" 290 | } 291 | }, 292 | "@types/d3-zoom": { 293 | "version": "3.0.1", 294 | "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", 295 | "integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==", 296 | "dev": true, 297 | "requires": { 298 | "@types/d3-interpolate": "*", 299 | "@types/d3-selection": "*" 300 | } 301 | }, 302 | "@types/geojson": { 303 | "version": "7946.0.8", 304 | "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", 305 | "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==", 306 | "dev": true 307 | }, 308 | "@types/leaflet": { 309 | "version": "1.7.9", 310 | "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.7.9.tgz", 311 | "integrity": "sha512-H8vPgD49HKzqM41ArHGZM70g/tfhp8W+JcPxfnF+5H/Xvp+xiP+KQOUNWU8U89fqS1Jj3cpRY/+nbnaHFzwnFA==", 312 | "dev": true, 313 | "requires": { 314 | "@types/geojson": "*" 315 | } 316 | }, 317 | "acorn": { 318 | "version": "8.7.0", 319 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", 320 | "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", 321 | "dev": true 322 | }, 323 | "acorn-jsx": { 324 | "version": "5.3.2", 325 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 326 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 327 | "dev": true 328 | }, 329 | "ajv": { 330 | "version": "6.12.6", 331 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 332 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 333 | "dev": true, 334 | "requires": { 335 | "fast-deep-equal": "^3.1.1", 336 | "fast-json-stable-stringify": "^2.0.0", 337 | "json-schema-traverse": "^0.4.1", 338 | "uri-js": "^4.2.2" 339 | } 340 | }, 341 | "ansi-regex": { 342 | "version": "5.0.1", 343 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 344 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 345 | "dev": true 346 | }, 347 | "ansi-styles": { 348 | "version": "4.3.0", 349 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 350 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 351 | "dev": true, 352 | "requires": { 353 | "color-convert": "^2.0.1" 354 | } 355 | }, 356 | "argparse": { 357 | "version": "2.0.1", 358 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 359 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 360 | "dev": true 361 | }, 362 | "balanced-match": { 363 | "version": "1.0.2", 364 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 365 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 366 | "dev": true 367 | }, 368 | "brace-expansion": { 369 | "version": "1.1.11", 370 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 371 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 372 | "dev": true, 373 | "requires": { 374 | "balanced-match": "^1.0.0", 375 | "concat-map": "0.0.1" 376 | } 377 | }, 378 | "callsites": { 379 | "version": "3.1.0", 380 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 381 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 382 | "dev": true 383 | }, 384 | "chalk": { 385 | "version": "4.1.2", 386 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 387 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 388 | "dev": true, 389 | "requires": { 390 | "ansi-styles": "^4.1.0", 391 | "supports-color": "^7.1.0" 392 | } 393 | }, 394 | "color-convert": { 395 | "version": "2.0.1", 396 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 397 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 398 | "dev": true, 399 | "requires": { 400 | "color-name": "~1.1.4" 401 | } 402 | }, 403 | "color-name": { 404 | "version": "1.1.4", 405 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 406 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 407 | "dev": true 408 | }, 409 | "commander": { 410 | "version": "7.2.0", 411 | "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", 412 | "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", 413 | "dev": true 414 | }, 415 | "concat-map": { 416 | "version": "0.0.1", 417 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 418 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 419 | "dev": true 420 | }, 421 | "cross-spawn": { 422 | "version": "7.0.3", 423 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 424 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 425 | "dev": true, 426 | "requires": { 427 | "path-key": "^3.1.0", 428 | "shebang-command": "^2.0.0", 429 | "which": "^2.0.1" 430 | } 431 | }, 432 | "d3": { 433 | "version": "7.3.0", 434 | "resolved": "https://registry.npmjs.org/d3/-/d3-7.3.0.tgz", 435 | "integrity": "sha512-MDRLJCMK232OJQRqGljQ/gCxtB8k3/sLKFjftMjzPB3nKVUODpdW9Rb3vcq7U8Ka5YKoZkAmp++Ur6I+6iNWIw==", 436 | "dev": true, 437 | "requires": { 438 | "d3-array": "3", 439 | "d3-axis": "3", 440 | "d3-brush": "3", 441 | "d3-chord": "3", 442 | "d3-color": "3", 443 | "d3-contour": "3", 444 | "d3-delaunay": "6", 445 | "d3-dispatch": "3", 446 | "d3-drag": "3", 447 | "d3-dsv": "3", 448 | "d3-ease": "3", 449 | "d3-fetch": "3", 450 | "d3-force": "3", 451 | "d3-format": "3", 452 | "d3-geo": "3", 453 | "d3-hierarchy": "3", 454 | "d3-interpolate": "3", 455 | "d3-path": "3", 456 | "d3-polygon": "3", 457 | "d3-quadtree": "3", 458 | "d3-random": "3", 459 | "d3-scale": "4", 460 | "d3-scale-chromatic": "3", 461 | "d3-selection": "3", 462 | "d3-shape": "3", 463 | "d3-time": "3", 464 | "d3-time-format": "4", 465 | "d3-timer": "3", 466 | "d3-transition": "3", 467 | "d3-zoom": "3" 468 | } 469 | }, 470 | "d3-array": { 471 | "version": "3.1.1", 472 | "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.1.1.tgz", 473 | "integrity": "sha512-33qQ+ZoZlli19IFiQx4QEpf2CBEayMRzhlisJHSCsSUbDXv6ZishqS1x7uFVClKG4Wr7rZVHvaAttoLow6GqdQ==", 474 | "dev": true, 475 | "requires": { 476 | "internmap": "1 - 2" 477 | } 478 | }, 479 | "d3-axis": { 480 | "version": "3.0.0", 481 | "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", 482 | "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", 483 | "dev": true 484 | }, 485 | "d3-brush": { 486 | "version": "3.0.0", 487 | "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", 488 | "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", 489 | "dev": true, 490 | "requires": { 491 | "d3-dispatch": "1 - 3", 492 | "d3-drag": "2 - 3", 493 | "d3-interpolate": "1 - 3", 494 | "d3-selection": "3", 495 | "d3-transition": "3" 496 | } 497 | }, 498 | "d3-chord": { 499 | "version": "3.0.1", 500 | "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", 501 | "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", 502 | "dev": true, 503 | "requires": { 504 | "d3-path": "1 - 3" 505 | } 506 | }, 507 | "d3-color": { 508 | "version": "3.0.1", 509 | "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.0.1.tgz", 510 | "integrity": "sha512-6/SlHkDOBLyQSJ1j1Ghs82OIUXpKWlR0hCsw0XrLSQhuUPuCSmLQ1QPH98vpnQxMUQM2/gfAkUEWsupVpd9JGw==", 511 | "dev": true 512 | }, 513 | "d3-contour": { 514 | "version": "3.0.1", 515 | "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-3.0.1.tgz", 516 | "integrity": "sha512-0Oc4D0KyhwhM7ZL0RMnfGycLN7hxHB8CMmwZ3+H26PWAG0ozNuYG5hXSDNgmP1SgJkQMrlG6cP20HoaSbvcJTQ==", 517 | "dev": true, 518 | "requires": { 519 | "d3-array": "2 - 3" 520 | } 521 | }, 522 | "d3-delaunay": { 523 | "version": "6.0.2", 524 | "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", 525 | "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", 526 | "dev": true, 527 | "requires": { 528 | "delaunator": "5" 529 | } 530 | }, 531 | "d3-dispatch": { 532 | "version": "3.0.1", 533 | "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", 534 | "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", 535 | "dev": true 536 | }, 537 | "d3-drag": { 538 | "version": "3.0.0", 539 | "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", 540 | "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", 541 | "dev": true, 542 | "requires": { 543 | "d3-dispatch": "1 - 3", 544 | "d3-selection": "3" 545 | } 546 | }, 547 | "d3-dsv": { 548 | "version": "3.0.1", 549 | "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", 550 | "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", 551 | "dev": true, 552 | "requires": { 553 | "commander": "7", 554 | "iconv-lite": "0.6", 555 | "rw": "1" 556 | } 557 | }, 558 | "d3-ease": { 559 | "version": "3.0.1", 560 | "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", 561 | "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", 562 | "dev": true 563 | }, 564 | "d3-fetch": { 565 | "version": "3.0.1", 566 | "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", 567 | "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", 568 | "dev": true, 569 | "requires": { 570 | "d3-dsv": "1 - 3" 571 | } 572 | }, 573 | "d3-force": { 574 | "version": "3.0.0", 575 | "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", 576 | "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", 577 | "dev": true, 578 | "requires": { 579 | "d3-dispatch": "1 - 3", 580 | "d3-quadtree": "1 - 3", 581 | "d3-timer": "1 - 3" 582 | } 583 | }, 584 | "d3-format": { 585 | "version": "3.1.0", 586 | "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", 587 | "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", 588 | "dev": true 589 | }, 590 | "d3-geo": { 591 | "version": "3.0.1", 592 | "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", 593 | "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", 594 | "dev": true, 595 | "requires": { 596 | "d3-array": "2.5.0 - 3" 597 | } 598 | }, 599 | "d3-hexbin": { 600 | "version": "0.2.2", 601 | "resolved": "https://registry.npmjs.org/d3-hexbin/-/d3-hexbin-0.2.2.tgz", 602 | "integrity": "sha1-nFg32s/UcasFM3qeke8Qv8T5iDE=", 603 | "dev": true 604 | }, 605 | "d3-hierarchy": { 606 | "version": "3.1.1", 607 | "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.1.tgz", 608 | "integrity": "sha512-LtAIu54UctRmhGKllleflmHalttH3zkfSi4NlKrTAoFKjC+AFBJohsCAdgCBYQwH0F8hIOGY89X1pPqAchlMkA==", 609 | "dev": true 610 | }, 611 | "d3-interpolate": { 612 | "version": "3.0.1", 613 | "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", 614 | "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", 615 | "dev": true, 616 | "requires": { 617 | "d3-color": "1 - 3" 618 | } 619 | }, 620 | "d3-path": { 621 | "version": "3.0.1", 622 | "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", 623 | "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==", 624 | "dev": true 625 | }, 626 | "d3-polygon": { 627 | "version": "3.0.1", 628 | "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", 629 | "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", 630 | "dev": true 631 | }, 632 | "d3-quadtree": { 633 | "version": "3.0.1", 634 | "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", 635 | "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", 636 | "dev": true 637 | }, 638 | "d3-random": { 639 | "version": "3.0.1", 640 | "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", 641 | "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", 642 | "dev": true 643 | }, 644 | "d3-scale": { 645 | "version": "4.0.2", 646 | "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", 647 | "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", 648 | "dev": true, 649 | "requires": { 650 | "d3-array": "2.10.0 - 3", 651 | "d3-format": "1 - 3", 652 | "d3-interpolate": "1.2.0 - 3", 653 | "d3-time": "2.1.1 - 3", 654 | "d3-time-format": "2 - 4" 655 | } 656 | }, 657 | "d3-scale-chromatic": { 658 | "version": "3.0.0", 659 | "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", 660 | "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", 661 | "dev": true, 662 | "requires": { 663 | "d3-color": "1 - 3", 664 | "d3-interpolate": "1 - 3" 665 | } 666 | }, 667 | "d3-selection": { 668 | "version": "3.0.0", 669 | "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", 670 | "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", 671 | "dev": true 672 | }, 673 | "d3-shape": { 674 | "version": "3.1.0", 675 | "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", 676 | "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", 677 | "dev": true, 678 | "requires": { 679 | "d3-path": "1 - 3" 680 | } 681 | }, 682 | "d3-time": { 683 | "version": "3.0.0", 684 | "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", 685 | "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", 686 | "dev": true, 687 | "requires": { 688 | "d3-array": "2 - 3" 689 | } 690 | }, 691 | "d3-time-format": { 692 | "version": "4.1.0", 693 | "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", 694 | "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", 695 | "dev": true, 696 | "requires": { 697 | "d3-time": "1 - 3" 698 | } 699 | }, 700 | "d3-timer": { 701 | "version": "3.0.1", 702 | "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", 703 | "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", 704 | "dev": true 705 | }, 706 | "d3-transition": { 707 | "version": "3.0.1", 708 | "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", 709 | "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", 710 | "dev": true, 711 | "requires": { 712 | "d3-color": "1 - 3", 713 | "d3-dispatch": "1 - 3", 714 | "d3-ease": "1 - 3", 715 | "d3-interpolate": "1 - 3", 716 | "d3-timer": "1 - 3" 717 | } 718 | }, 719 | "d3-zoom": { 720 | "version": "3.0.0", 721 | "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", 722 | "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", 723 | "dev": true, 724 | "requires": { 725 | "d3-dispatch": "1 - 3", 726 | "d3-drag": "2 - 3", 727 | "d3-interpolate": "1 - 3", 728 | "d3-selection": "2 - 3", 729 | "d3-transition": "2 - 3" 730 | } 731 | }, 732 | "debug": { 733 | "version": "4.3.3", 734 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 735 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 736 | "dev": true, 737 | "requires": { 738 | "ms": "2.1.2" 739 | } 740 | }, 741 | "deep-is": { 742 | "version": "0.1.4", 743 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 744 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 745 | "dev": true 746 | }, 747 | "delaunator": { 748 | "version": "5.0.0", 749 | "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", 750 | "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", 751 | "dev": true, 752 | "requires": { 753 | "robust-predicates": "^3.0.0" 754 | } 755 | }, 756 | "doctrine": { 757 | "version": "3.0.0", 758 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 759 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 760 | "dev": true, 761 | "requires": { 762 | "esutils": "^2.0.2" 763 | } 764 | }, 765 | "escape-string-regexp": { 766 | "version": "4.0.0", 767 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 768 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 769 | "dev": true 770 | }, 771 | "eslint": { 772 | "version": "8.9.0", 773 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", 774 | "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", 775 | "dev": true, 776 | "requires": { 777 | "@eslint/eslintrc": "^1.1.0", 778 | "@humanwhocodes/config-array": "^0.9.2", 779 | "ajv": "^6.10.0", 780 | "chalk": "^4.0.0", 781 | "cross-spawn": "^7.0.2", 782 | "debug": "^4.3.2", 783 | "doctrine": "^3.0.0", 784 | "escape-string-regexp": "^4.0.0", 785 | "eslint-scope": "^7.1.1", 786 | "eslint-utils": "^3.0.0", 787 | "eslint-visitor-keys": "^3.3.0", 788 | "espree": "^9.3.1", 789 | "esquery": "^1.4.0", 790 | "esutils": "^2.0.2", 791 | "fast-deep-equal": "^3.1.3", 792 | "file-entry-cache": "^6.0.1", 793 | "functional-red-black-tree": "^1.0.1", 794 | "glob-parent": "^6.0.1", 795 | "globals": "^13.6.0", 796 | "ignore": "^5.2.0", 797 | "import-fresh": "^3.0.0", 798 | "imurmurhash": "^0.1.4", 799 | "is-glob": "^4.0.0", 800 | "js-yaml": "^4.1.0", 801 | "json-stable-stringify-without-jsonify": "^1.0.1", 802 | "levn": "^0.4.1", 803 | "lodash.merge": "^4.6.2", 804 | "minimatch": "^3.0.4", 805 | "natural-compare": "^1.4.0", 806 | "optionator": "^0.9.1", 807 | "regexpp": "^3.2.0", 808 | "strip-ansi": "^6.0.1", 809 | "strip-json-comments": "^3.1.0", 810 | "text-table": "^0.2.0", 811 | "v8-compile-cache": "^2.0.3" 812 | } 813 | }, 814 | "eslint-scope": { 815 | "version": "7.1.1", 816 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", 817 | "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", 818 | "dev": true, 819 | "requires": { 820 | "esrecurse": "^4.3.0", 821 | "estraverse": "^5.2.0" 822 | } 823 | }, 824 | "eslint-utils": { 825 | "version": "3.0.0", 826 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", 827 | "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", 828 | "dev": true, 829 | "requires": { 830 | "eslint-visitor-keys": "^2.0.0" 831 | }, 832 | "dependencies": { 833 | "eslint-visitor-keys": { 834 | "version": "2.1.0", 835 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", 836 | "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", 837 | "dev": true 838 | } 839 | } 840 | }, 841 | "eslint-visitor-keys": { 842 | "version": "3.3.0", 843 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", 844 | "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", 845 | "dev": true 846 | }, 847 | "espree": { 848 | "version": "9.3.1", 849 | "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", 850 | "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", 851 | "dev": true, 852 | "requires": { 853 | "acorn": "^8.7.0", 854 | "acorn-jsx": "^5.3.1", 855 | "eslint-visitor-keys": "^3.3.0" 856 | } 857 | }, 858 | "esquery": { 859 | "version": "1.4.0", 860 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", 861 | "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", 862 | "dev": true, 863 | "requires": { 864 | "estraverse": "^5.1.0" 865 | } 866 | }, 867 | "esrecurse": { 868 | "version": "4.3.0", 869 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 870 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 871 | "dev": true, 872 | "requires": { 873 | "estraverse": "^5.2.0" 874 | } 875 | }, 876 | "estraverse": { 877 | "version": "5.3.0", 878 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 879 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 880 | "dev": true 881 | }, 882 | "esutils": { 883 | "version": "2.0.3", 884 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 885 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 886 | "dev": true 887 | }, 888 | "exec-sh": { 889 | "version": "0.2.2", 890 | "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", 891 | "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", 892 | "dev": true, 893 | "requires": { 894 | "merge": "^1.2.0" 895 | } 896 | }, 897 | "fast-deep-equal": { 898 | "version": "3.1.3", 899 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 900 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 901 | "dev": true 902 | }, 903 | "fast-json-stable-stringify": { 904 | "version": "2.1.0", 905 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 906 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 907 | "dev": true 908 | }, 909 | "fast-levenshtein": { 910 | "version": "2.0.6", 911 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 912 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 913 | "dev": true 914 | }, 915 | "file-entry-cache": { 916 | "version": "6.0.1", 917 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 918 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 919 | "dev": true, 920 | "requires": { 921 | "flat-cache": "^3.0.4" 922 | } 923 | }, 924 | "flat-cache": { 925 | "version": "3.0.4", 926 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", 927 | "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", 928 | "dev": true, 929 | "requires": { 930 | "flatted": "^3.1.0", 931 | "rimraf": "^3.0.2" 932 | } 933 | }, 934 | "flatted": { 935 | "version": "3.2.5", 936 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", 937 | "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", 938 | "dev": true 939 | }, 940 | "fs.realpath": { 941 | "version": "1.0.0", 942 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 943 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 944 | "dev": true 945 | }, 946 | "fsevents": { 947 | "version": "2.3.2", 948 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 949 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 950 | "dev": true, 951 | "optional": true 952 | }, 953 | "functional-red-black-tree": { 954 | "version": "1.0.1", 955 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 956 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 957 | "dev": true 958 | }, 959 | "glob": { 960 | "version": "7.2.0", 961 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 962 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 963 | "dev": true, 964 | "requires": { 965 | "fs.realpath": "^1.0.0", 966 | "inflight": "^1.0.4", 967 | "inherits": "2", 968 | "minimatch": "^3.0.4", 969 | "once": "^1.3.0", 970 | "path-is-absolute": "^1.0.0" 971 | } 972 | }, 973 | "glob-parent": { 974 | "version": "6.0.2", 975 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 976 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 977 | "dev": true, 978 | "requires": { 979 | "is-glob": "^4.0.3" 980 | } 981 | }, 982 | "globals": { 983 | "version": "13.12.1", 984 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", 985 | "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", 986 | "dev": true, 987 | "requires": { 988 | "type-fest": "^0.20.2" 989 | } 990 | }, 991 | "has-flag": { 992 | "version": "4.0.0", 993 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 994 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 995 | "dev": true 996 | }, 997 | "iconv-lite": { 998 | "version": "0.6.3", 999 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 1000 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 1001 | "dev": true, 1002 | "requires": { 1003 | "safer-buffer": ">= 2.1.2 < 3.0.0" 1004 | } 1005 | }, 1006 | "ignore": { 1007 | "version": "5.2.0", 1008 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", 1009 | "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", 1010 | "dev": true 1011 | }, 1012 | "import-fresh": { 1013 | "version": "3.3.0", 1014 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 1015 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 1016 | "dev": true, 1017 | "requires": { 1018 | "parent-module": "^1.0.0", 1019 | "resolve-from": "^4.0.0" 1020 | } 1021 | }, 1022 | "imurmurhash": { 1023 | "version": "0.1.4", 1024 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1025 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 1026 | "dev": true 1027 | }, 1028 | "inflight": { 1029 | "version": "1.0.6", 1030 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1031 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 1032 | "dev": true, 1033 | "requires": { 1034 | "once": "^1.3.0", 1035 | "wrappy": "1" 1036 | } 1037 | }, 1038 | "inherits": { 1039 | "version": "2.0.4", 1040 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1041 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1042 | "dev": true 1043 | }, 1044 | "internmap": { 1045 | "version": "2.0.3", 1046 | "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", 1047 | "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", 1048 | "dev": true 1049 | }, 1050 | "is-extglob": { 1051 | "version": "2.1.1", 1052 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1053 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 1054 | "dev": true 1055 | }, 1056 | "is-glob": { 1057 | "version": "4.0.3", 1058 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1059 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1060 | "dev": true, 1061 | "requires": { 1062 | "is-extglob": "^2.1.1" 1063 | } 1064 | }, 1065 | "isexe": { 1066 | "version": "2.0.0", 1067 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1068 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 1069 | "dev": true 1070 | }, 1071 | "js-yaml": { 1072 | "version": "4.1.0", 1073 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1074 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1075 | "dev": true, 1076 | "requires": { 1077 | "argparse": "^2.0.1" 1078 | } 1079 | }, 1080 | "json-schema-traverse": { 1081 | "version": "0.4.1", 1082 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1083 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1084 | "dev": true 1085 | }, 1086 | "json-stable-stringify-without-jsonify": { 1087 | "version": "1.0.1", 1088 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1089 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", 1090 | "dev": true 1091 | }, 1092 | "leaflet": { 1093 | "version": "1.7.1", 1094 | "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", 1095 | "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==", 1096 | "dev": true 1097 | }, 1098 | "levn": { 1099 | "version": "0.4.1", 1100 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 1101 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 1102 | "dev": true, 1103 | "requires": { 1104 | "prelude-ls": "^1.2.1", 1105 | "type-check": "~0.4.0" 1106 | } 1107 | }, 1108 | "lodash.merge": { 1109 | "version": "4.6.2", 1110 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 1111 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 1112 | "dev": true 1113 | }, 1114 | "merge": { 1115 | "version": "1.2.1", 1116 | "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", 1117 | "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", 1118 | "dev": true 1119 | }, 1120 | "minimatch": { 1121 | "version": "3.1.2", 1122 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1123 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1124 | "dev": true, 1125 | "requires": { 1126 | "brace-expansion": "^1.1.7" 1127 | } 1128 | }, 1129 | "minimist": { 1130 | "version": "1.2.5", 1131 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 1132 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 1133 | "dev": true 1134 | }, 1135 | "ms": { 1136 | "version": "2.1.2", 1137 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1138 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1139 | "dev": true 1140 | }, 1141 | "natural-compare": { 1142 | "version": "1.4.0", 1143 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1144 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 1145 | "dev": true 1146 | }, 1147 | "once": { 1148 | "version": "1.4.0", 1149 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1150 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1151 | "dev": true, 1152 | "requires": { 1153 | "wrappy": "1" 1154 | } 1155 | }, 1156 | "optionator": { 1157 | "version": "0.9.1", 1158 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", 1159 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", 1160 | "dev": true, 1161 | "requires": { 1162 | "deep-is": "^0.1.3", 1163 | "fast-levenshtein": "^2.0.6", 1164 | "levn": "^0.4.1", 1165 | "prelude-ls": "^1.2.1", 1166 | "type-check": "^0.4.0", 1167 | "word-wrap": "^1.2.3" 1168 | } 1169 | }, 1170 | "parent-module": { 1171 | "version": "1.0.1", 1172 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1173 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1174 | "dev": true, 1175 | "requires": { 1176 | "callsites": "^3.0.0" 1177 | } 1178 | }, 1179 | "path-is-absolute": { 1180 | "version": "1.0.1", 1181 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1182 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1183 | "dev": true 1184 | }, 1185 | "path-key": { 1186 | "version": "3.1.1", 1187 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1188 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1189 | "dev": true 1190 | }, 1191 | "prelude-ls": { 1192 | "version": "1.2.1", 1193 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1194 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1195 | "dev": true 1196 | }, 1197 | "punycode": { 1198 | "version": "2.1.1", 1199 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1200 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1201 | "dev": true 1202 | }, 1203 | "regexpp": { 1204 | "version": "3.2.0", 1205 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", 1206 | "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", 1207 | "dev": true 1208 | }, 1209 | "resolve-from": { 1210 | "version": "4.0.0", 1211 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1212 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1213 | "dev": true 1214 | }, 1215 | "rimraf": { 1216 | "version": "3.0.2", 1217 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1218 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1219 | "dev": true, 1220 | "requires": { 1221 | "glob": "^7.1.3" 1222 | } 1223 | }, 1224 | "robust-predicates": { 1225 | "version": "3.0.1", 1226 | "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", 1227 | "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==", 1228 | "dev": true 1229 | }, 1230 | "rollup": { 1231 | "version": "2.67.3", 1232 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.67.3.tgz", 1233 | "integrity": "sha512-G/x1vUwbGtP6O5ZM8/sWr8+p7YfZhI18pPqMRtMYMWSbHjKZ/ajHGiM+GWNTlWyOR0EHIdT8LHU+Z4ciIZ1oBw==", 1234 | "dev": true, 1235 | "requires": { 1236 | "fsevents": "~2.3.2" 1237 | } 1238 | }, 1239 | "rw": { 1240 | "version": "1.3.3", 1241 | "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", 1242 | "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=", 1243 | "dev": true 1244 | }, 1245 | "safer-buffer": { 1246 | "version": "2.1.2", 1247 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1248 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1249 | "dev": true 1250 | }, 1251 | "shebang-command": { 1252 | "version": "2.0.0", 1253 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1254 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1255 | "dev": true, 1256 | "requires": { 1257 | "shebang-regex": "^3.0.0" 1258 | } 1259 | }, 1260 | "shebang-regex": { 1261 | "version": "3.0.0", 1262 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1263 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1264 | "dev": true 1265 | }, 1266 | "strip-ansi": { 1267 | "version": "6.0.1", 1268 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1269 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1270 | "dev": true, 1271 | "requires": { 1272 | "ansi-regex": "^5.0.1" 1273 | } 1274 | }, 1275 | "strip-json-comments": { 1276 | "version": "3.1.1", 1277 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1278 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1279 | "dev": true 1280 | }, 1281 | "supports-color": { 1282 | "version": "7.2.0", 1283 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1284 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1285 | "dev": true, 1286 | "requires": { 1287 | "has-flag": "^4.0.0" 1288 | } 1289 | }, 1290 | "text-table": { 1291 | "version": "0.2.0", 1292 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1293 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1294 | "dev": true 1295 | }, 1296 | "type-check": { 1297 | "version": "0.4.0", 1298 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1299 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1300 | "dev": true, 1301 | "requires": { 1302 | "prelude-ls": "^1.2.1" 1303 | } 1304 | }, 1305 | "type-fest": { 1306 | "version": "0.20.2", 1307 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 1308 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 1309 | "dev": true 1310 | }, 1311 | "uglify-js": { 1312 | "version": "3.15.1", 1313 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.1.tgz", 1314 | "integrity": "sha512-FAGKF12fWdkpvNJZENacOH0e/83eG6JyVQyanIJaBXCN1J11TUQv1T1/z8S+Z0CG0ZPk1nPcreF/c7lrTd0TEQ==", 1315 | "dev": true 1316 | }, 1317 | "uri-js": { 1318 | "version": "4.4.1", 1319 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1320 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1321 | "dev": true, 1322 | "requires": { 1323 | "punycode": "^2.1.0" 1324 | } 1325 | }, 1326 | "v8-compile-cache": { 1327 | "version": "2.3.0", 1328 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", 1329 | "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", 1330 | "dev": true 1331 | }, 1332 | "watch": { 1333 | "version": "1.0.2", 1334 | "resolved": "https://registry.npmjs.org/watch/-/watch-1.0.2.tgz", 1335 | "integrity": "sha1-NApxe952Vyb6CqB9ch4BR6VR3ww=", 1336 | "dev": true, 1337 | "requires": { 1338 | "exec-sh": "^0.2.0", 1339 | "minimist": "^1.2.0" 1340 | } 1341 | }, 1342 | "which": { 1343 | "version": "2.0.2", 1344 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1345 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1346 | "dev": true, 1347 | "requires": { 1348 | "isexe": "^2.0.0" 1349 | } 1350 | }, 1351 | "word-wrap": { 1352 | "version": "1.2.3", 1353 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1354 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", 1355 | "dev": true 1356 | }, 1357 | "wrappy": { 1358 | "version": "1.0.2", 1359 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1360 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1361 | "dev": true 1362 | } 1363 | } 1364 | } 1365 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@asymmetrik/leaflet-d3", 3 | "artifactName": "leaflet-d3", 4 | "moduleName": "leafletD3", 5 | "description": "Custom d3 layers for leaflet", 6 | "version": "6.0.1", 7 | "author": "Asymmetrik, Ltd. a BlueHalo Company", 8 | "copyright": "Copyright (c) 2007-2022 Asymmetrik Ltd, a BlueHalo Company", 9 | "license": "MIT", 10 | "scripts": { 11 | "build": "npm run bundle", 12 | "prebuild": "npm run lint", 13 | "bundle": "npm run rollup && npm run uglify", 14 | "lint": "eslint 'src/**/*.js'", 15 | "prepare": "npm run build", 16 | "rollup": "rollup -c rollup.config.js", 17 | "uglify": "uglifyjs --comments 'license' -o ./dist/leaflet-d3.min.js -- ./dist/leaflet-d3.js", 18 | "watch": " watch 'npm run build' ./src" 19 | }, 20 | "main": "dist/leaflet-d3.js", 21 | "module": "index.js", 22 | "typings": "index.d.ts", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/Asymmetrik/leaflet-d3.git" 26 | }, 27 | "peerDependencies": { 28 | "d3": "7", 29 | "d3-hexbin": "0.2", 30 | "leaflet": "1" 31 | }, 32 | "devDependencies": { 33 | "d3": "7", 34 | "@types/d3": "7", 35 | "d3-hexbin": "0.2", 36 | "leaflet": "1.7", 37 | "@types/leaflet": "1.7", 38 | "eslint": "8", 39 | "rollup": "2", 40 | "uglify-js": "3", 41 | "watch": "1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const 2 | path = require('path'), 3 | pkg = require('./package.json'); 4 | 5 | export default { 6 | input: path.posix.resolve('src/js/index.js'), 7 | external: [ 8 | 'd3', 9 | 'd3-hexbin', 10 | 'leaflet' 11 | ], 12 | output: { 13 | banner: `/*! ${pkg.name} - ${pkg.version} - ${pkg.copyright} + */`, 14 | file: path.posix.join('./dist', `${pkg.artifactName}.js`), 15 | format: 'umd', 16 | globals: { 17 | 'd3': 'd3', 18 | 'd3-hexbin': 'd3.hexbin', 19 | 'leaflet': 'L' 20 | }, 21 | name: pkg.moduleName, 22 | sourcemap: true 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/js/hexbin/HexbinLayer.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | import * as d3Hexbin from 'd3-hexbin'; 3 | import 'leaflet'; 4 | 5 | /** 6 | * This is a convoluted way of getting ahold of the hexbin function. 7 | * - When imported globally, d3 is exposed in the global namespace as 'd3' 8 | * - When imported using a module system, it's a named import (and can't collide with d3) 9 | * - When someone isn't importing d3-hexbin, the named import will be undefined 10 | * 11 | * As a result, we have to figure out how it's being imported and get the function reference 12 | * (which is why we have this convoluted nested ternary statement 13 | */ 14 | var d3_hexbin = (null != d3.hexbin)? d3.hexbin : (null != d3Hexbin)? d3Hexbin.hexbin : null; 15 | 16 | /** 17 | * L is defined by the Leaflet library, see git://github.com/Leaflet/Leaflet.git for documentation 18 | * We extend L.SVG to take advantage of built-in zoom animations. 19 | */ 20 | L.HexbinLayer = L.SVG.extend({ 21 | includes: L.Evented || L.Mixin.Events, 22 | 23 | /** 24 | * Default options 25 | */ 26 | options : { 27 | radius : 12, 28 | opacity: 0.6, 29 | duration: 200, 30 | 31 | colorScaleExtent: [ 1, undefined ], 32 | radiusScaleExtent: [ 1, undefined ], 33 | colorDomain: null, 34 | radiusDomain: null, 35 | colorRange: [ '#f7fbff', '#08306b' ], 36 | radiusRange: [ 4, 12 ], 37 | 38 | pointerEvents: 'all' 39 | }, 40 | 41 | 42 | /** 43 | * Standard Leaflet initialize function, accepting an options argument provided by the 44 | * user when they create the layer 45 | * @param options Options object where the options override the defaults 46 | */ 47 | initialize : function(options) { 48 | L.setOptions(this, options); 49 | 50 | // Set up the various overrideable functions 51 | this._fn = { 52 | lng: function(d) { return d[0]; }, 53 | lat: function(d) { return d[1]; }, 54 | colorValue: function(d) { return d.length; }, 55 | radiusValue: function(d) { return Number.MAX_VALUE; }, 56 | 57 | fill: function(d) { 58 | var val = this._fn.colorValue(d); 59 | return (null != val) ? this._scale.color(val) : 'none'; 60 | } 61 | }; 62 | 63 | // Set up the customizable scale 64 | this._scale = { 65 | color: d3.scaleLinear(), 66 | radius: d3.scaleLinear() 67 | }; 68 | 69 | // Set up the Dispatcher for managing events and callbacks 70 | this._dispatch = d3.dispatch('mouseover', 'mouseout', 'click'); 71 | 72 | // Set up the default hover handler 73 | this._hoverHandler = L.HexbinHoverHandler.none(); 74 | 75 | // Create the hex layout 76 | this._hexLayout = d3_hexbin() 77 | .radius(this.options.radius) 78 | .x(function(d) { return d.point[0]; }) 79 | .y(function(d) { return d.point[1]; }); 80 | 81 | // Initialize the data array to be empty 82 | this._data = []; 83 | 84 | this._scale.color 85 | .range(this.options.colorRange) 86 | .clamp(true); 87 | 88 | this._scale.radius 89 | .range(this.options.radiusRange) 90 | .clamp(true); 91 | 92 | }, 93 | 94 | /** 95 | * Callback made by Leaflet when the layer is added to the map 96 | * @param map Reference to the map to which this layer has been added 97 | */ 98 | onAdd : function(map) { 99 | 100 | L.SVG.prototype.onAdd.call(this); 101 | 102 | // Store a reference to the map for later use 103 | this._map = map; 104 | 105 | // Redraw on moveend 106 | map.on({ 'moveend': this.redraw }, this); 107 | 108 | // Initial draw 109 | this.redraw(); 110 | 111 | }, 112 | 113 | /** 114 | * Callback made by Leaflet when the layer is removed from the map 115 | * @param map Reference to the map from which this layer is being removed 116 | */ 117 | onRemove : function(map) { 118 | 119 | L.SVG.prototype.onRemove.call(this); 120 | 121 | // Destroy the svg container 122 | this._destroyContainer(); 123 | 124 | // Remove events 125 | map.off({ 'moveend': this.redraw }, this); 126 | 127 | this._map = null; 128 | 129 | }, 130 | 131 | /** 132 | * Create the SVG container for the hexbins 133 | * @private 134 | */ 135 | _initContainer : function() { 136 | 137 | L.SVG.prototype._initContainer.call(this); 138 | this._d3Container = d3.select(this._container).select('g'); 139 | }, 140 | 141 | /** 142 | * Clean up the svg container 143 | * @private 144 | */ 145 | _destroyContainer: function() { 146 | d3.select(this._container).remove(); 147 | }, 148 | 149 | /** 150 | * (Re)draws the hexbins data on the container 151 | * @private 152 | */ 153 | redraw : function() { 154 | var that = this; 155 | 156 | if (!that._map) { 157 | return; 158 | } 159 | 160 | // Generate the mapped version of the data 161 | var data = that._data.map(function(d) { 162 | var lng = that._fn.lng(d); 163 | var lat = that._fn.lat(d); 164 | 165 | var point = that._project([ lng, lat ]); 166 | return { o: d, point: point }; 167 | }); 168 | 169 | // Select the hex group for the current zoom level. This has 170 | // the effect of recreating the group if the zoom level has changed 171 | var join = this._d3Container.selectAll('g.hexbin') 172 | .data([ this._map.getZoom() ], function(d) { return d; }); 173 | 174 | // enter 175 | var enter = join.enter().append('g') 176 | .attr('class', function(d) { return 'hexbin zoom-' + d; }); 177 | 178 | // enter + update 179 | var enterUpdate = enter.merge(join); 180 | 181 | // exit 182 | join.exit().remove(); 183 | 184 | // add the hexagons to the select 185 | this._createHexagons(enterUpdate, data); 186 | 187 | }, 188 | 189 | _createHexagons : function(g, data) { 190 | var that = this; 191 | 192 | // Create the bins using the hexbin layout 193 | 194 | // Generate the map bounds (to be used to filter the hexes to what is visible) 195 | var bounds = that._map.getBounds(); 196 | var size = that._map.getSize(); 197 | bounds = bounds.pad(that.options.radius * 2 / Math.max(size.x, size.y)); 198 | 199 | var bins = that._hexLayout(data); 200 | 201 | // Derive the extents of the data values for each dimension 202 | var colorExtent = that._getExtent(bins, that._fn.colorValue, that.options.colorScaleExtent); 203 | var radiusExtent = that._getExtent(bins, that._fn.radiusValue, that.options.radiusScaleExtent); 204 | 205 | // Match the domain cardinality to that of the color range, to allow for a polylinear scale 206 | var colorDomain = this.options.colorDomain; 207 | if (null == colorDomain) { 208 | colorDomain = that._linearlySpace(colorExtent[0], colorExtent[1], that._scale.color.range().length); 209 | } 210 | var radiusDomain = this.options.radiusDomain || radiusExtent; 211 | 212 | // Set the scale domains 213 | that._scale.color.domain(colorDomain); 214 | that._scale.radius.domain(radiusDomain); 215 | 216 | 217 | /* 218 | * Join 219 | * Join the Hexagons to the data 220 | * Use a deterministic id for tracking bins based on position 221 | */ 222 | bins = bins.filter(function(d) { 223 | return bounds.contains(that._map.layerPointToLatLng(L.point(d.x, d.y))); 224 | }); 225 | var join = g.selectAll('g.hexbin-container') 226 | .data(bins, function(d) { 227 | return d.x + ':' + d.y; 228 | }); 229 | 230 | 231 | /* 232 | * Update 233 | * Set the fill and opacity on a transition 234 | * opacity is re-applied in case the enter transition was cancelled 235 | * the path is applied as well to resize the bins 236 | */ 237 | join.select('path.hexbin-hexagon').transition().duration(that.options.duration) 238 | .attr('fill', that._fn.fill.bind(that)) 239 | .attr('fill-opacity', that.options.opacity) 240 | .attr('stroke-opacity', that.options.opacity) 241 | .attr('d', function(d) { 242 | return that._hexLayout.hexagon(that._scale.radius(that._fn.radiusValue.call(that, d))); 243 | }); 244 | 245 | 246 | /* 247 | * Enter 248 | * Establish the path, size, fill, and the initial opacity 249 | * Transition to the final opacity and size 250 | */ 251 | var enter = join.enter().append('g').attr('class', 'hexbin-container'); 252 | 253 | enter.append('path').attr('class', 'hexbin-hexagon') 254 | .attr('transform', function(d) { 255 | return 'translate(' + d.x + ',' + d.y + ')'; 256 | }) 257 | .attr('d', function(d) { 258 | return that._hexLayout.hexagon(that._scale.radius.range()[0]); 259 | }) 260 | .attr('fill', that._fn.fill.bind(that)) 261 | .attr('fill-opacity', 0.01) 262 | .attr('stroke-opacity', 0.01) 263 | .transition().duration(that.options.duration) 264 | .attr('fill-opacity', that.options.opacity) 265 | .attr('stroke-opacity', that.options.opacity) 266 | .attr('d', function(d) { 267 | return that._hexLayout.hexagon(that._scale.radius(that._fn.radiusValue.call(that, d))); 268 | }); 269 | 270 | // Grid 271 | var gridEnter = enter.append('path').attr('class', 'hexbin-grid') 272 | .attr('transform', function(d) { 273 | return 'translate(' + d.x + ',' + d.y + ')'; 274 | }) 275 | .attr('d', function(d) { 276 | return that._hexLayout.hexagon(that.options.radius); 277 | }) 278 | .attr('fill', 'none') 279 | .attr('stroke', 'none') 280 | .style('pointer-events', that.options.pointerEvents); 281 | 282 | // Grid enter-update 283 | gridEnter.merge(join.select('path.hexbin-grid')) 284 | .on('mouseover', function(event, d, i) { 285 | that._hoverHandler.mouseover.call(this, that, event, d, i); 286 | that._dispatch.call('mouseover', this, event, d, i); 287 | }) 288 | .on('mouseout', function(event, d, i) { 289 | that._dispatch.call('mouseout', this, event, d, i); 290 | that._hoverHandler.mouseout.call(this, that, event, d, i); 291 | }) 292 | .on('click', function(event, d, i) { 293 | that._dispatch.call('click', this, event, d, i); 294 | }); 295 | 296 | 297 | // Exit 298 | var exit = join.exit(); 299 | 300 | exit.select('path.hexbin-hexagon') 301 | .transition().duration(that.options.duration) 302 | .attr('fill-opacity', 0) 303 | .attr('stroke-opacity', 0) 304 | .attr('d', function(d) { 305 | return that._hexLayout.hexagon(0); 306 | }); 307 | 308 | exit.transition().duration(that.options.duration) 309 | .remove(); 310 | 311 | }, 312 | 313 | _getExtent: function(bins, valueFn, scaleExtent) { 314 | 315 | // Determine the extent of the values 316 | var extent = d3.extent(bins, valueFn.bind(this)); 317 | 318 | // If either's null, initialize them to 0 319 | if (null == extent[0]) extent[0] = 0; 320 | if (null == extent[1]) extent[1] = 0; 321 | 322 | // Now apply the optional clipping of the extent 323 | if (null != scaleExtent[0]) extent[0] = scaleExtent[0]; 324 | if (null != scaleExtent[1]) extent[1] = scaleExtent[1]; 325 | 326 | return extent; 327 | 328 | }, 329 | 330 | _project : function(coord) { 331 | var point = this._map.latLngToLayerPoint([ coord[1], coord[0] ]); 332 | return [ point.x, point.y ]; 333 | }, 334 | 335 | _getBounds: function(data) { 336 | if(null == data || data.length < 1) { 337 | return { min: [ 0, 0 ], max: [ 0, 0 ]}; 338 | } 339 | 340 | // bounds is [[min long, min lat], [max long, max lat]] 341 | var bounds = [ [ 999, 999 ], [ -999, -999 ] ]; 342 | 343 | data.forEach(function(element) { 344 | var x = element.point[0]; 345 | var y = element.point[1]; 346 | 347 | bounds[0][0] = Math.min(bounds[0][0], x); 348 | bounds[0][1] = Math.min(bounds[0][1], y); 349 | bounds[1][0] = Math.max(bounds[1][0], x); 350 | bounds[1][1] = Math.max(bounds[1][1], y); 351 | }); 352 | 353 | return { min: bounds[0], max: bounds[1] }; 354 | }, 355 | 356 | _linearlySpace: function(from, to, length) { 357 | var arr = new Array(length); 358 | var step = (to - from) / Math.max(length - 1, 1); 359 | 360 | for (var i = 0; i < length; ++i) { 361 | arr[i] = from + (i * step); 362 | } 363 | 364 | return arr; 365 | }, 366 | 367 | 368 | // ------------------------------------ 369 | // Public API 370 | // ------------------------------------ 371 | 372 | radius: function(v) { 373 | if (!arguments.length) { return this.options.radius; } 374 | 375 | this.options.radius = v; 376 | this._hexLayout.radius(v); 377 | 378 | return this; 379 | }, 380 | 381 | opacity: function(v) { 382 | if (!arguments.length) { return this.options.opacity; } 383 | this.options.opacity = v; 384 | 385 | return this; 386 | }, 387 | 388 | duration: function(v) { 389 | if (!arguments.length) { return this.options.duration; } 390 | this.options.duration = v; 391 | 392 | return this; 393 | }, 394 | 395 | colorScaleExtent: function(v) { 396 | if (!arguments.length) { return this.options.colorScaleExtent; } 397 | this.options.colorScaleExtent = v; 398 | 399 | return this; 400 | }, 401 | 402 | radiusScaleExtent: function(v) { 403 | if (!arguments.length) { return this.options.radiusScaleExtent; } 404 | this.options.radiusScaleExtent = v; 405 | 406 | return this; 407 | }, 408 | 409 | colorRange: function(v) { 410 | if (!arguments.length) { return this.options.colorRange; } 411 | this.options.colorRange = v; 412 | this._scale.color.range(v); 413 | 414 | return this; 415 | }, 416 | 417 | radiusRange: function(v) { 418 | if (!arguments.length) { return this.options.radiusRange; } 419 | this.options.radiusRange = v; 420 | this._scale.radius.range(v); 421 | 422 | return this; 423 | }, 424 | 425 | colorScale: function(v) { 426 | if (!arguments.length) { return this._scale.color; } 427 | this._scale.color = v; 428 | 429 | return this; 430 | }, 431 | 432 | radiusScale: function(v) { 433 | if (!arguments.length) { return this._scale.radius; } 434 | this._scale.radius = v; 435 | 436 | return this; 437 | }, 438 | 439 | lng: function(v) { 440 | if (!arguments.length) { return this._fn.lng; } 441 | this._fn.lng = v; 442 | 443 | return this; 444 | }, 445 | 446 | lat: function(v) { 447 | if (!arguments.length) { return this._fn.lat; } 448 | this._fn.lat = v; 449 | 450 | return this; 451 | }, 452 | 453 | colorValue: function(v) { 454 | if (!arguments.length) { return this._fn.colorValue; } 455 | this._fn.colorValue = v; 456 | 457 | return this; 458 | }, 459 | 460 | radiusValue: function(v) { 461 | if (!arguments.length) { return this._fn.radiusValue; } 462 | this._fn.radiusValue = v; 463 | 464 | return this; 465 | }, 466 | 467 | fill: function(v) { 468 | if (!arguments.length) { return this._fn.fill; } 469 | this._fn.fill = v; 470 | 471 | return this; 472 | }, 473 | 474 | data: function(v) { 475 | if (!arguments.length) { return this._data; } 476 | this._data = (null != v) ? v : []; 477 | 478 | this.redraw(); 479 | 480 | return this; 481 | }, 482 | 483 | /* 484 | * Getter for the event dispatcher 485 | */ 486 | dispatch: function() { 487 | return this._dispatch; 488 | }, 489 | 490 | hoverHandler: function(v) { 491 | if (!arguments.length) { return this._hoverHandler; } 492 | this._hoverHandler = (null != v) ? v : L.HexbinHoverHandler.none(); 493 | 494 | this.redraw(); 495 | 496 | return this; 497 | }, 498 | 499 | /* 500 | * Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 501 | */ 502 | getLatLngs: function () { 503 | var that = this; 504 | 505 | // Map the data into an array of latLngs using the configured lat/lng accessors 506 | return this._data.map(function(d) { 507 | return L.latLng(that.options.lat(d), that.options.lng(d)); 508 | }); 509 | }, 510 | 511 | /* 512 | * Get path geometry as GeoJSON 513 | */ 514 | toGeoJSON: function () { 515 | return L.GeoJSON.getFeature(this, { 516 | type: 'LineString', 517 | coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs(), 0) 518 | }); 519 | } 520 | 521 | }); 522 | 523 | // Hover Handlers modify the hexagon and can be combined 524 | L.HexbinHoverHandler = { 525 | 526 | tooltip: function(options) { 527 | 528 | // merge options with defaults 529 | options = options || {}; 530 | if (null == options.tooltipContent) { options.tooltipContent = function(d) { return 'Count: ' + d.length; }; } 531 | 532 | // Generate the tooltip 533 | var tooltip = d3.select('body').append('div') 534 | .attr('class', 'hexbin-tooltip') 535 | .style('z-index', 9999) 536 | .style('pointer-events', 'none') 537 | .style('visibility', 'hidden') 538 | .style('position', 'fixed'); 539 | 540 | tooltip.append('div').attr('class', 'tooltip-content'); 541 | 542 | // return the handler instance 543 | return { 544 | mouseover: function (hexLayer, event, data) { 545 | var gCoords = d3.pointer(event); 546 | 547 | tooltip 548 | .style('visibility', 'visible') 549 | .html(options.tooltipContent(data, hexLayer)); 550 | 551 | var div = null; 552 | if (null != tooltip._groups && tooltip._groups.length > 0 && tooltip._groups[0].length > 0) { 553 | div = tooltip._groups[0][0]; 554 | } 555 | var h = div.clientHeight, w = div.clientWidth; 556 | 557 | tooltip 558 | .style('top', '' + event.clientY - gCoords[1] - h - 16 + 'px') 559 | .style('left', '' + event.clientX - gCoords[0] - w/2 + 'px'); 560 | 561 | }, 562 | mouseout: function (hexLayer, event, data) { 563 | tooltip 564 | .style('visibility', 'hidden') 565 | .html(); 566 | } 567 | }; 568 | 569 | }, 570 | 571 | resizeFill: function() { 572 | 573 | // return the handler instance 574 | return { 575 | mouseover: function (hexLayer, event, data) { 576 | var o = d3.select(this.parentNode); 577 | o.select('path.hexbin-hexagon') 578 | .attr('d', function (d) { 579 | return hexLayer._hexLayout.hexagon(hexLayer.options.radius); 580 | }); 581 | }, 582 | mouseout: function (hexLayer, event, data) { 583 | var o = d3.select(this.parentNode); 584 | o.select('path.hexbin-hexagon') 585 | .attr('d', function (d) { 586 | return hexLayer._hexLayout.hexagon(hexLayer._scale.radius(hexLayer._fn.radiusValue.call(hexLayer, d))); 587 | }); 588 | } 589 | }; 590 | 591 | }, 592 | 593 | resizeScale: function(options) { 594 | 595 | // merge options with defaults 596 | options = options || {}; 597 | if (null == options.radiusScale) options.radiusScale = 0.5; 598 | 599 | // return the handler instance 600 | return { 601 | mouseover: function (hexLayer, event, data) { 602 | var o = d3.select(this.parentNode); 603 | o.select('path.hexbin-hexagon') 604 | .attr('d', function (d) { 605 | return hexLayer._hexLayout.hexagon(hexLayer._scale.radius.range()[1] * (1 + options.radiusScale)); 606 | }); 607 | }, 608 | mouseout: function (hexLayer, event, data) { 609 | var o = d3.select(this.parentNode); 610 | o.select('path.hexbin-hexagon') 611 | .attr('d', function (d) { 612 | return hexLayer._hexLayout.hexagon(hexLayer._scale.radius(hexLayer._fn.radiusValue.call(hexLayer, d))); 613 | }); 614 | } 615 | }; 616 | 617 | }, 618 | 619 | compound: function(options) { 620 | 621 | options = options || {}; 622 | if (null == options.handlers) options.handlers = [ L.HexbinHoverHandler.none() ]; 623 | 624 | return { 625 | mouseover: function (hexLayer, event, data) { 626 | var that = this; 627 | options.handlers.forEach(function(h) { h.mouseover.call(that, hexLayer, event, data); }); 628 | }, 629 | mouseout: function (hexLayer, event, data) { 630 | var that = this; 631 | options.handlers.forEach(function(h) { h.mouseout.call(that, hexLayer, event, data); }); 632 | } 633 | }; 634 | 635 | }, 636 | 637 | none: function() { 638 | return { 639 | mouseover: function () {}, 640 | mouseout: function () {} 641 | }; 642 | } 643 | }; 644 | 645 | L.hexbinLayer = function(options) { 646 | return new L.HexbinLayer(options); 647 | }; 648 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | export * from './hexbin/HexbinLayer'; 2 | export * from './ping/PingLayer'; 3 | -------------------------------------------------------------------------------- /src/js/ping/PingLayer.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | import 'leaflet'; 3 | 4 | /** 5 | * L is defined by the Leaflet library, see git://github.com/Leaflet/Leaflet.git for documentation 6 | * We extend L.SVG to take advantage of built-in zoom animations. 7 | */ 8 | L.PingLayer = L.SVG.extend({ 9 | includes: L.Evented || L.Mixin.Events, 10 | 11 | /* 12 | * Default options 13 | */ 14 | options : { 15 | duration: 800, 16 | fps: 32, 17 | opacityRange: [ 1, 0 ], 18 | radiusRange: [ 3, 15 ] 19 | }, 20 | 21 | 22 | // Initialization of the plugin 23 | initialize : function(options) { 24 | L.setOptions(this, options); 25 | 26 | this._fn = { 27 | lng: function(d) { return d[0]; }, 28 | lat: function(d) { return d[1]; }, 29 | radiusScaleFactor: function(d) { return 1; } 30 | }; 31 | 32 | this._scale = { 33 | radius: d3.scalePow().exponent(0.35), 34 | opacity: d3.scaleLinear() 35 | }; 36 | 37 | this._lastUpdate = Date.now(); 38 | this._fps = 0; 39 | 40 | this._scale.radius 41 | .domain([ 0, this.options.duration ]) 42 | .range(this.options.radiusRange) 43 | .clamp(true); 44 | this._scale.opacity 45 | .domain([ 0, this.options.duration ]) 46 | .range(this.options.opacityRange) 47 | .clamp(true); 48 | }, 49 | 50 | // Called when the plugin layer is added to the map 51 | onAdd : function(map) { 52 | 53 | L.SVG.prototype.onAdd.call(this); 54 | 55 | // Store a reference to the map for later use 56 | this._map = map; 57 | 58 | // Init the state of the simulation 59 | this._running = false; 60 | 61 | // Set up events 62 | map.on({'move': this._updateContainer}, this); 63 | 64 | }, 65 | 66 | // Called when the plugin layer is removed from the map 67 | onRemove : function(map) { 68 | 69 | L.SVG.prototype.onRemove.call(this); 70 | 71 | // Destroy the svg container 72 | this._destroyContainer(); 73 | 74 | // Remove events 75 | map.off({'move': this._updateContainer}, this); 76 | 77 | this._map = null; 78 | this._data = null; 79 | 80 | }, 81 | 82 | 83 | /* 84 | * Private Methods 85 | */ 86 | 87 | // Initialize the Container - creates the svg pane 88 | _initContainer : function() { 89 | 90 | L.SVG.prototype._initContainer.call(this); 91 | this._d3Container = d3.select(this._container).select('g'); 92 | 93 | }, 94 | 95 | // Update the container - Updates the dimensions of the svg pane 96 | _updateContainer : function() { 97 | 98 | this._updatePings(true); 99 | 100 | }, 101 | 102 | // Cleanup the svg pane 103 | _destroyContainer: function() { 104 | this._d3Container.selectAll('circle').remove(); 105 | d3.select(this._container).remove(); 106 | }, 107 | 108 | 109 | // Calculate the circle coordinates for the provided data 110 | _getCircleCoords: function(geo) { 111 | var point = this._map.latLngToLayerPoint(geo); 112 | return { x: point.x, y: point.y }; 113 | }, 114 | 115 | 116 | // Add a ping to the map 117 | _addPing : function(data, cssClass) { 118 | // Lazy init the data array 119 | if (null == this._data) this._data = []; 120 | 121 | // Derive the spatial data 122 | var geo = [ this._fn.lat(data), this._fn.lng(data) ]; 123 | var coords = this._getCircleCoords(geo); 124 | 125 | // Add the data to the list of pings 126 | var circle = { 127 | data: data, 128 | geo: geo, 129 | ts: Date.now(), 130 | nts: 0 131 | }; 132 | circle.c = this._d3Container.append('circle') 133 | .attr('class', (null != cssClass)? 'ping ' + cssClass : 'ping') 134 | .attr('cx', coords.x) 135 | .attr('cy', coords.y) 136 | .attr('r', this._fn.radiusScaleFactor.call(this, data) * this._scale.radius.range()[0]); 137 | 138 | // Push new circles 139 | this._data.push(circle); 140 | }, 141 | 142 | // Main update loop 143 | _updatePings : function(immediate) { 144 | var nowTs = Date.now(); 145 | if (null == this._data) this._data = []; 146 | 147 | var maxIndex = -1; 148 | 149 | // Update everything 150 | for (var i=0; i < this._data.length; i++) { 151 | 152 | var d = this._data[i]; 153 | var age = nowTs - d.ts; 154 | 155 | if (this.options.duration < age) { 156 | 157 | // If the blip is beyond it's life, remove it from the dom and track the lowest index to remove 158 | d.c.remove(); 159 | maxIndex = i; 160 | 161 | } 162 | else { 163 | 164 | // If the blip is still alive, process it 165 | if (immediate || d.nts < nowTs) { 166 | 167 | var coords = this._getCircleCoords(d.geo); 168 | 169 | d.c.attr('cx', coords.x) 170 | .attr('cy', coords.y) 171 | .attr('r', this._fn.radiusScaleFactor.call(this, d.data) * this._scale.radius(age)) 172 | .attr('fill-opacity', this._scale.opacity(age)) 173 | .attr('stroke-opacity', this._scale.opacity(age)); 174 | d.nts = Math.round(nowTs + 1000/this.options.fps); 175 | 176 | } 177 | } 178 | } 179 | 180 | // Delete all the aged off data at once 181 | if (maxIndex > -1) { 182 | this._data.splice(0, maxIndex + 1); 183 | } 184 | 185 | // The return function dictates whether the timer loop will continue 186 | this._running = (this._data.length > 0); 187 | 188 | if (this._running) { 189 | this._fps = 1000/(nowTs - this._lastUpdate); 190 | this._lastUpdate = nowTs; 191 | } 192 | 193 | return !this._running; 194 | }, 195 | 196 | // Expire old pings 197 | _expirePings : function() { 198 | var maxIndex = -1; 199 | var nowTs = Date.now(); 200 | 201 | // Search from the front of the array 202 | for (var i=0; i < this._data.length; i++) { 203 | var d = this._data[i]; 204 | var age = nowTs - d.ts; 205 | 206 | if(this.options.duration < age) { 207 | // If the blip is beyond it's life, remove it from the dom and track the lowest index to remove 208 | d.c.remove(); 209 | maxIndex = i; 210 | } 211 | else { 212 | break; 213 | } 214 | } 215 | 216 | // Delete all the aged off data at once 217 | if (maxIndex > -1) { 218 | this._data.splice(0, maxIndex + 1); 219 | } 220 | }, 221 | 222 | /* 223 | * Public Methods 224 | */ 225 | 226 | duration: function(v) { 227 | if (!arguments.length) { return this.options.duration; } 228 | this.options.duration = v; 229 | 230 | return this; 231 | }, 232 | 233 | fps: function(v) { 234 | if (!arguments.length) { return this.options.fps; } 235 | this.options.fps = v; 236 | 237 | return this; 238 | }, 239 | 240 | lng: function(v) { 241 | if (!arguments.length) { return this._fn.lng; } 242 | this._fn.lng = v; 243 | 244 | return this; 245 | }, 246 | 247 | lat: function(v) { 248 | if (!arguments.length) { return this._fn.lat; } 249 | this._fn.lat = v; 250 | 251 | return this; 252 | }, 253 | 254 | radiusRange: function(v) { 255 | if (!arguments.length) { return this.options.radiusRange; } 256 | this.options.radiusRange = v; 257 | this._scale.radius().range(v); 258 | 259 | return this; 260 | }, 261 | 262 | opacityRange: function(v) { 263 | if (!arguments.length) { return this.options.opacityRange; } 264 | this.options.opacityRange = v; 265 | this._scale.opacity().range(v); 266 | 267 | return this; 268 | }, 269 | 270 | radiusScale: function(v) { 271 | if (!arguments.length) { return this._scale.radius; } 272 | this._scale.radius = v; 273 | 274 | return this; 275 | }, 276 | 277 | opacityScale: function(v) { 278 | if (!arguments.length) { return this._scale.opacity; } 279 | this._scale.opacity = v; 280 | 281 | return this; 282 | }, 283 | 284 | radiusScaleFactor: function(v) { 285 | if (!arguments.length) { return this._fn.radiusScaleFactor; } 286 | this._fn.radiusScaleFactor = v; 287 | 288 | return this; 289 | }, 290 | 291 | /* 292 | * Method by which to "add" pings 293 | */ 294 | ping : function(data, cssClass) { 295 | // If the layer isn't shown, ignore pings 296 | if (null == this._map) { 297 | return this; 298 | } 299 | 300 | this._addPing(data, cssClass); 301 | this._expirePings(); 302 | 303 | // Start timer if not active 304 | if (!this._running && this._data.length > 0) { 305 | this._running = true; 306 | this._lastUpdate = Date.now(); 307 | 308 | var that = this; 309 | d3.timer(function() { that._updatePings.call(that, false) }); 310 | } 311 | 312 | return this; 313 | }, 314 | 315 | getActualFps : function() { 316 | return this._fps; 317 | }, 318 | 319 | data : function() { 320 | return this._data; 321 | }, 322 | 323 | }); 324 | 325 | L.pingLayer = function(options) { 326 | return new L.PingLayer(options); 327 | }; 328 | --------------------------------------------------------------------------------