autumnplot-gl 2.2.3 → 3.1.0

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 (49) hide show
  1. package/README.md +65 -8
  2. package/dist/110.autumnplot-gl.js +2 -0
  3. package/dist/110.autumnplot-gl.js.map +1 -0
  4. package/dist/autumnplot-gl.js +3 -0
  5. package/dist/autumnplot-gl.js.LICENSE.txt +12 -0
  6. package/dist/autumnplot-gl.js.map +1 -0
  7. package/dist/marchingsquares.wasm +0 -0
  8. package/lib/AutumnTypes.d.ts +14 -13
  9. package/lib/Barbs.d.ts +10 -5
  10. package/lib/Barbs.js +22 -5
  11. package/lib/BillboardCollection.d.ts +11 -7
  12. package/lib/BillboardCollection.js +52 -30
  13. package/lib/ColorBar.js +51 -7
  14. package/lib/Colormap.d.ts +14 -3
  15. package/lib/Colormap.js +63 -12
  16. package/lib/Contour.d.ts +73 -18
  17. package/lib/Contour.js +180 -148
  18. package/lib/ContourCreator.d.ts +18 -0
  19. package/lib/ContourCreator.js +23 -0
  20. package/lib/Fill.d.ts +18 -7
  21. package/lib/Fill.js +73 -41
  22. package/lib/Grid.d.ts +167 -0
  23. package/lib/Grid.js +339 -0
  24. package/lib/Hodographs.d.ts +13 -6
  25. package/lib/Hodographs.js +58 -71
  26. package/lib/Map.d.ts +14 -3
  27. package/lib/Map.js +9 -0
  28. package/lib/Paintball.d.ts +11 -5
  29. package/lib/Paintball.js +35 -15
  30. package/lib/PlotComponent.d.ts +5 -5
  31. package/lib/PlotLayer.d.ts +12 -12
  32. package/lib/PlotLayer.js +16 -14
  33. package/lib/PlotLayer.worker.d.ts +2 -2
  34. package/lib/PlotLayer.worker.js +105 -66
  35. package/lib/PolylineCollection.d.ts +20 -9
  36. package/lib/PolylineCollection.js +158 -32
  37. package/lib/RawField.d.ts +11 -167
  38. package/lib/RawField.js +37 -383
  39. package/lib/TextCollection.d.ts +31 -0
  40. package/lib/TextCollection.js +295 -0
  41. package/lib/cpp/marchingsquares.d.ts +6 -0
  42. package/lib/cpp/marchingsquares.js +3449 -0
  43. package/lib/cpp/marchingsquares.wasm +0 -0
  44. package/lib/cpp/marchingsquares_embind.d.ts +6 -0
  45. package/lib/index.d.ts +13 -6
  46. package/lib/index.js +12 -3
  47. package/lib/utils.d.ts +5 -3
  48. package/lib/utils.js +17 -6
  49. package/package.json +14 -9
Binary file
@@ -19,21 +19,22 @@ interface BillboardSpec {
19
19
  BB_MAG_WRAP: number;
20
20
  BB_MAG_MAX: number;
21
21
  }
22
- interface PolylineSpec {
23
- origin: Float32Array;
24
- verts: Float32Array;
22
+ type LineData = {
23
+ vertices: [number, number][];
24
+ offsets?: [number, number][];
25
+ data?: number[];
26
+ zoom?: number;
27
+ };
28
+ type Polyline = {
25
29
  extrusion: Float32Array;
26
- zoom: Float32Array;
27
- texcoords: Float32Array;
28
- }
29
- interface LineSpec {
30
- verts: [number, number][];
31
- origin: [number, number];
32
- zoom: number;
33
- texcoords: [number, number][];
34
- }
30
+ vertices: Float32Array;
31
+ offsets?: Float32Array;
32
+ data?: Float32Array;
33
+ zoom?: Float32Array;
34
+ };
35
35
  type WebGLAnyRenderingContext = WebGLRenderingContext | WebGL2RenderingContext;
36
36
  declare function isWebGL2Ctx(gl: WebGLAnyRenderingContext): gl is WebGL2RenderingContext;
37
37
  type TypedArray = Float16Array | Float32Array;
38
+ type ContourData = Record<number, [number, number][][]>;
38
39
  export { isWebGL2Ctx };
39
- export type { WindProfile, BillboardSpec, PolylineSpec, LineSpec, WebGLAnyRenderingContext, TypedArray };
40
+ export type { WindProfile, BillboardSpec, Polyline, LineData, WebGLAnyRenderingContext, TypedArray, ContourData };
package/lib/Barbs.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { PlotComponent } from "./PlotComponent";
2
2
  import { RawVectorField } from "./RawField";
3
- import { MapType } from "./Map";
3
+ import { MapLikeType } from "./Map";
4
4
  import { TypedArray, WebGLAnyRenderingContext } from "./AutumnTypes";
