gridviz 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +18 -15
  2. package/dist/gridviz.js +3829 -2623
  3. package/dist/gridviz.min.js +1 -1
  4. package/package.json +5 -3
  5. package/src/button/Button.js +77 -68
  6. package/src/button/FullscreenButton.js +123 -123
  7. package/src/button/ZoomButtons.js +109 -104
  8. package/src/core/GeoCanvas.js +15 -0
  9. package/src/core/Legend.js +267 -245
  10. package/src/core/Map.js +819 -670
  11. package/src/core/Tooltip.js +191 -185
  12. package/src/index.js +82 -79
  13. package/src/layer/GridLayer.js +4 -3
  14. package/src/layer/LabelLayer.js +7 -4
  15. package/src/legend/ColorCategoryLegend.js +127 -125
  16. package/src/legend/ColorDiscreteLegend.js +116 -114
  17. package/src/legend/ColorLegend.js +33 -11
  18. package/src/legend/OrientationLegend.js +94 -92
  19. package/src/legend/SizeLegend.js +8 -1
  20. package/src/legend/TernaryLegend.js +317 -314
  21. package/src/legend/VennLegend.js +202 -0
  22. package/src/style/CircleColorSizeWebGLStyle.js +249 -0
  23. package/src/style/CompositionStyle.js +1 -1
  24. package/src/style/DotDensityStyle.js +8 -4
  25. package/src/style/ImageStyle.js +1 -1
  26. package/src/style/Interpolator.js +283 -259
  27. package/src/style/IsoFenceStyle.js +1 -1
  28. package/src/style/LegoStyle.js +6 -5
  29. package/src/style/MosaicStyle.js +1 -1
  30. package/src/style/NinjaStarStyle.js +1 -1
  31. package/src/style/PillarStyle.js +1 -1
  32. package/src/style/RandomWalkInterpolator.js +213 -0
  33. package/src/style/ShadingRayStyle.js +16 -2
  34. package/src/style/ShadingStyle.js +80 -72
  35. package/src/style/ShapeColorSizeStyle.js +18 -2
  36. package/src/style/SideCategoryStyle.js +1 -1
  37. package/src/style/SideStyle.js +1 -1
  38. package/src/style/SideTanakaStyle.js +1 -1
  39. package/src/style/SquareColorCategoryWebGLStyle.js +71 -20
  40. package/src/style/SquareColorWebGLStyle.js +7 -4
  41. package/src/style/StrokeStyle.js +1 -1
  42. package/src/style/TextStyle.js +1 -1
  43. package/src/style/TimeSeriesStyle.js +14 -7
  44. package/src/utils/WebGLSquareColoringAdvanced.js +47 -9
  45. package/src/utils/scale.js +20 -0
  46. package/src/utils/webGLUtils.js +52 -0
@@ -343,6 +343,21 @@ export class GeoCanvas {
343
343
  return [kx, 0.0, 0.0, 0.0, ky, 0.0, -kx * this.view.x, -ky * this.view.y, 1.0]
344
344
  }
345
345
 
346
+ /**
347
+ * Get the transformation matrix to webGL screen coordinates, for positions given relative to the center of the view
348
+ * (x - view.x, y - view.y).
349
+ *
350
+ * The GPU computes with 32 bits floats, which have 7 digits: with the geographical coordinates of a map (millions of
351
+ * meters), the translation of getWebGLTransform() loses the position of the squares when zoomed in, by tens of pixels
352
+ * (the squares are not where the cells are). Positions relative to the center of the view are small numbers: no loss.
353
+ * The relative positions are computed in JavaScript, with 64 bits floats.
354
+ */
355
+ getWebGLTransformCentered() {
356
+ const kx = 2.0 / (this.w * this.view.z)
357
+ const ky = 2.0 / (this.h * this.view.z)
358
+ return [kx, 0.0, 0.0, 0.0, ky, 0.0, 0.0, 0.0, 1.0]
359
+ }
360
+
346
361
  /** The function specifying how to draw the map. */
