gridviz 3.0.37 → 3.0.39
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.
- package/dist/gridviz.js +8491 -11551
- package/dist/gridviz.min.js +2 -2
- package/package.json +74 -74
- package/src/button/Button.js +2 -1
- package/src/button/FullscreenButton.js +2 -0
- package/src/button/ZoomButtons.js +3 -2
- package/src/core/GeoCanvas.js +34 -38
- package/src/core/Legend.js +145 -8
- package/src/core/Map.js +17 -2
- package/src/core/Style.js +48 -0
- package/src/core/Tooltip.js +3 -0
- package/src/layer/GridLayer.js +25 -5
- package/src/legend/ColorCategoryLegend.js +25 -9
- package/src/legend/ColorDiscreteLegend.js +19 -5
- package/src/legend/ColorLegend.js +11 -5
- package/src/legend/OrientationLegend.js +6 -3
- package/src/legend/SizeLegend.js +9 -2
- package/src/legend/TernaryLegend.js +14 -2
- package/src/style/ShapeColorSizeStyle.js +4 -0
- package/src/style/SquareColorCategoryWebGLStyle.js +9 -1
- package/src/utils/scale.js +30 -1
package/src/layer/GridLayer.js
CHANGED
|
@@ -68,6 +68,9 @@ export class GridLayer extends Layer {
|
|
|
68
68
|
if (this.blendOperation) ctx.globalCompositeOperation = this.blendOperation(z)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// tell the legends where they are drawn, so that they can fit it (width:'fill')
|
|
72
|
+
if (legend) for (const s of this.styles) GridLayer.setLegendContainer(legend.node(), s.legends, s['styles'])
|
|
73
|
+
|
|
71
74
|
//draw cells, style by style
|
|
72
75
|
for (const s of this.styles) {
|
|
73
76
|
//check if style is visible
|
|
@@ -102,14 +105,14 @@ export class GridLayer extends Layer {
|
|
|
102
105
|
for (const s of this.styles) {
|
|
103
106
|
//check if style is visible
|
|
104
107
|
if (s.visible && !s.visible(z)) continue
|
|
105
|
-
GridLayer.addLegends(legend, s.legends)
|
|
108
|
+
GridLayer.addLegends(legend, s.legends, s, geoCanvas)
|
|
106
109
|
|
|
107
110
|
//case for styles of styles, like kernel smoothing
|
|
108
111
|
//TODO do better
|
|
109
112
|
if (s['styles']) {
|
|
110
113
|
for (const s2 of s['styles']) {
|
|
111
114
|
if (s2.visible && !s2.visible(z)) continue
|
|
112
|
-
GridLayer.addLegends(legend, s2.legends)
|
|
115
|
+
GridLayer.addLegends(legend, s2.legends, s2, geoCanvas)
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
}
|
|
@@ -117,9 +120,26 @@ export class GridLayer extends Layer {
|
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
/** @private */
|
|
120
|
-
static
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
static setLegendContainer(container, legends, subStyles) {
|
|
124
|
+
// legends can be nested in arrays, like in Style.updateLegendsRecursive
|
|
125
|
+
const set = (lg) => {
|
|
126
|
+
if (Array.isArray(lg)) lg.forEach(set)
|
|
127
|
+
else if (lg) lg.container = container
|
|
128
|
+
}
|
|
129
|
+
set(legends)
|
|
130
|
+
for (const s2 of subStyles || []) GridLayer.setLegendContainer(container, s2.legends, undefined)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** @private */
|
|
134
|
+
static addLegends(legendComp, lg, style, geoCanvas) {
|
|
135
|
+
if (Array.isArray(lg)) for (const lg_ of lg) this.addLegends(legendComp, lg_, style, geoCanvas)
|
|
136
|
+
else {
|
|
137
|
+
// so that hovering a swatch can highlight the cells of the style on the map
|
|
138
|
+
lg.style_ = style
|
|
139
|
+
lg.redraw = () => (geoCanvas.redraw ? geoCanvas.redraw() : undefined)
|
|
140
|
+
// a hovered legend is still in place (see Map.redrawNow): moving it would lose its mouseleave
|
|
141
|
+
if (lg.div.node().parentNode !== legendComp.node()) legendComp.node().append(lg.div.node())
|
|
142
|
+
}
|
|
123
143
|
}
|
|
124
144
|
|
|
125
145
|
/**
|
|
@@ -28,6 +28,7 @@ export class ColorCategoryLegend extends Legend {
|
|
|
28
28
|
this.dimension = opts.dimension || { r: 8 }
|
|
29
29
|
this.strokeColor = opts.strokeColor || 'gray'
|
|
30
30
|
this.strokeWidth = opts.strokeWidth || 1
|
|
31
|
+
this.setType('category')
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -47,7 +48,12 @@ export class ColorCategoryLegend extends Legend {
|
|
|
47
48
|
const cat = this.colorLabel[i]
|
|
48
49
|
|
|
49
50
|
//make div for category
|
|
50
|
-
const d = this.div.append('div')
|
|
51
|
+
const d = this.div.append('div').classed('gridviz-legend-category', true)
|
|
52
|
+
// hovering a category shows only its cells on the map
|
|
53
|
+
this.bindHover(d, cat[0], {
|
|
54
|
+
enter: () => d.classed('gridviz-legend-category--active', true).select('svg').select('rect, circle').style('stroke', 'red'),
|
|
55
|
+
leave: () => d.classed('gridviz-legend-category--active', false).select('svg').select('rect, circle').style('stroke', this.strokeColor),
|
|
56
|
+
})
|
|
51
57
|
//to enable vertical centering
|
|
52
58
|
//.style("position", "relative")
|
|
53
59
|
|
|
@@ -58,13 +64,16 @@ export class ColorCategoryLegend extends Legend {
|
|
|
58
64
|
const h = this.dimension.h || 15
|
|
59
65
|
const w = this.dimension.w || 20
|
|
60
66
|
d.append('div')
|
|
67
|
+
.classed('gridviz-legend-symbol-wrapper', true)
|
|
61
68
|
.style('display', 'inline')
|
|
62
69
|
|
|
63
70
|
.append('svg')
|
|
71
|
+
.classed('gridviz-legend-graphic', true)
|
|
64
72
|
.attr('width', w + 2 * sw)
|
|
65
73
|
.attr('height', h + 2 * sw)
|
|
66
74
|
|
|
67
75
|
.append('rect')
|
|
76
|
+
.classed('gridviz-legend-swatch', true)
|
|
68
77
|
.attr('x', sw)
|
|
69
78
|
.attr('y', sw)
|
|
70
79
|
.attr('width', w)
|
|
@@ -76,13 +85,16 @@ export class ColorCategoryLegend extends Legend {
|
|
|
76
85
|
const r = this.dimension.r || 8
|
|
77
86
|
const h = 2 * r + 2 * sw
|
|
78
87
|
d.append('div')
|
|
88
|
+
.classed('gridviz-legend-symbol-wrapper', true)
|
|
79
89
|
.style('display', 'inline')
|
|
80
90
|
|
|
81
91
|
.append('svg')
|
|
92
|
+
.classed('gridviz-legend-graphic', true)
|
|
82
93
|
.attr('width', h)
|
|
83
94
|
.attr('height', h)
|
|
84
95
|
|
|
85
96
|
.append('circle')
|
|
97
|
+
.classed('gridviz-legend-swatch', true)
|
|
86
98
|
.attr('cx', r + sw)
|
|
87
99
|
.attr('cy', r + sw)
|
|
88
100
|
.attr('r', r)
|
|
@@ -94,16 +106,20 @@ export class ColorCategoryLegend extends Legend {
|
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
//write label text
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
this.setText(
|
|
110
|
+
d
|
|
111
|
+
.append('div')
|
|
112
|
+
//show on right of graphic
|
|
113
|
+
.style('display', 'inline')
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
//center vertically
|
|
116
|
+
//.style("position", "absolute").style("top", "0").style("bottom", "0")
|
|
103
117
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
.classed('gridviz-legend-label', true)
|
|
119
|
+
.style('padding-left', '5px')
|
|
120
|
+
.style('font-size', this.labelFontSizeCSS()),
|
|
121
|
+
cat[1]
|
|
122
|
+
)
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
125
|
}
|
|
@@ -21,8 +21,10 @@ export class ColorDiscreteLegend extends Legend {
|
|
|
21
21
|
/** @private @type {function(import('../core/Style').ViewScale):Array.<number>} */
|
|
22
22
|
this.breaks = opts.breaks
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// a number of pixels, or 'fill' for the width of the container
|
|
25
|
+
this.width = opts.width === 'fill' ? 300 : opts.width || 300
|
|
25
26
|
this.height = opts.height || 15
|
|
27
|
+
this.setType('discrete')
|
|
26
28
|
|
|
27
29
|
this.tickSize = opts.tickSize || 3
|
|
28
30
|
|
|
@@ -48,27 +50,38 @@ export class ColorDiscreteLegend extends Legend {
|
|
|
48
50
|
//classes
|
|
49
51
|
const nb = colors.length
|
|
50
52
|
if (nb == 0) return
|
|
51
|
-
const
|
|
53
|
+
const width = this.availableWidth(this.width)
|
|
54
|
+
const w = width / nb
|
|
52
55
|
|
|
53
56
|
//make svg element
|
|
54
57
|
const svg = this.div
|
|
55
58
|
.append('svg')
|
|
56
|
-
.
|
|
59
|
+
.classed('gridviz-legend-graphic', true)
|
|
60
|
+
.attr('width', width)
|
|
57
61
|
.attr('height', this.height + this.tickSize + 2 + 10)
|
|
58
62
|
|
|
59
63
|
//draw graphic elements
|
|
60
64
|
for (let i = 0; i < nb; i++) {
|
|
61
|
-
svg
|
|
65
|
+
const rect = svg
|
|
66
|
+
.append('rect')
|
|
67
|
+
.classed('gridviz-legend-swatch', true)
|
|
62
68
|
.attr('x', i * w)
|
|
63
69
|
.attr('y', 0)
|
|
64
70
|
.attr('width', w)
|
|
65
71
|
.attr('height', this.height)
|
|
66
72
|
.style('fill', colors[i])
|
|
73
|
+
// hovering a class shows only its cells on the map
|
|
74
|
+
this.bindHover(rect, colors[i], {
|
|
75
|
+
// a presentation attribute, so that a stylesheet can restyle the active swatch
|
|
76
|
+
enter: () => rect.classed('gridviz-legend-swatch--active', true).attr('stroke', 'red'),
|
|
77
|
+
leave: () => rect.classed('gridviz-legend-swatch--active', false).attr('stroke', null),
|
|
78
|
+
})
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
//tick line
|
|
70
82
|
for (let i = 1; i < nb; i++) {
|
|
71
83
|
svg.append('line')
|
|
84
|
+
.classed('gridviz-legend-tick', true)
|
|
72
85
|
.attr('x1', w * i)
|
|
73
86
|
.attr('y1', 0)
|
|
74
87
|
.attr('x2', w * i)
|
|
@@ -84,10 +97,11 @@ export class ColorDiscreteLegend extends Legend {
|
|
|
84
97
|
|
|
85
98
|
//label
|
|
86
99
|
svg.append('text')
|
|
100
|
+
.classed('gridviz-legend-label', true)
|
|
87
101
|
.attr('id', 'ticklabel_' + i)
|
|
88
102
|
.attr('x', w * i)
|
|
89
103
|
.attr('y', this.height + this.tickSize + 2)
|
|
90
|
-
.style('font-size', this.
|
|
104
|
+
.style('font-size', this.labelFontSizeCSS())
|
|
91
105
|
//.style("font-weight", "bold")
|
|
92
106
|
//.style("font-family", "Arial")
|
|
93
107
|
.style('text-anchor', 'middle')
|
|
@@ -35,8 +35,10 @@ export class ColorLegend extends Legend {
|
|
|
35
35
|
this.fontSize = opts.fontSize || '0.8em'
|
|
36
36
|
this.invert = opts.invert
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// a number of pixels, or 'fill' for the width of the container
|
|
39
|
+
this.width = opts.width === 'fill' ? 300 : opts.width || 300
|
|
39
40
|
this.height = opts.height || 15
|
|
41
|
+
this.setType('color')
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -49,21 +51,23 @@ export class ColorLegend extends Legend {
|
|
|
49
51
|
//title
|
|
50
52
|
this.makeTitle()
|
|
51
53
|
|
|
52
|
-
const
|
|
54
|
+
const width = this.availableWidth(this.width)
|
|
55
|
+
const svgW = width + 2 * this.margin
|
|
53
56
|
const svgH = this.height + this.margin + this.tickSize + 10
|
|
54
|
-
const svg = this.div.append('svg').attr('width', svgW).attr('height', svgH)
|
|
57
|
+
const svg = this.div.append('svg').classed('gridviz-legend-graphic', true).attr('width', svgW).attr('height', svgH)
|
|
55
58
|
// <rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
|
|
56
59
|
|
|
57
|
-
const g = svg.append('g').attr('transform', 'translate(' + this.margin + ' ' + 0 + ')')
|
|
60
|
+
const g = svg.append('g').classed('gridviz-legend-bar', true).attr('transform', 'translate(' + this.margin + ' ' + 0 + ')')
|
|
58
61
|
|
|
59
62
|
//draw color bar
|
|
60
|
-
const w =
|
|
63
|
+
const w = width,
|
|
61
64
|
h = this.height
|
|
62
65
|
const step = 5
|
|
63
66
|
for (let i = 0; i < w; i += step) {
|
|
64
67
|
let t = i / (w - 1)
|
|
65
68
|
if (this.invert) t = 1 - t
|
|
66
69
|
g.append('rect')
|
|
70
|
+
.classed('gridviz-legend-swatch', true)
|
|
67
71
|
.attr('x', i)
|
|
68
72
|
.attr('y', 0)
|
|
69
73
|
.attr('width', step)
|
|
@@ -76,6 +80,7 @@ export class ColorLegend extends Legend {
|
|
|
76
80
|
|
|
77
81
|
//tick line
|
|
78
82
|
g.append('line')
|
|
83
|
+
.classed('gridviz-legend-tick', true)
|
|
79
84
|
.attr('x1', w * t)
|
|
80
85
|
.attr('y1', 0)
|
|
81
86
|
.attr('x2', w * t)
|
|
@@ -84,6 +89,7 @@ export class ColorLegend extends Legend {
|
|
|
84
89
|
|
|
85
90
|
//prepare tick label
|
|
86
91
|
g.append('text')
|
|
92
|
+
.classed('gridviz-legend-label', true)
|
|
87
93
|
.attr('id', 'ticklabel_' + i)
|
|
88
94
|
.attr('x', w * t)
|
|
89
95
|
.attr('y', h + this.tickSize + 2)
|
|
@@ -13,6 +13,7 @@ export class OrientationLegend extends Legend {
|
|
|
13
13
|
/** @param {Object} opts */
|
|
14
14
|
constructor(opts) {
|
|
15
15
|
super(opts)
|
|
16
|
+
this.setType('orientation')
|
|
16
17
|
opts = opts || {}
|
|
17
18
|
|
|
18
19
|
//orientation
|
|
@@ -38,7 +39,7 @@ export class OrientationLegend extends Legend {
|
|
|
38
39
|
//title
|
|
39
40
|
this.makeTitle()
|
|
40
41
|
|
|
41
|
-
const d = this.div.append('div')
|
|
42
|
+
const d = this.div.append('div').classed('gridviz-legend-category', true)
|
|
42
43
|
|
|
43
44
|
//compute segment color, width and length
|
|
44
45
|
const color = this.color(opts.resolution, opts.z, opts.viewScale)
|
|
@@ -47,13 +48,14 @@ export class OrientationLegend extends Legend {
|
|
|
47
48
|
|
|
48
49
|
//draw SVG segment
|
|
49
50
|
const svgS = Math.max(lengthPix, widthPix)
|
|
50
|
-
const svg = d.append('svg').attr('width', svgS).attr('height', svgS).style('', 'inline-block')
|
|
51
|
+
const svg = d.append('svg').classed('gridviz-legend-graphic', true).attr('width', svgS).attr('height', svgS).style('', 'inline-block')
|
|
51
52
|
|
|
52
53
|
const cos = Math.cos((-this.orientation * Math.PI) / 180)
|
|
53
54
|
const sin = Math.sin((-this.orientation * Math.PI) / 180)
|
|
54
55
|
const dc = svgS * 0.5,
|
|
55
56
|
l2 = lengthPix * 0.5
|
|
56
57
|
svg.append('line')
|
|
58
|
+
.classed('gridviz-legend-symbol', true)
|
|
57
59
|
.attr('x1', dc - cos * l2)
|
|
58
60
|
.attr('y1', dc - sin * l2)
|
|
59
61
|
.attr('x2', dc + cos * l2)
|
|
@@ -63,9 +65,10 @@ export class OrientationLegend extends Legend {
|
|
|
63
65
|
|
|
64
66
|
//label
|
|
65
67
|
d.append('div')
|
|
68
|
+
.classed('gridviz-legend-label', true)
|
|
66
69
|
.style('display', 'inline')
|
|
67
70
|
.style('padding-left', '5px')
|
|
68
|
-
.style('font-size', this.
|
|
71
|
+
.style('font-size', this.labelFontSizeCSS())
|
|
69
72
|
.text(this.label + (this.labelUnitText ? ' ' : '') + this.labelUnitText)
|
|
70
73
|
}
|
|
71
74
|
}
|
package/src/legend/SizeLegend.js
CHANGED
|
@@ -15,6 +15,7 @@ export class SizeLegend extends Legend {
|
|
|
15
15
|
/** @param {Object} opts */
|
|
16
16
|
constructor(opts) {
|
|
17
17
|
super(opts)
|
|
18
|
+
this.setType('size')
|
|
18
19
|
opts = opts || {}
|
|
19
20
|
|
|
20
21
|
/** A function returning the text label, from the view scale and list of cells, resolution and zoom
|
|
@@ -62,7 +63,7 @@ export class SizeLegend extends Legend {
|
|
|
62
63
|
//format label, if specified and possible
|
|
63
64
|
if (this.labelFormat && !isNaN(+label)) label = this.labelFormat(label)
|
|
64
65
|
|
|
65
|
-
const d = this.div.append('div')
|
|
66
|
+
const d = this.div.append('div').classed('gridviz-legend-category', true)
|
|
66
67
|
//to enable vertical centering
|
|
67
68
|
//.style("position", "relative")
|
|
68
69
|
|
|
@@ -70,6 +71,7 @@ export class SizeLegend extends Legend {
|
|
|
70
71
|
const svg = () =>
|
|
71
72
|
d
|
|
72
73
|
.append('svg')
|
|
74
|
+
.classed('gridviz-legend-graphic', true)
|
|
73
75
|
.attr('width', sizePix + this.strokeWidth + 2)
|
|
74
76
|
.attr('height', sizePix + this.strokeWidth + 2)
|
|
75
77
|
.style('', 'inline-block')
|
|
@@ -77,6 +79,7 @@ export class SizeLegend extends Legend {
|
|
|
77
79
|
if (this.shape === 'square') {
|
|
78
80
|
svg()
|
|
79
81
|
.append('rect')
|
|
82
|
+
.classed('gridviz-legend-symbol', true)
|
|
80
83
|
.attr('x', 0)
|
|
81
84
|
.attr('y', 0)
|
|
82
85
|
.attr('width', sizePix)
|
|
@@ -89,6 +92,7 @@ export class SizeLegend extends Legend {
|
|
|
89
92
|
const r = (sizePix + this.strokeWidth) * 0.5
|
|
90
93
|
svg()
|
|
91
94
|
.append('circle')
|
|
95
|
+
.classed('gridviz-legend-symbol', true)
|
|
92
96
|
.attr('cx', r + 1)
|
|
93
97
|
.attr('cy', r + 1)
|
|
94
98
|
.attr('r', r)
|
|
@@ -108,6 +112,7 @@ export class SizeLegend extends Legend {
|
|
|
108
112
|
|
|
109
113
|
const svg = d
|
|
110
114
|
.append('svg')
|
|
115
|
+
.classed('gridviz-legend-graphic', true)
|
|
111
116
|
.attr('width', lengthPix)
|
|
112
117
|
.attr('height', sizePix)
|
|
113
118
|
.style('', 'inline-block')
|
|
@@ -115,6 +120,7 @@ export class SizeLegend extends Legend {
|
|
|
115
120
|
//TODO orientation
|
|
116
121
|
//<line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" />
|
|
117
122
|
svg.append('line')
|
|
123
|
+
.classed('gridviz-legend-symbol', true)
|
|
118
124
|
.attr('x1', 0)
|
|
119
125
|
.attr('y1', sizePix / 2)
|
|
120
126
|
.attr('x2', lengthPix)
|
|
@@ -127,9 +133,10 @@ export class SizeLegend extends Legend {
|
|
|
127
133
|
|
|
128
134
|
//label
|
|
129
135
|
d.append('div')
|
|
136
|
+
.classed('gridviz-legend-label', true)
|
|
130
137
|
.style('display', 'inline')
|
|
131
138
|
.style('padding-left', '5px')
|
|
132
|
-
.style('font-size', this.
|
|
139
|
+
.style('font-size', this.labelFontSizeCSS())
|
|
133
140
|
.text(label + (this.labelUnitText ? ' ' : '') + this.labelUnitText)
|
|
134
141
|
}
|
|
135
142
|
}
|
|
@@ -28,6 +28,7 @@ export class TernaryLegend extends Legend {
|
|
|
28
28
|
this.rightText = opts.rightText || 'Category 2'
|
|
29
29
|
|
|
30
30
|
this.centerCoefficient = opts.centerCoefficient || this.classifier.centerCoefficient
|
|
31
|
+
this.setType('ternary')
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -51,10 +52,12 @@ export class TernaryLegend extends Legend {
|
|
|
51
52
|
|
|
52
53
|
const padding = 2
|
|
53
54
|
const fontSize = 12
|
|
55
|
+
const legend = this
|
|
54
56
|
|
|
55
57
|
//make svg element
|
|
56
58
|
const svg = this.div
|
|
57
59
|
.append('svg')
|
|
60
|
+
.classed('gridviz-legend-graphic', true)
|
|
58
61
|
.attr('width', w + selectionStrokeWidth)
|
|
59
62
|
.attr('height', h + 4 * padding + 2 * fontSize)
|
|
60
63
|
|
|
@@ -62,6 +65,7 @@ export class TernaryLegend extends Legend {
|
|
|
62
65
|
svg.append('text')
|
|
63
66
|
.attr('x', w / 2)
|
|
64
67
|
.attr('y', padding + fontSize)
|
|
68
|
+
.classed('gridviz-legend-label', true)
|
|
65
69
|
.text(this.topText)
|
|
66
70
|
.attr('font-size', fontSize)
|
|
67
71
|
.attr('text-anchor', 'middle')
|
|
@@ -69,6 +73,7 @@ export class TernaryLegend extends Legend {
|
|
|
69
73
|
svg.append('text')
|
|
70
74
|
.attr('x', 0)
|
|
71
75
|
.attr('y', 3 * padding + 2 * fontSize + h)
|
|
76
|
+
.classed('gridviz-legend-label', true)
|
|
72
77
|
.text(this.leftText)
|
|
73
78
|
.attr('font-size', fontSize)
|
|
74
79
|
.attr('text-anchor', 'start')
|
|
@@ -76,6 +81,7 @@ export class TernaryLegend extends Legend {
|
|
|
76
81
|
svg.append('text')
|
|
77
82
|
.attr('x', w)
|
|
78
83
|
.attr('y', 3 * padding + 2 * fontSize + h)
|
|
84
|
+
.classed('gridviz-legend-label', true)
|
|
79
85
|
.text(this.rightText)
|
|
80
86
|
.attr('font-size', fontSize)
|
|
81
87
|
.attr('text-anchor', 'end')
|
|
@@ -83,6 +89,7 @@ export class TernaryLegend extends Legend {
|
|
|
83
89
|
//triangle group
|
|
84
90
|
const g = svg
|
|
85
91
|
.append('g')
|
|
92
|
+
.classed('gridviz-legend-triangle', true)
|
|
86
93
|
.attr(
|
|
87
94
|
'transform',
|
|
88
95
|
'translate(' +
|
|
@@ -96,12 +103,16 @@ export class TernaryLegend extends Legend {
|
|
|
96
103
|
const setAttributes = (elt, color, text) => {
|
|
97
104
|
//elt.raise();
|
|
98
105
|
elt.attr('fill', color)
|
|
106
|
+
.classed('gridviz-legend-swatch', true)
|
|
99
107
|
//.attr("stroke", colorOver)
|
|
100
108
|
//.attr("stroke-width", 0)
|
|
101
109
|
//.attr("stroke-linejoin", "round")
|
|
110
|
+
.style('cursor', this.interactive ? 'pointer' : null)
|
|
102
111
|
.on('mouseover', function (e) {
|
|
103
112
|
/*this.parentNode.appendChild(this); select(this).attr("stroke-width", selectionStrokeWidth);*/
|
|
104
|
-
select(this).attr('fill', selectionColor)
|
|
113
|
+
select(this).classed('gridviz-legend-swatch--active', true).attr('fill', selectionColor)
|
|
114
|
+
// show only the cells of this class on the map
|
|
115
|
+
legend.highlight([color])
|
|
105
116
|
if (!tt || !text) return
|
|
106
117
|
tt.html(text)
|
|
107
118
|
tt.setPosition(e)
|
|
@@ -109,7 +120,8 @@ export class TernaryLegend extends Legend {
|
|
|
109
120
|
})
|
|
110
121
|
.on('mouseout', function () {
|
|
111
122
|
/*select(this).attr("stroke-width", 0);*/
|
|
112
|
-
select(this).attr('fill', color)
|
|
123
|
+
select(this).classed('gridviz-legend-swatch--active', false).attr('fill', color)
|
|
124
|
+
legend.highlight(null)
|
|
113
125
|
if (tt) tt.hide()
|
|
114
126
|
})
|
|
115
127
|
if (tt && text)
|
|
@@ -45,6 +45,9 @@ export class ShapeColorSizeStyle extends Style {
|
|
|
45
45
|
|
|
46
46
|
const r2 = resolution * 0.5
|
|
47
47
|
|
|
48
|
+
// the legend swatch hovered, if any: only the cells of its colour are drawn
|
|
49
|
+
const highlight = this.highlightColors
|
|
50
|
+
|
|
48
51
|
// Precompute if color, size, and shape are functions, for efficiency
|
|
49
52
|
const isColorFunction = typeof this.color === 'function'
|
|
50
53
|
const isSizeFunction = typeof this.size === 'function'
|
|
@@ -64,6 +67,7 @@ export class ShapeColorSizeStyle extends Style {
|
|
|
64
67
|
//@ts-ignore
|
|
65
68
|
const col = colorFunction ? colorFunction(c, resolution, z, viewScale) : defaultColor
|
|
66
69
|
if (!col || col === 'none') continue
|
|
70
|
+
if (highlight && !this.isHighlighted(col)) continue
|
|
67
71
|
|
|
68
72
|
// Determine size
|
|
69
73
|
//@ts-ignore
|
|
@@ -86,6 +86,8 @@ export class SquareColorCategoryWebGLStyle extends Style {
|
|
|
86
86
|
this.y = undefined
|
|
87
87
|
this.z = undefined
|
|
88
88
|
this.cellsNb = undefined
|
|
89
|
+
/** The highlight (see Style.setHighlight) the vertices were bound with @private */
|
|
90
|
+
this.highlightKey = ''
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
/**
|
|
@@ -149,6 +151,8 @@ export class SquareColorCategoryWebGLStyle extends Style {
|
|
|
149
151
|
nb = cells.length
|
|
150
152
|
const verticesBuffer = new Float32Array(2 * nb)
|
|
151
153
|
const iBuffer = new Float32Array(nb)
|
|
154
|
+
// categories shown while a legend swatch is hovered: those drawn in the colour of the swatch
|
|
155
|
+
const shown = this.highlightColors ? this.colors.map((c) => this.isHighlighted(c)) : null
|
|
152
156
|
let n = 0
|
|
153
157
|
for (let i = 0; i < nb; i++) {
|
|
154
158
|
c = cells[i]
|
|
@@ -162,6 +166,7 @@ export class SquareColorCategoryWebGLStyle extends Style {
|
|
|
162
166
|
console.log('Unexpected category index: ' + cat + ' ' + i_)
|
|
163
167
|
continue
|
|
164
168
|
}
|
|
169
|
+
if (shown && !shown[+i_]) continue
|
|
165
170
|
verticesBuffer[2 * n] = c.x + r2
|
|
166
171
|
verticesBuffer[2 * n + 1] = c.y + r2
|
|
167
172
|
iBuffer[n] = +i_
|
|
@@ -228,7 +233,10 @@ export class SquareColorCategoryWebGLStyle extends Style {
|
|
|
228
233
|
// x/y/z and cell count alone. The upload is also needed when the shared buffers
|
|
229
234
|
// hold another style's data (bufferOwner), or the context is new to this style
|
|
230
235
|
// (e.g. replaced after being lost).
|
|
231
|
-
const
|
|
236
|
+
const highlightKey = this.highlightColors ? this.highlightColors.join('|') : ''
|
|
237
|
+
const highlightChanged = highlightKey !== this.highlightKey
|
|
238
|
+
this.highlightKey = highlightKey
|
|
239
|
+
const contentChanged = this.mapContentChanged(geoCanvas.view, cells.length) || highlightChanged
|
|
232
240
|
if (contentChanged || contextChanged || bufferOwner(gl, POSITION_BUFFER) !== this)
|
|
233
241
|
this.pointsNb = this.bindVertices(cells, resolution, z, viewScale)
|
|
234
242
|
this.bindAttributes()
|
package/src/utils/scale.js
CHANGED
|
@@ -16,10 +16,33 @@ import { scaleQuantile } from 'd3-scale'
|
|
|
16
16
|
* @module utils
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The inverse of a stretching function, within [0,1]: its own invert function if it has one,
|
|
21
|
+
* else found by bisection (a stretching is increasing).
|
|
22
|
+
* @param {function(number):number} stretching
|
|
23
|
+
* @param {number} t
|
|
24
|
+
* @returns {number}
|
|
25
|
+
*/
|
|
26
|
+
const invertStretching = (stretching, t) => {
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
if (stretching.invert) return stretching.invert(t)
|
|
29
|
+
let lo = 0,
|
|
30
|
+
hi = 1
|
|
31
|
+
for (let i = 0; i < 50; i++) {
|
|
32
|
+
const mid = (lo + hi) / 2
|
|
33
|
+
if (stretching(mid) < t) lo = mid
|
|
34
|
+
else hi = mid
|
|
35
|
+
}
|
|
36
|
+
return (lo + hi) / 2
|
|
37
|
+
}
|
|
38
|
+
|
|
19
39
|
/**
|
|
20
40
|
* Generic function for view scale - continuous or discrete
|
|
21
41
|
*
|
|
22
42
|
* @param {{ valueFunction:function(import("../core/Dataset").Cell):number, minValue?:number, minSizePix?:number, maxSizeFactor?:number, range?:[number, number], domain?:[number, number], stretching?:function(number):number, classNumber?:number}} opts
|
|
43
|
+
* The scale has an invert(t) function, like the one of viewScaleColor: it returns the value of the domain
|
|
44
|
+
* for a t within [0,1] (what a legend needs to write its labels).
|
|
45
|
+
*
|
|
23
46
|
* @returns {function(Array.<import("../core/Dataset").Cell>):Scale}
|
|
24
47
|
*/
|
|
25
48
|
export const viewScale = (opts) => {
|
|
@@ -38,7 +61,7 @@ export const viewScale = (opts) => {
|
|
|
38
61
|
domainMin = domain[0]
|
|
39
62
|
const rangeSize = range[1] - range[0],
|
|
40
63
|
rangeMin = range[0]
|
|
41
|
-
|
|
64
|
+
const scale = (t) => {
|
|
42
65
|
//scale to [0,1]
|
|
43
66
|
t = (t - domainMin) / domainSize
|
|
44
67
|
//stretch
|
|
@@ -48,6 +71,12 @@ export const viewScale = (opts) => {
|
|
|
48
71
|
//scale to range
|
|
49
72
|
return rangeMin + t * rangeSize
|
|
50
73
|
}
|
|
74
|
+
//the domain value for a position t within [0,1], as the invert of viewScaleColor
|
|
75
|
+
scale.invert = (t) => {
|
|
76
|
+
if (stretching) t = invertStretching(stretching, t)
|
|
77
|
+
return domainMin + t * domainSize
|
|
78
|
+
}
|
|
79
|
+
return scale
|
|
51
80
|
}
|
|
52
81
|
}
|
|
53
82
|
|