5
5
  interface BarbsOptions {
6
6
  /**
@@ -23,11 +23,11 @@ interface BarbsOptions {
23
23
  * const vector_field = new RawVectorField(grid, u_data, v_data);
24
24
  * const barbs = new Barbs(vector_field, {color: '#000000', thin_fac: 16});
25
25
  */
26
- declare class Barbs<ArrayType extends TypedArray> extends PlotComponent {
26
+ declare class Barbs<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
27
27
  /** The vector field */
28
- private readonly fields;
29
- readonly color: [number, number, number];
30
- readonly thin_fac: number;
28
+ private fields;
29
+ readonly opts: Required<BarbsOptions>;
30
+ private readonly color_rgb;
31
31
  private gl_elems;
32
32
  /**
33
33
  * Create a field of wind barbs
@@ -35,6 +35,11 @@ declare class Barbs<ArrayType extends TypedArray> extends PlotComponent {
35
35
  * @param opts - Options for creating the wind barbs
36
36
  */
37
37
  constructor(fields: RawVectorField<ArrayType>, opts: BarbsOptions);
38
+ /**
39
+ * Update the field displayed as barbs
40
+ * @param fields - The new field to display as barbs
41
+ */
42
+ updateField(fields: RawVectorField<ArrayType>): Promise<void>;
38
43
  /**
39
44
  * @internal
40
45
  * Add the barb field to a map
package/lib/Barbs.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { PlotComponent } from "./PlotComponent";
2
2
  import { BillboardCollection } from './BillboardCollection';
3
- import { hex2rgba } from './utils';
3
+ import { hex2rgb, normalizeOptions } from './utils';
4
4
  const BARB_DIMS = {
5
5
  BB_WIDTH: 85,
6
6
  BB_HEIGHT: 256,
@@ -97,6 +97,10 @@ function _createBarbTexture() {
97
97
  return canvas;
98
98
  }
99
99
  let BARB_TEXTURE = null;
100
+ const barb_opt_defaults = {
101
+ color: '#000000',
102
+ thin_fac: 1
103
+ };
100
104
  /**
101
105
  * A class representing a field of wind barbs. The barbs are automatically thinned based on the zoom level on the map; the user only has to provide a
102
106
  * thinning factor at zoom level 1.
@@ -114,11 +118,22 @@ class Barbs extends PlotComponent {
114
118
  constructor(fields, opts) {
115
119
  super();
116
120
  this.fields = fields;
117
- const color = hex2rgba(opts.color || '#000000');
118
- this.color = [color[0], color[1], color[2]];
119
- this.thin_fac = opts.thin_fac || 1;
121
+ this.opts = normalizeOptions(opts, barb_opt_defaults);
122
+ const color_rgb = hex2rgb(this.opts.color);
123
+ this.color_rgb = [color_rgb[0], color_rgb[1], color_rgb[2]];
120
124
  this.gl_elems = null;
121
125
  }
126
+ /**
127
+ * Update the field displayed as barbs
128
+ * @param fields - The new field to display as barbs
129
+ */
130
+ async updateField(fields) {
131
+ this.fields = fields;
132
+ if (this.gl_elems === null)
133
+ return;
134
+ this.gl_elems.barb_billboards.updateField(fields);
135
+ this.gl_elems.map.triggerRepaint();
136
+ }
122
137
  /**
123
138
  * @internal
124
139
  * Add the barb field to a map
@@ -131,10 +146,12 @@ class Barbs extends PlotComponent {
131
146
  BARB_TEXTURE = _createBarbTexture();
132
147
  }
133
148
  const barb_image = { format: gl.RGBA, type: gl.UNSIGNED_BYTE, image: BARB_TEXTURE, mag_filter: gl.NEAREST };
134
- const barb_billboards = new BillboardCollection(gl, this.fields, this.thin_fac, map_max_zoom, barb_image, BARB_DIMS, this.color, 0.1);
149
+ const barb_billboards = new BillboardCollection(this.fields, this.opts.thin_fac, map_max_zoom, barb_image, BARB_DIMS, this.color_rgb, 0.1);
150
+ await barb_billboards.setup(gl);
135
151
  this.gl_elems = {
136
152
  map: map, barb_billboards: barb_billboards
137
153
  };
154
+ this.updateField(this.fields);
138
155
  }
139
156
  /**
140
157
  * @internal
@@ -2,16 +2,20 @@ import { BillboardSpec, TypedArray, WebGLAnyRenderingContext } from "./AutumnTyp
2
2
  import { RawVectorField } from "./RawField";
3
3
  import { WGLTextureSpec } from "autumn-wgl";
4
4
  declare class BillboardCollection<ArrayType extends TypedArray> {
5
+ private field;
5
6
  readonly spec: BillboardSpec;
6
7
  readonly color: [number, number, number];
7
8
  readonly size_multiplier: number;
8
- private readonly program;
9
- private vertices;
10
- private texcoords;
11
- private readonly texture;
12
- private readonly u_texture;
13
- private readonly v_texture;
14
- constructor(gl: WebGLAnyRenderingContext, field: RawVectorField<ArrayType>, thin_fac: number, max_zoom: number, billboard_image: WGLTextureSpec, billboard_spec: BillboardSpec, billboard_color: [number, number, number], billboard_size_mult: number);
9
+ readonly thin_fac: number;
10
+ readonly max_zoom: number;
11
+ readonly billboard_image: WGLTextureSpec;
12
+ private gl_elems;
13
+ private wind_textures;
14
+ private readonly trim_inaccessible;
15
+ private show_field;
16
+ constructor(field: RawVectorField<ArrayType>, thin_fac: number, max_zoom: number, billboard_image: WGLTextureSpec, billboard_spec: BillboardSpec, billboard_color: [number, number, number], billboard_size_mult: number);
17
+ updateField(field: RawVectorField<ArrayType>): void;
18
+ setup(gl: WebGLAnyRenderingContext): Promise<void>;
15
19
  render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array, [map_width, map_height]: [number, number], map_zoom: number, map_bearing: number, map_pitch: number): void;
16
20
  }
17
21
  export { BillboardCollection };
@@ -17,6 +17,7 @@ uniform highp float u_bb_mag_wrap;
17
17
 
18
18
  uniform sampler2D u_u_sampler;
19
19
  uniform sampler2D u_v_sampler;
20
+ uniform sampler2D u_rot_sampler;
20
21
 
21
22
  varying highp vec2 v_tex_coord;
22
23
 
@@ -58,6 +59,7 @@ void main() {
58
59
 
59
60
  highp float u = texture2D(u_u_sampler, a_tex_coord).r;
60
61
  highp float v = texture2D(u_v_sampler, a_tex_coord).r;
62
+ highp float rot = texture2D(u_rot_sampler, a_tex_coord).r;
61
63
 
62
64
  lowp float bb_aspect = u_bb_width / u_bb_height;
63
65
  lowp float ang = (abs(u) < 1e-6 && abs(v) < 1e-6) ? 0. : atan(v, u) - 3.141592654 / 2.0;
@@ -88,7 +90,7 @@ void main() {
88
90
  texcoord = tex_loc + vec2(u_bb_width, u_bb_height);
89
91
  }
90
92
 
91
- mat4 barb_rotation = rotationZMatrix(ang + radians(u_map_bearing));
93
+ mat4 barb_rotation = rotationZMatrix(ang + radians(u_map_bearing) - rot);
92
94
  mat4 map_stretch_matrix = scalingMatrix(1.0, 1. / u_map_aspect, 1.0);
93
95
  offset = map_stretch_matrix * barb_rotation * offset;
94
96
  }
@@ -105,58 +107,78 @@ void main() {
105
107
  lowp vec4 tex_color = texture2D(u_sampler, v_tex_coord);
106
108
  gl_FragColor = vec4(u_bb_color, tex_color.a);
107
109
  }`
110
+ class BillboardCollectionGLElems {
111
+ }
108
112
  class BillboardCollection {
109
- constructor(gl, field, thin_fac, max_zoom, billboard_image, billboard_spec, billboard_color, billboard_size_mult) {
113
+ constructor(field, thin_fac, max_zoom, billboard_image, billboard_spec, billboard_color, billboard_size_mult) {
114
+ this.field = field;
110
115
  this.spec = billboard_spec;
111
116
  this.color = billboard_color;
112
117
  this.size_multiplier = billboard_size_mult;
113
- this.program = new WGLProgram(gl, billboard_vertex_shader_src, billboard_fragment_shader_src);
114
- this.vertices = null;
115
- this.texcoords = null;
118
+ this.thin_fac = thin_fac;
119
+ this.max_zoom = max_zoom;
120
+ this.billboard_image = billboard_image;
121
+ this.gl_elems = null;
122
+ this.wind_textures = null;
116
123
  const n_density_tiers = Math.log2(thin_fac);
117
124
  const n_inaccessible_tiers = Math.max(n_density_tiers + 1 - max_zoom, 0);
118
- const trim_inaccessible = Math.pow(2, n_inaccessible_tiers);
119
- const earth_relative = field.getThinnedField(trim_inaccessible, trim_inaccessible).toEarthRelative();
120
- const u_thin = earth_relative.u;
121
- const v_thin = earth_relative.v;
122
- (async () => {
123
- const { vertices, texcoords } = await earth_relative.grid.getWGLBillboardBuffers(gl, thin_fac / trim_inaccessible, max_zoom);
124
- this.vertices = vertices;
125
- this.texcoords = texcoords;
126
- })();
127
- const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, u_thin.isFloat16());
125
+ this.trim_inaccessible = Math.pow(2, n_inaccessible_tiers);
126
+ this.show_field = true;
127
+ }
128
+ updateField(field) {
129
+ this.field = field;
130
+ if (this.gl_elems === null)
131
+ return;
132
+ const gl = this.gl_elems.gl;
133
+ const data = this.field.getThinnedField(this.trim_inaccessible, this.trim_inaccessible);
134
+ const { u: u_thin, v: v_thin } = data.getTextureData();
135
+ this.show_field = u_thin !== null;
136
+ const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, !(u_thin instanceof Float32Array));
128
137
  const u_image = { 'format': format, 'type': type,
129
- 'width': u_thin.grid.ni, 'height': u_thin.grid.nj, 'image': u_thin.getTextureData(),
138
+ 'width': data.grid.ni, 'height': data.grid.nj, 'image': u_thin,
130
139
  'mag_filter': gl.NEAREST, 'row_alignment': row_alignment,
131
140
  };
132
141
  const v_image = { 'format': format, 'type': type,
133
- 'width': v_thin.grid.ni, 'height': v_thin.grid.nj, 'image': v_thin.getTextureData(),
142
+ 'width': data.grid.ni, 'height': data.grid.nj, 'image': v_thin,
134
143
  'mag_filter': gl.NEAREST, 'row_alignment': row_alignment,
135
144
  };
136
- this.texture = new WGLTexture(gl, billboard_image);
137
- this.u_texture = new WGLTexture(gl, u_image);
138
- this.v_texture = new WGLTexture(gl, v_image);
145
+ if (this.wind_textures === null) {
146
+ this.wind_textures = { u: new WGLTexture(gl, u_image), v: new WGLTexture(gl, v_image) };
147
+ }
148
+ else {
149
+ this.wind_textures.u.setImageData(u_image);
150
+ this.wind_textures.v.setImageData(v_image);
151
+ }
152
+ }
153
+ async setup(gl) {
154
+ const program = new WGLProgram(gl, billboard_vertex_shader_src, billboard_fragment_shader_src);
155
+ const thinned_field = this.field.getThinnedField(this.trim_inaccessible, this.trim_inaccessible);
156
+ const { vertices, texcoords } = await thinned_field.grid.getWGLBillboardBuffers(gl, this.thin_fac / this.trim_inaccessible, this.max_zoom);
157
+ const { rotation: proj_rotation_tex } = thinned_field.grid.getVectorRotationTexture(gl);
158
+ const texture = new WGLTexture(gl, this.billboard_image);
159
+ this.gl_elems = { gl: gl, program: program, vertices: vertices, texcoords: texcoords, texture: texture, proj_rot_texture: proj_rotation_tex };
139
160
  }
140
161
  render(gl, matrix, [map_width, map_height], map_zoom, map_bearing, map_pitch) {
141
- if (this.vertices === null || this.texcoords === null)
162
+ if (this.gl_elems === null || this.wind_textures === null || !this.show_field)
142
163
  return;
143
164
  if (matrix instanceof Float32Array)
144
165
  matrix = [...matrix];
166
+ const gl_elems = this.gl_elems;
145
167
  const bb_size = this.spec.BB_HEIGHT * (map_height / map_width) * this.size_multiplier;
146
168
  const bb_width = this.spec.BB_WIDTH / this.spec.BB_TEX_WIDTH;
147
169
  const bb_height = this.spec.BB_HEIGHT / this.spec.BB_TEX_HEIGHT;
148
- this.program.use({ 'a_pos': this.vertices, 'a_tex_coord': this.texcoords }, { 'u_bb_size': bb_size, 'u_bb_width': bb_width, 'u_bb_height': bb_height,
170
+ gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_bb_size': bb_size, 'u_bb_width': bb_width, 'u_bb_height': bb_height,
149
171
  'u_bb_mag_bin_size': this.spec.BB_MAG_BIN_SIZE, 'u_bb_mag_wrap': this.spec.BB_MAG_WRAP, 'u_offset': 0,
150
- 'u_bb_color': this.color, 'u_matrix': matrix, 'u_map_aspect': map_height / map_width, 'u_zoom': map_zoom, 'u_map_bearing': map_bearing }, { 'u_sampler': this.texture, 'u_u_sampler': this.u_texture, 'u_v_sampler': this.v_texture });
172
+ 'u_bb_color': this.color, 'u_matrix': matrix, 'u_map_aspect': map_height / map_width, 'u_zoom': map_zoom, 'u_map_bearing': map_bearing }, { 'u_sampler': gl_elems.texture, 'u_u_sampler': this.wind_textures.u, 'u_v_sampler': this.wind_textures.v, 'u_rot_sampler': gl_elems.proj_rot_texture });
151
173
  gl.enable(gl.BLEND);
152
174
  gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
153
- this.program.draw();
154
- this.program.setUniforms({ 'u_offset': -2 });
155
- this.program.draw();
156
- this.program.setUniforms({ 'u_offset': -1 });
157
- this.program.draw();
158
- this.program.setUniforms({ 'u_offset': 1 });
159
- this.program.draw();
175
+ gl_elems.program.draw();
176
+ gl_elems.program.setUniforms({ 'u_offset': -2 });
177
+ gl_elems.program.draw();
178
+ gl_elems.program.setUniforms({ 'u_offset': -1 });
179
+ gl_elems.program.draw();
180
+ gl_elems.program.setUniforms({ 'u_offset': 1 });
181
+ gl_elems.program.draw();
160
182
  }
161
183
  }
162
184
  export { BillboardCollection };
package/lib/ColorBar.js CHANGED
@@ -40,12 +40,13 @@ function makeColorBar(colormap, opts) {
40
40
  };
41
41
  const chars_left = getNChar(ticks[0]);
42
42
  const chars_right = getNChar(ticks[ticks.length - 1]);
43
+ const need_overflow = colormap.underflow_color !== null || colormap.overflow_color !== null;
43
44
  const bar_long_size = 600;
44
45
  const bar_cross_size = bar_long_size / 9;
45
- const bar_long_pad = orientation == 'horizontal' ? Math.max(chars_left, chars_right) * 6 : 8;
46
+ const bar_long_pad = Math.max(orientation == 'horizontal' ? Math.max(chars_left, chars_right) * 6 : 8, need_overflow ? bar_cross_size / (2 * Math.sqrt(3)) : 0);
46
47
  const bar_cross_pad = 3;
47
48
  const bar_thickness = 10;
48
- let height, width, bar_left, bar_top, bar_width, bar_height;
49
+ let height, width, bar_left, bar_top, bar_width, bar_height, bar_right, bar_bottom, bar_middle, bar_low_arrow, bar_high_arrow;
49
50
  if (orientation == 'vertical') {
50
51
  height = bar_long_size;
51
52
  width = bar_cross_size;
@@ -53,6 +54,11 @@ function makeColorBar(colormap, opts) {
53
54
  bar_top = bar_long_pad;
54
55
  bar_width = bar_thickness;
55
56
  bar_height = bar_long_size - 2 * bar_long_pad;
57
+ bar_right = bar_left + bar_width;
58
+ bar_middle = bar_left + bar_width / 2;
59
+ bar_bottom = bar_top + bar_height;
60
+ bar_low_arrow = colormap.underflow_color === null ? bar_bottom : bar_bottom + bar_cross_size / (Math.sqrt(3) * 2);
61
+ bar_high_arrow = colormap.overflow_color === null ? bar_top : bar_top - bar_cross_size / (Math.sqrt(3) * 2);
56
62
  }
57
63
  else {
58
64
  width = bar_long_size;
@@ -61,6 +67,11 @@ function makeColorBar(colormap, opts) {
61
67
  bar_top = tick_dir == 'bottom' ? bar_cross_pad : bar_cross_size - 6 - bar_cross_pad - bar_thickness;
62
68
  bar_height = bar_thickness;
63
69
  bar_width = bar_long_size - 2 * bar_long_pad;
70
+ bar_right = bar_left + bar_width;
71
+ bar_middle = bar_top + bar_height / 2;
72
+ bar_bottom = bar_top + bar_height;
73
+ bar_low_arrow = colormap.underflow_color === null ? bar_left : bar_left - bar_cross_size / (Math.sqrt(3) * 2);
74
+ bar_high_arrow = colormap.overflow_color === null ? bar_right : bar_right + bar_cross_size / (Math.sqrt(3) * 2);
64
75
  }
65
76
  const n_colors = colormap.colors.length;
66
77
  const root = createElement('svg', { width: width, height: height });
@@ -75,6 +86,7 @@ function makeColorBar(colormap, opts) {
75
86
  { 'text-anchor': 'middle', transform: `translate(${bar_left}, ${bar_top})` };
76
87
  }
77
88
  const gticks = createElement('g', gtickattrs, root);
89
+ // Make the colored background
78
90
  colormap.colors.forEach((color, icolor) => {
79
91
  let attrs = {};
80
92
  if (orientation == 'vertical') {
@@ -87,6 +99,30 @@ function makeColorBar(colormap, opts) {
87
99
  }
88
100
  createElement('rect', { ...attrs, fill: color.color, opacity: color.opacity }, gbar);
89
101
  });
102
+ // Make the overflow and underflow triangles
103
+ if (colormap.underflow_color !== null) {
104
+ let point_list;
105
+ if (orientation == 'vertical') {
106
+ point_list = `${bar_right} ${bar_bottom}, ${bar_middle} ${bar_low_arrow}, ${bar_left} ${bar_bottom}, ${bar_right} ${bar_bottom}`;
107
+ }
108
+ else {
109
+ point_list = `${bar_left} ${bar_bottom}, ${bar_low_arrow} ${bar_middle}, ${bar_left} ${bar_top}, ${bar_left} ${bar_bottom}`;
110
+ }
111
+ const underflow_attrs = { points: point_list, fill: colormap.underflow_color.color, opacity: colormap.underflow_color.opacity };
112
+ createElement('polygon', underflow_attrs, gbar);
113
+ }
114
+ if (colormap.overflow_color !== null) {
115
+ let point_list;
116
+ if (orientation == 'vertical') {
117
+ point_list = `${bar_left} ${bar_top}, ${bar_middle} ${bar_high_arrow}, ${bar_right} ${bar_top}, ${bar_left} ${bar_top}`;
118
+ }
119
+ else {
120
+ point_list = `${bar_right} ${bar_top}, ${bar_high_arrow} ${bar_middle}, ${bar_right} ${bar_bottom}, ${bar_right} ${bar_top}`;
121
+ }
122
+ const overflow_attrs = { points: point_list, fill: colormap.overflow_color.color, opacity: colormap.overflow_color.opacity };
123
+ createElement('polygon', overflow_attrs, gbar);
124
+ }
125
+ // Make the ticks marks and labels
90
126
  const first_level = colormap.levels[0];
91
127
  const last_level = colormap.levels[colormap.levels.length - 1];
92
128
  ticks.filter(level => first_level <= level && level <= last_level).forEach(level => {
@@ -116,16 +152,24 @@ function makeColorBar(colormap, opts) {
116
152
  const text = createElement('text', { ...textattrs, fill: '#000000', style: `font-family: ${fontface}; font-size: ${tickfontsize}pt` }, gtick);
117
153
  text.textContent = level.toString();
118
154
  });
155
+ // Draw the outline
156
+ let point_list;
157
+ if (orientation == 'vertical') {
158
+ point_list = `${bar_left} ${bar_top}, ${bar_middle} ${bar_high_arrow}, ${bar_right} ${bar_top}, ${bar_right} ${bar_bottom}, ` +
159
+ `${bar_middle} ${bar_low_arrow}, ${bar_left} ${bar_bottom}, ${bar_left} ${bar_top}`;
160
+ }
161
+ else {
162
+ point_list = `${bar_left} ${bar_top}, ${bar_right} ${bar_top}, ${bar_high_arrow} ${bar_middle}, ${bar_right} ${bar_bottom}, ` +
163
+ `${bar_left} ${bar_bottom}, ${bar_low_arrow} ${bar_middle}, ${bar_left} ${bar_top}`;
164
+ }
119
165
  const outline_attrs = {
120
- x: bar_left,
121
- y: bar_top,
122
- width: bar_width,
123
- height: bar_height,
166
+ points: point_list,
124
167
  stroke: '#000000',
125
168
  'stroke-width': 1.5,
126
169
  fill: 'none'
127
170
  };
128
- createElement('rect', outline_attrs, root);
171
+ createElement('polygon', outline_attrs, root);
172
+ // Draw the colorbar label
129
173
  let labelattrs;
130
174
  if (orientation == 'vertical') {
131
175
  labelattrs = tick_dir == 'left' ? { transform: `translate(15, ${height / 2}) rotate(-90)` } : { transform: `translate(${width - 6}, ${height / 2}) rotate(-90)` };
package/lib/Colormap.d.ts CHANGED
@@ -1,19 +1,29 @@
1
+ import { Float16Array } from "@petamoriken/float16";
1
2
  interface Color {
2
3
  /** The color as a hex color string */
3
4
  color: string;
4
5
  /** The opacity as a number from 0 to 1 */
5
6
  opacity: number;
6
7
  }
8
+ interface ColorMapOptions {
9
+ /** The color to use for areas where the value is below the lowest value in the color map */
10
+ overflow_color?: Color | string;
11
+ /** The color to use for areas where the value is above the highest value in the color map */
12
+ underflow_color?: Color | string;
13
+ }
7
14
  /** A mapping from values to colors */
8
15
  declare class ColorMap {
9
16
  readonly levels: number[];
10
17
  readonly colors: Color[];
18
+ readonly overflow_color: Color | null;
19
+ readonly underflow_color: Color | null;
11
20
  /**
12
21
  * Create a color map
13
22
  * @param levels - The list of levels. The number of levels should always be one more than the number of colors.
14
23
  * @param colors - A list of colors
24
+ * @param opts - Options for the color map
15
25
  */
16
- constructor(levels: number[], colors: Color[] | string[]);
26
+ constructor(levels: number[], colors: Color[] | string[], opts?: ColorMapOptions);
17
27
  /**
18
28
  * @returns an array of hex color strings
19
29
  */
@@ -67,5 +77,6 @@ declare const bluered: (level_min: number, level_max: number, n_colors: number)
67
77
  * @returns A canvas element containing each color of the color map
68
78
  */
69
79
  declare function makeTextureImage(colormap: ColorMap): HTMLCanvasElement;
70
- export { ColorMap, bluered, redblue, pw_speed500mb, pw_speed850mb, pw_cape, pw_t2m, pw_td2m, nws_storm_clear_refl, makeTextureImage };
71
- export type { Color };
80
+ declare function makeIndexMap(colormap: ColorMap): Float16Array;
81
+ export { ColorMap, bluered, redblue, pw_speed500mb, pw_speed850mb, pw_cape, pw_t2m, pw_td2m, nws_storm_clear_refl, makeTextureImage, makeIndexMap };
82
+ export type { Color, ColorMapOptions };
package/lib/Colormap.js CHANGED
@@ -5,6 +5,7 @@ const cape_colormap_data = { "levels": [0, 100, 200, 300, 400, 500, 600, 700
5
5
  const t2m_colormap_data = { "levels": [-60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120], "colors": [ "#235877", "#2a5f7c", "#316782", "#396e87", "#40768c", "#477d91", "#4e8497", "#558c9c", "#5d93a1", "#649ba6", "#6ba2ac", "#72a9b1", "#79b1b6", "#81b8bb", "#88c0c1", "#8fc7c6", "#96cecb", "#9dd6d0", "#a5ddd6", "#ace5db", "#b3ece0", "#b3ece0", "#b1e7df", "#b0e1dd", "#aedcdc", "#add7da", "#abd2d9", "#aaccd8", "#a8c7d6", "#a7c2d5", "#a5bcd4", "#a4b7d2", "#a2b2d1", "#9fa7ce", "#9ea2cd", "#9c9dcb", "#9c9dcb", "#9b97ca", "#9992c8", "#988dc7", "#9688c6", "#9582c4", "#9278c2", "#9073c0", "#8f6dbf", "#8d68bd", "#8c63bc", "#8a5dbb", "#8958b9", "#8753b8", "#864eb6", "#8448b5", "#8343b4", "#813eb2", "#8038b1", "#7e33b0", "#7d33ae", "#7b29ad", "#7a23ab", "#781eaa", "#a037af", "#a443b3", "#a74fb7", "#ab5cbb", "#af68bf", "#b374c3", "#b680c7", "#ba8dcc", "#be99d0", "#c1a5d4", "#c5b1d8", "#c9bddc", "#cdcae0", "#d0d6e4", "#d4e2e8", "#deecf2", "#d1e2ee", "#c5d9ea", "#b8cfe6", "#acc5e3", "#9fbbdf", "#92b2db", "#86a8d7", "#799ed3", "#6c94cf", "#608bcb", "#5381c7", "#4777c4", "#3a6dc0", "#2d64bc", "#215ab8", "#1450b4", "#0f4455", "#1c4e5a", "#2a585f", "#376363", "#456d68", "#52776d", "#5f8172", "#6d8c77", "#7a967c", "#88a080", "#95aa85", "#a3b58a", "#b0bf8f", "#bdc994", "#cbd399", "#d8de9d", "#e6e8a2", "#f3f2a7", "#f8eea2", "#f0e199", "#e8d591", "#e1c888", "#d9bc80", "#d1af77", "#c9a36f", "#c19666", "#ba8a5e", "#b27d55", "#aa714d", "#a26444", "#9b583c", "#8b3f2b", "#833222", "#7b261a", "#7b261a", "#741911", "#6c0d09", "#640000", "#5f0000", "#630507", "#670a0e", "#6c0f15", "#70141c", "#741824", "#781d2b", "#7d2232", "#812739", "#852c40", "#73372d", "#7a4036", "#80493f", "#875349", "#8e5c52", "#94655b", "#9b6e64", "#a88177", "#af8a80", "#b69389", "#bd9c92", "#c3a69c", "#caafa5", "#d1b8ae", "#d7c1b7", "#decac0", "#e5d4ca", "#ebddd3", "#f2e6dc", "#e8dfd6", "#e0d7cf", "#d8d0c8", "#d0c8c0", "#c8c0b9", "#c0b9b2", "#b7b1ab", "#afa9a4", "#a7a29c", "#9f9a95", "#97938e", "#8f8b87", "#878380", "#7f7c78", "#777471", "#6f6c6a", "#666563", "#5e5d5c", "#565554", "#4e4e4d", "#464646" ] }
6
6
  const td2m_colormap_data = { "levels": [-40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90], "colors": [ "#986d4d", "#966c4c", "#946b4c", "#926a4b", "#90694b", "#8e684a", "#8c664a", "#8a6549", "#886448", "#866348", "#846247", "#826147", "#806046", "#7e5f46", "#7c5e45", "#7a5d44", "#785b44", "#765a43", "#745943", "#725842", "#715742", "#6f5641", "#6d5540", "#6b5440", "#69533f", "#67523f", "#65503e", "#634f3d", "#614e3d", "#5f4d3c", "#5d4c3c", "#5b4b3b", "#594a3b", "#57493a", "#554839", "#534739", "#514538", "#4f4438", "#4d4337", "#4b4237", "#494136", "#4d4334", "#514738", "#564c3c", "#5a5041", "#5e5545", "#625949", "#675e4d", "#6b6251", "#6f6755", "#746b5a", "#78705e", "#7c7462", "#807966", "#857d6a", "#89826f", "#8d8673", "#928b77", "#968f7b", "#9a947f", "#9e9883", "#a39d88", "#a7a18c", "#aba690", "#afaa94", "#b8b39c", "#b8b39c", "#bcb8a1", "#c1bca5", "#c9c5ad", "#c9cab1", "#d2ceb6", "#d2ceb6", "#d6d3ba", "#dfdcc2", "#e3e0c6", "#e7e5ca", "#ebe9cf", "#f0eed3", "#f4f2d7", "#e6f5e6", "#d7f0d7", "#c8eac8", "#b9e5b9", "#aadfaa", "#9bda9b", "#8cd48c", "#7dcf7d", "#6ec96e", "#5fc45f", "#30ae30", "#2ca32c", "#279927", "#238e23", "#1e831e", "#1a791a", "#156e15", "#116311", "#0c590c", "#084e08", "#61a3af", "#5896a0", "#508992", "#477b83", "#3e6e74", "#366166", "#2d5457", "#244648", "#1c393a", "#132c2b", "#66669a", "#605e94", "#59568e", "#534e88", "#4d4682", "#463e7c", "#403676", "#3a2e70", "#33266a", "#2d1e64", "#724071", "#784573", "#7d4b75", "#835076", "#885678", "#8e5b7a", "#93617c", "#99667d", "#9e6c7f", "#a47181" ] }
7
7
  const nws_storm_clear_refl_colormap_data = { "levels": [ -15, -14.53608247, -14.07216495, -13.60824742, -13.1443299, -12.68041237, -12.21649485, -11.75257732, -11.28865979, -10.82474227, -10.36082474, -9.89690722, -9.43298969, -8.96907216, -8.50515464, -8.04123711, -7.57731959, -7.11340206, -6.64948454, -6.18556701, -5.72164948, -5.25773196, -4.79381443, -4.32989691, -3.86597938, -3.40206186, -2.93814433, -2.4742268, -2.01030928, -1.54639175, -1.08247423, -0.6185567, -0.15463918, 0.30927835, 0.77319588, 1.2371134, 1.70103093, 2.16494845, 2.62886598, 3.09278351, 3.55670103, 4.02061856, 4.48453608, 4.94845361, 5.41237113, 5.87628866, 6.34020619, 6.80412371, 7.26804124, 7.73195876, 8.19587629, 8.65979381, 9.12371134, 9.58762887, 10.05154639, 10.51546392, 10.97938144, 11.44329897, 11.90721649, 12.37113402, 12.83505155, 13.29896907, 13.7628866, 14.22680412, 14.69072165, 15.15463918, 15.6185567, 16.08247423, 16.54639175, 17.01030928, 17.4742268, 17.93814433, 18.40206186, 18.86597938, 19.32989691, 19.79381443, 20.25773196, 20.72164948, 21.18556701, 21.64948454, 22.11340206, 22.57731959, 23.04123711, 23.50515464, 23.96907216, 24.43298969, 24.89690722, 25.36082474, 25.82474227, 26.28865979, 26.75257732, 27.21649485, 27.68041237, 28.1443299, 28.60824742, 29.07216495, 29.53608247, 30, 30.46391753, 30.92783505, 31.39175258, 31.8556701, 32.31958763, 32.78350515, 33.24742268, 33.71134021, 34.17525773, 34.63917526, 35.10309278, 35.56701031, 36.03092784, 36.49484536, 36.95876289, 37.42268041, 37.88659794, 38.35051546, 38.81443299, 39.27835052, 39.74226804, 40.20618557, 40.67010309, 41.13402062, 41.59793814, 42.06185567, 42.5257732, 42.98969072, 43.45360825, 43.91752577, 44.3814433, 44.84536082, 45.30927835, 45.77319588, 46.2371134, 46.70103093, 47.16494845, 47.62886598, 48.09278351, 48.55670103, 49.02061856, 49.48453608, 49.94845361, 50.41237113, 50.87628866, 51.34020619, 51.80412371, 52.26804124, 52.73195876, 53.19587629, 53.65979381, 54.12371134, 54.58762887, 55.05154639, 55.51546392, 55.97938144, 56.44329897, 56.90721649, 57.37113402, 57.83505155, 58.29896907, 58.7628866, 59.22680412, 59.69072165, 60.15463918, 60.6185567, 61.08247423, 61.54639175, 62.01030928, 62.4742268, 62.93814433, 63.40206186, 63.86597938, 64.32989691, 64.79381443, 65.25773196, 65.72164948, 66.18556701, 66.64948454, 67.11340206, 67.57731959, 68.04123711, 68.50515464, 68.96907216, 69.43298969, 69.89690722, 70.36082474, 70.82474227, 71.28865979, 71.75257732, 72.21649485, 72.68041237, 73.1443299, 73.60824742, 74.07216495, 74.53608247, 75 ], "colors": [ "#959052", "#979356", "#9c9a60", "#a09c64", "#a3a067", "#a8a570", "#aaa875", "#acac79", "#b1b182", "#b5b587", "#b6b88c", "#bcbd93", "#bfc199", "#c1c39c", "#c1c39c", "#c6caa5", "#cacdaa", "#cccfaf", "#cfd1b3", "#cccfb3", "#c8ccb3", "#c6c8b3", "#bfc3b3", "#bfc3b3", "#bcc1b3", "#b8bdb3", "#b5bab3", "#afb5b3", "#acb3b3", "#aaafb3", "#a7aeb3", "#a3aab3", "#a0a8b3", "#9ca5b3", "#9aa1b3", "#97a0b3", "#939cb3", "#909ab3", "#939ab5", "#8c95b3", "#8791b1", "#838eb1", "#808caf", "#7c89af", "#7785ae", "#7482ac", "#7080aa", "#6b7caa", "#6275a8", "#5e72a7", "#5e72a7", "#5b70a5", "#566da3", "#5269a3", "#4f67a1", "#4b64a1", "#4660a0", "#425d9e", "#4260a1", "#4467a5", "#486eaa", "#4975ae", "#4d7cb1", "#4f83b5", "#508aba", "#5491bf", "#5699c3", "#599ec6", "#5ba5ca", "#5daccf", "#60b3d4", "#62bad8", "#64c1db", "#67c8df", "#69cfe4", "#6ed6e8", "#67d6d6", "#60d6c4", "#59d6b3", "#52d6a1", "#4bd690", "#42d67e", "#3bd66d", "#34d65b", "#11d418", "#11d116", "#0fcd16", "#0fc816", "#0fc316", "#0fbf15", "#0fbc15", "#0fb613", "#0eb313", "#0eaf13", "#0eaa13", "#0ca511", "#0ca111", "#0c9e11", "#0c9911", "#0c950f", "#0c900f", "#0a8c0f", "#0a870f", "#0a830e", "#0a800e", "#0a7c0c", "#0a770c", "#08720c", "#086e0c", "#086b0a", "#08660a", "#08620a", "#085d08", "#1c6708", "#317208", "#467c08", "#5b8707", "#6e9107", "#839c05", "#97a805", "#acb105", "#c1bc05", "#d6c603", "#e9d103", "#ffe200", "#ffd800", "#ffd300", "#ffc800", "#ffc300", "#ffba00", "#ffb500", "#ffb000", "#ffac00", "#ffa700", "#ff9e00", "#ff9900", "#ff9300", "#ff8900", "#ff8500", "#ff7f00", "#ff0000", "#f70000", "#f00000", "#e90000", "#e20000", "#db0000", "#d40000", "#cd0000", "#c60000", "#bf0000", "#b80000", "#b10000", "#aa0000", "#a30000", "#9a0000", "#930000", "#8c0000", "#850000", "#7e0000", "#770000", "#700000", "#ffffff", "#fff4ff", "#ffe9ff", "#ffdfff", "#ffd4ff", "#ffc8ff", "#ffbdff", "#ffb3ff", "#ffa8ff", "#ff9cff", "#ff91ff", "#ff75ff", "#fb6bfd", "#f960f9", "#f656f6", "#f24bf4", "#ef3ff0", "#ed36ef", "#e92aeb", "#e61fe8", "#e416e6", "#e10ae2", "#b100ff", "#ac00fb", "#a300f6", "#9a00f4", "#9300ef", "#8700e9", "#8200e8", "#7900e2", "#7200dd", "#6900db", "#6200d6" ] }
8
+ import { Float16Array } from "@petamoriken/float16";
8
9
  function isColor(obj) {
9
10
  return (typeof obj == 'object') && 'color' in obj && 'opacity' in obj;
10
11
  }
@@ -14,13 +15,18 @@ class ColorMap {
14
15
  * Create a color map
15
16
  * @param levels - The list of levels. The number of levels should always be one more than the number of colors.
16
17
  * @param colors - A list of colors
18
+ * @param opts - Options for the color map
17
19
  */
18
- constructor(levels, colors) {
20
+ constructor(levels, colors, opts) {
19
21
  if (levels.length != colors.length + 1) {
20
22
  throw `Mismatch between number of levels (${levels.length}) and number of colors (${colors.length}; expected ${levels.length - 1})`;
21
23
  }
24
+ const normalizeColor = (c) => isColor(c) ? c : { 'color': c, 'opacity': 1. };
22
25
  this.levels = levels;
23
- this.colors = colors.map(c => isColor(c) ? c : { 'color': c, 'opacity': 1. });
26
+ this.colors = colors.map(c => normalizeColor(c));
27
+ opts = opts === undefined ? {} : opts;
28
+ this.overflow_color = opts.overflow_color === undefined ? null : normalizeColor(opts.overflow_color);
29
+ this.underflow_color = opts.underflow_color === undefined ? null : normalizeColor(opts.underflow_color);
24
30
  }
25
31
  /**
26
32
  * @returns an array of hex color strings
@@ -42,19 +48,33 @@ class ColorMap {
42
48
  withOpacity(func) {
43
49
  const new_colors = [];
44
50
  const new_levels = [];
51
+ const opts = {};
45
52
  for (let ic = 0; ic < this.colors.length; ic++) {
46
53
  const color = this.colors[ic];
47
54
  const level_lower = this.levels[ic];
48
55
  const level_upper = this.levels[ic + 1];
49
- const new_color = { 'color': color['color'], 'opacity': func(level_lower, level_upper) };
50
- if (new_color['opacity'] > 0) {
56
+ const new_opacity = func(level_lower, level_upper);
57
+ const new_color = { color: color.color, opacity: new_opacity };
58
+ if (new_opacity > 0) {
51
59
  if (new_levels[new_levels.length - 1] != level_lower)
52
60
  new_levels.push(level_lower);
53
61
  new_levels.push(level_upper);
54
62
  new_colors.push(new_color);
55
63
  }
56
64
  }
57
- return new ColorMap(new_levels, new_colors);
65
+ if (this.underflow_color !== null) {
66
+ const underflow_opacity = func(this.levels[0], this.levels[0]);
67
+ if (underflow_opacity > 0) {
68
+ opts.underflow_color = { color: this.underflow_color.color, opacity: underflow_opacity };
69
+ }
70
+ }
71
+ if (this.overflow_color !== null) {
72
+ const overflow_opacity = func(this.levels[this.levels.length - 1], this.levels[this.levels.length - 1]);
73
+ if (overflow_opacity > 0) {
74
+ opts.overflow_color = { color: this.overflow_color.color, opacity: overflow_opacity };
75
+ }
76
+ }
77
+ return new ColorMap(new_levels, new_colors, opts);
58
78
  }
59
79
  /**
60
80
  * Create a diverging color map using two input colors
@@ -103,13 +123,24 @@ class ColorMap {
103
123
  return new ColorMap(levels, stops);
104
124
  }
105
125
  }
126
+ function buildColormap(cm_data, overflow) {
127
+ const n_colors = cm_data.colors.length;
128
+ const opts = {};
129
+ if (overflow == 'over' || overflow == 'both') {
130
+ opts.overflow_color = cm_data.colors[n_colors - 1];
131
+ }
132
+ if (overflow == 'under' || overflow == 'both') {
133
+ opts.underflow_color = cm_data.colors[0];
134
+ }
135
+ return new ColorMap(cm_data.levels, cm_data.colors, opts);
136
+ }
106
137
  // Some built-in colormaps
107
- const pw_speed500mb = new ColorMap(spd500_colormap_data.levels, spd500_colormap_data.colors).withOpacity((levl, levu) => Math.min((levu - 20) / 10, 1.));
108
- const pw_speed850mb = new ColorMap(spd850_colormap_data.levels, spd850_colormap_data.colors).withOpacity((levl, levu) => Math.min((levu - 20) / 10, 1.));
109
- const pw_cape = new ColorMap(cape_colormap_data.levels, cape_colormap_data.colors).withOpacity((levl, levu) => Math.min(levu / 1000., 1.));
110
- const pw_t2m = new ColorMap(t2m_colormap_data.levels, t2m_colormap_data.colors);
111
- const pw_td2m = new ColorMap(td2m_colormap_data.levels, td2m_colormap_data.colors);
112
- const nws_storm_clear_refl = new ColorMap(nws_storm_clear_refl_colormap_data.levels, nws_storm_clear_refl_colormap_data.colors);
138
+ const pw_speed500mb = buildColormap(spd500_colormap_data, 'over').withOpacity((levl, levu) => Math.min((levu - 20) / 10, 1.));
139
+ const pw_speed850mb = buildColormap(spd850_colormap_data, 'over').withOpacity((levl, levu) => Math.min((levu - 20) / 10, 1.));
140
+ const pw_cape = buildColormap(cape_colormap_data, 'over').withOpacity((levl, levu) => Math.min(levu / 1000., 1.));
141
+ const pw_t2m = buildColormap(t2m_colormap_data, 'both');
142
+ const pw_td2m = buildColormap(td2m_colormap_data, 'both');
143
+ const nws_storm_clear_refl = buildColormap(nws_storm_clear_refl_colormap_data, 'over');
113
144
  /**
114
145
  * Create a diverging red/blue colormap, where red corresponds to the lowest value and blue corresponds to the highest value
115
146
  * @param level_min - The lowest value in the color map
@@ -149,4 +180,24 @@ function makeTextureImage(colormap) {
149
180
  });
150
181
  return cmap_image;
151
182
  }
152
- export { ColorMap, bluered, redblue, pw_speed500mb, pw_speed850mb, pw_cape, pw_t2m, pw_td2m, nws_storm_clear_refl, makeTextureImage };
183
+ function makeIndexMap(colormap) {
184
+ // Build a texture to account for nonlinear colormaps (basically inverts the relationship between
185
+ // the normalized index and the normalized level)
186
+ const n_nonlin = 101;
187
+ const map_norm = [];
188
+ for (let i = 0; i < n_nonlin; i++) {
189
+ map_norm.push(i / (n_nonlin - 1));
190
+ }
191
+ const levels = colormap.levels;
192
+ const n_lev = levels.length - 1;
193
+ const input_norm = levels.map((lev, ilev) => ilev / n_lev);
194
+ const cmap_norm = levels.map(lev => (lev - levels[0]) / (levels[n_lev] - levels[0]));
195
+ const inv_cmap_norm = map_norm.map(lev => {
196
+ let jlev;
197
+ for (jlev = 0; !(cmap_norm[jlev] <= lev && lev <= cmap_norm[jlev + 1]); jlev++) { }
198
+ const alpha = (lev - cmap_norm[jlev]) / (cmap_norm[jlev + 1] - cmap_norm[jlev]);
199
+ return input_norm[jlev] * (1 - alpha) + input_norm[jlev + 1] * alpha;
200
+ });
201
+ return new Float16Array(inv_cmap_norm);
202
+ }
203
+ export { ColorMap, bluered, redblue, pw_speed500mb, pw_speed850mb, pw_cape, pw_t2m, pw_td2m, nws_storm_clear_refl, makeTextureImage, makeIndexMap };