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.
@@ -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 addLegends(legendComp, lg) {
121
- if (Array.isArray(lg)) for (const lg_ of lg) this.addLegends(legendComp, lg_)
122
- else legendComp.node().append(lg.div.node())
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
- d.append('div')
98
- //show on right of graphic
99
- .style('display', 'inline')
109
+ this.setText(
110
+ d
111
+ .append('div')
112
+ //show on right of graphic
113
+ .style('display', 'inline')
100
114
 
101
- //center vertically
102
- //.style("position", "absolute").style("top", "0").style("bottom", "0")
115
+ //center vertically
116
+ //.style("position", "absolute").style("top", "0").style("bottom", "0")
103
117
 
104
- .style('padding-left', '5px')
105
- .style('font-size', this.labelFontSize)
106
- .text(cat[1])
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
- this.width = opts.width || 300
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 w = this.width / nb
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
- .attr('width', this.width)
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.append('rect')
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.labelFontSize)
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
- this.width = opts.width || 300
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 svgW = this.width + 2 * this.margin
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 = this.width,
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.labelFontSize)
71
+ .style('font-size', this.labelFontSizeCSS())
69
72
  .text(this.label + (this.labelUnitText ? ' ' : '') + this.labelUnitText)
70
73
  }
71
74
  }
@@ -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.labelFontSize)
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 contentChanged = this.mapContentChanged(geoCanvas.view, cells.length)
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()
@@ -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
- return (t) => {
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