347
362
  redraw() {
348
363
  throw new Error('Method redraw not implemented.')
@@ -1,245 +1,267 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- import { select } from 'd3-selection'
5
-
6
- /**
7
- * A legend container.
8
- *
9
- * @module core
10
- * @author Joseph Davies, Julien Gaffuri
11
- */
12
- export class Legend {
13
- /**
14
- * @param {Object} opts
15
- */
16
- constructor(opts) {
17
- opts = opts || {}
18
-
19
- /** @type {string} */
20
- this.id = opts.id
21
-
22
- //TODO stop using it. Use style method below instead.
23
-
24
- /** @type {number} @deprecated */
25
- this.top = opts.top
26
- /** @type {number} @deprecated */
27
- this.bottom = opts.bottom
28
- /** @type {number} @deprecated */
29
- this.left = opts.left
30
- /** @type {number} @deprecated */
31
- this.right = opts.right
32
- /** @type {string} @deprecated */
33
- this.background = opts.background || 'none'
34
- /** @type {string} @deprecated */
35
- this.padding = opts.padding || '5px'
36
- /** @type {string} @deprecated */
37
- this.border = opts.border || '0px'
38
- /** @type {string} @deprecated */
39
- this['border-radius'] = opts['border-radius'] || 'none'
40
- /** @type {string} @deprecated */
41
- this['box-shadow'] = opts['box-shadow'] || 'none'
42
- /** @type {string} @deprecated */
43
- this['font-family'] = opts['font-family'] || 'Helvetica, Arial, sans-serif'
44
- /** @type {string} @deprecated */
45
- //this.width = opts.width
46
- /** @type {string} @deprecated */
47
- //this.height = opts.height
48
-
49
- //the div element
50
- if (this.id) this.div = select('#' + this.id)
51
-
52
- if (!this.div || this.div.empty()) {
53
- this.div = select(document.createElement('div'))
54
- if (this.id) this.div.attr('id', this.id)
55
- }
56
-
57
- // Which style options the caller set. The ones they didn't set are written as a CSS variable with the
58
- // usual value as its fallback (--gridviz-legend-font-family, ...): nothing changes unless a page defines the variable.
59
- /** @private */
60
- this._set = {
61
- padding: opts.padding !== undefined,
62
- fontFamily: opts['font-family'] !== undefined,
63
- titleFontSize: opts.titleFontSize !== undefined,
64
- titleFontWeight: opts.titleFontWeight !== undefined,
65
- labelFontSize: opts.labelFontSize !== undefined,
66
- }
67
-
68
- // CSS classes, to style legends from a stylesheet: gridviz-legend on every legend, a modifier for the
69
- // type (see each legend class), and whatever the caller asks for with the className option.
70
- this.div.classed('gridviz-legend', true)
71
- if (opts.className)
72
- for (const c of String(opts.className).split(/\s+/)) if (c) this.div.classed(c, true)
73
-
74
- //set style
75
- this.div.style('background', this.background)
76
- this.div.style('padding', this._css('padding', this.padding, this._set.padding))
77
- this.div.style('border', this.border)
78
- this.div.style('border-radius', this['border-radius'])
79
- this.div.style('box-shadow', this['box-shadow'])
80
- this.div.style('font-family', this._css('font-family', this['font-family'], this._set.fontFamily))
81
-
82
- //if (this.width) this.div.style('width', this.width)
83
- //if (this.height) this.div.style('height', this.height)
84
-
85
- //title
86
- this.title = opts.title
87
- this.titleFontSize = opts.titleFontSize || '0.8em'
88
- this.titleFontWeight = opts.titleFontWeight || 'bold'
89
-
90
- //label
91
- this.labelFontSize = opts.labelFontSize || '0.8em'
92
- this.labelUnitText = opts.labelUnitText || ''
93
- this.labelFormat = opts.labelFormat
94
-
95
- /**
96
- * Whether the title and the labels are HTML (to italicise a word, for example) instead of plain text.
97
- * Off by default, which is plain text. Only turn it on for text you trust: it is not escaped.
98
- * @type {boolean} */
99
- this.labelsAsHTML = opts.labelsAsHTML === true
100
-
101
- /**
102
- * Whether hovering a swatch of the legend shows only the matching cells on the map.
103
- * @type {boolean} */
104
- this.interactive = opts.interactive !== false
105
-
106
- /**
107
- * The style the legend belongs to, and a function to redraw the map. Both set by the layer when it draws.
108
- * @type {import('./Style.js').Style|undefined} */
109
- this.style_ = undefined
110
- /** @type {function|undefined} */
111
- this.redraw = undefined
112
- /**
113
- * The element the legends are drawn in, set by the layer when it draws.
114
- * @type {HTMLElement|undefined} */
115
- this.container = undefined
116
-
117
- /**
118
- * Draw at the width of the container instead of this.width (width:'fill').
119
- * @type {boolean} @protected */
120
- this.fill = opts.width === 'fill'
121
- }
122
-
123
- /**
124
- * Show only the cells drawn in some colours on the map, or all of them again.
125
- * @param {Array.<string>|null} colors
126
- */
127
- highlight(colors) {
128
- if (!this.interactive || !this.style_) return
129
- const before = this.style_.highlightColors
130
- // flag the legend while hovered, so that the map's redraw leaves its element in place:
131
- // a hovered element that is detached and appended again never gets its mouseleave
132
- if (this.div) this.div.attr('data-gridviz-hovered', colors && colors.length ? '' : null)
133
- this.style_.setHighlight(colors)
134
- // nothing to redraw when the highlight didn't change (mouse moving between a swatch's parts)
135
- if (before === null && this.style_.highlightColors === null) return
136
- if (this.redraw) this.redraw()
137
- }
138
-
139
- /**
140
- * Make a swatch react to the mouse: hovering it highlights the cells of a colour on the map.
141
- * @param {*} selection The d3 selection of the swatch (any element)
142
- * @param {string} color The colour the swatch stands for
143
- * @param {{enter?:function, leave?:function}} [extra] Extra feedback on the swatch itself
144
- */
145
- bindHover(selection, color, extra) {
146
- if (!this.interactive) return
147
- selection
148
- .style('cursor', 'pointer')
149
- .on('mouseenter', () => {
150
- if (extra && extra.enter) extra.enter()
151
- this.highlight([color])
152
- })
153
- .on('mouseleave', () => {
154
- if (extra && extra.leave) extra.leave()
155
- this.highlight(null)
156
- })
157
- }
158
-
159
- /**
160
- * A style value with a CSS variable around it, unless the caller set the option themselves.
161
- * @param {string} name The variable's name, after --gridviz-legend-
162
- * @param {string} value The value to use when the variable isn't defined
163
- * @param {boolean} custom
164
- * @returns {string}
165
- * @protected
166
- */
167
- _css(name, value, custom) {
168
- return custom ? value : `var(--gridviz-legend-${name}, ${value})`
169
- }
170
-
171
- /** The font size of the labels, for the style() of a label element.
172
- * @returns {string}
173
- * @protected */
174
- labelFontSizeCSS() {
175
- return this._css('label-font-size', this.labelFontSize, this._set.labelFontSize)
176
- }
177
-
178
- /**
179
- * Mark the root element as a type of legend: the CSS class gridviz-legend--<type>.
180
- * @param {string} type
181
- * @protected
182
- */
183
- setType(type) {
184
- this.div.classed('gridviz-legend--' + type, true)
185
- }
186
-
187
- /**
188
- * The width to draw at: this.width, or with width:'fill' the width of the container the legends are in
189
- * (the legendContainer of the map), less this legend's own padding.
190
- * @param {number} fallback Used when the container has no width (yet, or hidden)
191
- * @returns {number}
192
- * @protected
193
- */
194
- availableWidth(fallback) {
195
- if (!this.fill) return this.width
196
- const container = this.container
197
- const w = container && container.clientWidth
198
- if (!w) return fallback
199
- const p = parseFloat(this.padding) || 0
200
- return Math.max(60, Math.floor(w - 2 * p))
201
- }
202
-
203
- /**
204
- * Set the text of a d3 selection: as HTML if the legend has labelsAsHTML, as plain text otherwise.
205
- * @param {*} selection
206
- * @param {string} text
207
- * @protected
208
- */
209
- setText(selection, text) {
210
- return this.labelsAsHTML ? selection.html(text) : selection.text(text)
211
- }
212
-
213
- makeTitle() {
214
- if (!this.title) return
215
- this.setText(
216
- this.div
217
- .append('div')
218
- .classed('gridviz-legend-title', true)
219
- .style('font-size', this._css('title-font-size', this.titleFontSize, this._set.titleFontSize))
220
- .style('font-weight', this._css('title-font-weight', this.titleFontWeight, this._set.titleFontWeight))
221
- .style('margin-bottom', '7px'),
222
- this.title
223
- )
224
- }
225
-
226
- /**
227
- * Apply a style to the legend div.
228
- * @param {string} k
229
- * @param {string} v
230
- * @returns {this|string}
231
- */
232
- style(k, v) {
233
- if (arguments.length == 1) return this.div.style(k)
234
- this.div.style(k, v)
235
- return this
236
- }
237
-
238
- /**
239
- * @param {Object} opts
240
- * @abstract
241
- */
242
- update(opts = {}) {
243
- console.error('Legend update not implemented yet.')
244
- }
245
- }
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { select } from 'd3-selection'
5
+
6
+ /**
7
+ * A legend container.
8
+ *
9
+ * @module core
10
+ * @author Joseph Davies, Julien Gaffuri
11
+ */
12
+ export class Legend {
13
+ /**
14
+ * @param {Object} opts
15
+ */
16
+ constructor(opts) {
17
+ opts = opts || {}
18
+
19
+ /** @type {string} */
20
+ this.id = opts.id
21
+
22
+ //TODO stop using it. Use style method below instead.
23
+
24
+ /** @type {number} @deprecated */
25
+ this.top = opts.top
26
+ /** @type {number} @deprecated */
27
+ this.bottom = opts.bottom
28
+ /** @type {number} @deprecated */
29
+ this.left = opts.left
30
+ /** @type {number} @deprecated */
31
+ this.right = opts.right
32
+ /** @type {string} @deprecated */
33
+ this.background = opts.background || 'none'
34
+ /** @type {string} @deprecated */
35
+ this.padding = opts.padding || '5px'
36
+ /** @type {string} @deprecated */
37
+ this.border = opts.border || '0px'
38
+ /** @type {string} @deprecated */
39
+ this['border-radius'] = opts['border-radius'] || 'none'
40
+ /** @type {string} @deprecated */
41
+ this['box-shadow'] = opts['box-shadow'] || 'none'
42
+ /** @type {string} @deprecated */
43
+ this['font-family'] = opts['font-family'] || "'Helvetica Neue', Arial, Helvetica, sans-serif"
44
+ /** @type {string} @deprecated */
45
+ //this.width = opts.width
46
+ /** @type {string} @deprecated */
47
+ //this.height = opts.height
48
+
49
+ //the div element
50
+ if (this.id) this.div = select('#' + this.id)
51
+
52
+ if (!this.div || this.div.empty()) {
53
+ this.div = select(document.createElement('div'))
54
+ if (this.id) this.div.attr('id', this.id)
55
+ }
56
+
57
+ // Which style options the caller set. The ones they didn't set are written as a CSS variable with the
58
+ // usual value as its fallback (--gridviz-legend-font-family, ...): nothing changes unless a page defines the variable.
59
+ /** @private */
60
+ this._set = {
61
+ padding: opts.padding !== undefined,
62
+ fontFamily: opts['font-family'] !== undefined,
63
+ titleFontSize: opts.titleFontSize !== undefined,
64
+ titleFontWeight: opts.titleFontWeight !== undefined,
65
+ labelFontSize: opts.labelFontSize !== undefined,
66
+ }
67
+
68
+ // CSS classes, to style legends from a stylesheet: gridviz-legend on every legend, a modifier for the
69
+ // type (see each legend class), and whatever the caller asks for with the className option.
70
+ this.div.classed('gridviz-legend', true)
71
+ if (opts.className)
72
+ for (const c of String(opts.className).split(/\s+/)) if (c) this.div.classed(c, true)
73
+
74
+ //set style
75
+ this.div.style('background', this.background)
76
+ this.div.style('padding', this._css('padding', this.padding, this._set.padding))
77
+ this.div.style('border', this.border)
78
+ this.div.style('border-radius', this['border-radius'])
79
+ this.div.style('box-shadow', this['box-shadow'])
80
+ this.div.style('font-family', this._css('font-family', this['font-family'], this._set.fontFamily))
81
+
82
+ //if (this.width) this.div.style('width', this.width)
83
+ //if (this.height) this.div.style('height', this.height)
84
+
85
+ //title
86
+ this.title = opts.title
87
+ this.titleFontSize = opts.titleFontSize || '15px'
88
+ this.titleFontWeight = opts.titleFontWeight || '500'
89
+
90
+ //label
91
+ this.labelFontSize = opts.labelFontSize || '14px'
92
+ this.labelUnitText = opts.labelUnitText || ''
93
+ this.labelFormat = opts.labelFormat
94
+
95
+ /**
96
+ * Whether the title and the labels are HTML (to italicise a word, for example) instead of plain text.
97
+ * Off by default, which is plain text. Only turn it on for text you trust: it is not escaped.
98
+ * @type {boolean} */
99
+ this.labelsAsHTML = opts.labelsAsHTML === true
100
+
101
+ /**
102
+ * Whether hovering a swatch of the legend shows only the matching cells on the map.
103
+ * @type {boolean} */
104
+ this.interactive = opts.interactive !== false
105
+
106
+ /**
107
+ * The style the legend belongs to, and a function to redraw the map. Both set by the layer when it draws.
108
+ * @type {import('./Style.js').Style|undefined} */
109
+ this.style_ = undefined
110
+ /**
111
+ * All the styles of the layer the legend is in, set by the layer when it draws.
112
+ * @type {Array.<import('./Style.js').Style>|undefined} */
113
+ this.layerStyles = undefined
114
+ /** @type {function|undefined} */
115
+ this.redraw = undefined
116
+ /**
117
+ * The element the legends are drawn in, set by the layer when it draws.
118
+ * @type {HTMLElement|undefined} */
119
+ this.container = undefined
120
+
121
+ /**
122
+ * Draw at the width of the container instead of this.width (width:'fill').
123
+ * @type {boolean} @protected */
124
+ this.fill = opts.width === 'fill'
125
+ }
126
+
127
+ /**
128
+ * Show only the cells drawn in some colours on the map, or all of them again.
129
+ * @param {Array.<string>|null} colors
130
+ */
131
+ highlight(colors) {
132
+ const styles = this.highlightedStyles()
133
+ if (!this.interactive || !styles.length) return
134
+ const before = styles.some((s) => s.highlightColors !== null)
135
+ // flag the legend while hovered, so that the map's redraw leaves its element in place:
136
+ // a hovered element that is detached and appended again never gets its mouseleave
137
+ if (this.div) this.div.attr('data-gridviz-hovered', colors && colors.length ? '' : null)
138
+ for (const s of styles) s.setHighlight(colors)
139
+ // nothing to redraw when the highlight didn't change (mouse moving between a swatch's parts)
140
+ if (!before && styles.every((s) => s.highlightColors === null)) return
141
+ if (this.redraw) this.redraw()
142
+ }
143
+
144
+ /**
145
+ * The styles whose cells are highlighted when a swatch is hovered: the style of the legend, by default.
146
+ * A legend of several styles (a Venn diagram, for example) gives more.
147
+ * @returns {Array.<import('./Style.js').Style>}
148
+ * @protected
149
+ */
150
+ highlightedStyles() {
151
+ return this.style_ ? [this.style_] : []
152
+ }
153
+
154
+ /**
155
+ * Make a swatch react to the mouse: hovering it highlights the cells of a colour on the map.
156
+ * @param {*} selection The d3 selection of the swatch (any element)
157
+ * @param {string} color The colour the swatch stands for
158
+ * @param {{enter?:function, leave?:function}} [extra] Extra feedback on the swatch itself
159
+ */
160
+ bindHover(selection, color, extra) {
161
+ if (!this.interactive) return
162
+ selection
163
+ .style('cursor', 'pointer')
164
+ .on('mouseenter', () => {
165
+ if (extra && extra.enter) extra.enter()
166
+ this.highlight([color])
167
+ })
168
+ .on('mouseleave', () => {
169
+ if (extra && extra.leave) extra.leave()
170
+ this.highlight(null)
171
+ })
172
+ }
173
+
174
+ /**
175
+ * A style value with a CSS variable around it, unless the caller set the option themselves.
176
+ * @param {string} name The variable's name, after --gridviz-legend-
177
+ * @param {string} value The value to use when the variable isn't defined
178
+ * @param {boolean} custom
179
+ * @returns {string}
180
+ * @protected
181
+ */
182
+ _css(name, value, custom) {
183
+ return custom ? value : `var(--gridviz-legend-${name}, ${value})`
184
+ }
185
+
186
+ /** The font size of the labels, for the style() of a label element.
187
+ * @returns {string}
188
+ * @protected */
189
+ labelFontSizeCSS() {
190
+ return this._css('label-font-size', this.labelFontSize, this._set.labelFontSize)
191
+ }
192
+
193
+ /** The color of the labels (CSS variable --gridviz-legend-label-color), for the style() of a label element, as 'fill' for the text of a svg and 'color' for a html element.
194
+ * @returns {string}
195
+ * @protected */
196
+ labelColorCSS() {
197
+ return 'var(--gridviz-legend-label-color, #333)'
198
+ }
199
+
200
+ /**
201
+ * Mark the root element as a type of legend: the CSS class gridviz-legend--<type>.
202
+ * @param {string} type
203
+ * @protected
204
+ */
205
+ setType(type) {
206
+ this.div.classed('gridviz-legend--' + type, true)
207
+ }
208
+
209
+ /**
210
+ * The width to draw at: this.width, or with width:'fill' the width of the container the legends are in
211
+ * (the legendContainer of the map), less this legend's own padding.
212
+ * @param {number} fallback Used when the container has no width (yet, or hidden)
213
+ * @returns {number}
214
+ * @protected
215
+ */
216
+ availableWidth(fallback) {
217
+ if (!this.fill) return this.width
218
+ const container = this.container
219
+ const w = container && container.clientWidth
220
+ if (!w) return fallback
221
+ const p = parseFloat(this.padding) || 0
222
+ return Math.max(60, Math.floor(w - 2 * p))
223
+ }
224
+
225
+ /**
226
+ * Set the text of a d3 selection: as HTML if the legend has labelsAsHTML, as plain text otherwise.
227
+ * @param {*} selection
228
+ * @param {string} text
229
+ * @protected
230
+ */
231
+ setText(selection, text) {
232
+ return this.labelsAsHTML ? selection.html(text) : selection.text(text)
233
+ }
234
+
235
+ makeTitle() {
236
+ if (!this.title) return
237
+ this.setText(
238
+ this.div
239
+ .append('div')
240
+ .classed('gridviz-legend-title', true)
241
+ .style('font-size', this._css('title-font-size', this.titleFontSize, this._set.titleFontSize))
242
+ .style('font-weight', this._css('title-font-weight', this.titleFontWeight, this._set.titleFontWeight))
243
+ .style('margin-bottom', '7px'),
244
+ this.title
245
+ )
246
+ }
247
+
248
+ /**
249
+ * Apply a style to the legend div.
250
+ * @param {string} k
251
+ * @param {string} v
252
+ * @returns {this|string}
253
+ */
254
+ style(k, v) {
255
+ if (arguments.length == 1) return this.div.style(k)
256
+ this.div.style(k, v)
257
+ return this
258
+ }
259
+
260
+ /**
261
+ * @param {Object} opts
262
+ * @abstract
263
+ */
264
+ update(opts = {}) {
265
+ console.error('Legend update not implemented yet.')
266
+ }
267
+ }