Skip to content

Commit bad640f

Browse files
authored
Merge pull request #40 from martinRenou/colorbars
Implement colorbars
2 parents 0c77ea5 + 7233b63 commit bad640f

4 files changed

Lines changed: 176 additions & 17 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"backbone": "^1.4.0",
3232
"binary-search-tree": "^0.2.6",
3333
"d3-color": "^1.4.0",
34+
"d3-format": "^2.0.0",
3435
"d3-interpolate": "^1.4.0",
3536
"d3-scale": "^3.2.1",
3637
"d3-scale-chromatic": "^1.5.0",

src/Effects/IsoColor.ts

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as THREE from 'three';
22
import * as Nodes from 'three/examples/jsm/nodes/Nodes';
33

4+
const d3Format = require('d3-format');
5+
46
import {
57
Effect, Input, InputDimension
68
} from '../EffectBlock';
@@ -18,27 +20,25 @@ import {
1820
} from '../NodeMesh';
1921

2022
import {
21-
getColorMapTexture
23+
ScaleType, getColorMapTexture, getColorInterpolator, getColorBar, updateColorBar
2224
} from '../utils/colormaps';
2325

2426

2527
export
2628
class IsoColor extends Effect {
2729

28-
constructor (parent: Block, input: Input, min: number, max: number, colorMap: string = 'Viridis') {
30+
constructor (parent: Block, input: Input, min: number, max: number, colorMap: string = 'Viridis', type: ScaleType = ScaleType.linear) {
2931
super(parent, input);
3032

31-
this.texture = getColorMapTexture(colorMap);
33+
this.format = d3Format.format('.2e');
34+
this._type = type;
35+
this.colorInterpolator = getColorInterpolator(colorMap);
36+
this.colorBar = getColorBar(this.colorInterpolator, [min, max], type, this.format);
37+
this.texture = getColorMapTexture(this.colorInterpolator);
3238

3339
this.textureNode = new Nodes.TextureNode(this.texture);
3440

35-
const functionNode = new Nodes.FunctionNode(
36-
`vec3 isoColorFunc${this.id}(sampler2D textureMap, float min, float max, float data){
37-
vec2 colorPosition = vec2((data - min) / (max - min), 0.0);
38-
39-
return vec3(texture2D(textureMap, colorPosition));
40-
}`
41-
);
41+
const functionNode = this.getFunctionNode();
4242

4343
this.minNode = new Nodes.FloatNode(min);
4444
this.maxNode = new Nodes.FloatNode(max);
@@ -68,15 +68,21 @@ class IsoColor extends Effect {
6868
}
6969

7070
set min (value: number) {
71+
updateColorBar(this.colorBar, this.colorInterpolator, [value, this.maxNode.value], this._type, this.format);
7172
this.minNode.value = value;
73+
74+
this.trigger('change:colorbar');
7275
}
7376

7477
get min () {
7578
return this.minNode.value;
7679
}
7780

7881
set max (value: number) {
82+
updateColorBar(this.colorBar, this.colorInterpolator, [this.minNode.value, value], this._type, this.format);
7983
this.maxNode.value = value;
84+
85+
this.trigger('change:colorbar');
8086
}
8187

8288
get max () {
@@ -88,14 +94,57 @@ class IsoColor extends Effect {
8894
}
8995

9096
set colorMap (colorMap: string) {
91-
this.texture = getColorMapTexture(colorMap);
97+
this.colorInterpolator = getColorInterpolator(colorMap);
98+
updateColorBar(this.colorBar, this.colorInterpolator, [this.minNode.value, this.maxNode.value], this._type, this.format);
99+
this.texture = getColorMapTexture(this.colorInterpolator);
92100
this.textureNode.value = this.texture;
101+
102+
this.trigger('change:colorbar');
93103
}
94104

105+
set type (type: ScaleType) {
106+
this._type = type;
107+
108+
updateColorBar(this.colorBar, this.colorInterpolator, [this.minNode.value, this.maxNode.value], this._type, this.format);
109+
110+
const functionNode = this.getFunctionNode();
111+
this.functionCallNode.setFunction(functionNode, [this.textureNode, this.minNode, this.maxNode, this.inputNode]);
112+
113+
this.buildMaterial();
114+
115+
this.trigger('change:colorbar');
116+
}
117+
118+
private getFunctionNode () {
119+
if (this._type == ScaleType.linear) {
120+
return new Nodes.FunctionNode(
121+
`vec3 isoColorFunc${this.id}(sampler2D textureMap, float min, float max, float data){
122+
vec2 colorPosition = vec2((data - min) / (max - min), 0.0);
123+
124+
return vec3(texture2D(textureMap, colorPosition));
125+
}`
126+
);
127+
} else {
128+
return new Nodes.FunctionNode(
129+
`vec3 isoColorFunc${this.id}(sampler2D textureMap, float min, float max, float data){
130+
vec2 colorPosition = vec2((log(data) - log(min)) / (log(max) - log(min)), 0.0);
131+
132+
return vec3(texture2D(textureMap, colorPosition));
133+
}`
134+
);
135+
}
136+
}
137+
138+
colorBar: HTMLCanvasElement;
139+
95140
private initialized: boolean = false;
96141

97142
private functionCallNode: Nodes.FunctionCallNode;
98143

144+
private colorInterpolator: (v: number) => string;
145+
private format: (v: number) => string;
146+
private _type: ScaleType;
147+
99148
private minNode: Nodes.FloatNode;
100149
private maxNode: Nodes.FloatNode;
101150

src/utils/colormaps.ts

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as THREE from 'three';
22

33
const d3Color = require('d3-color');
44
const d3Chromatic = require('d3-scale-chromatic');
5-
const d3Scale = require('d3-scale');
65

76

87
const colormapsInterpolators: { [name: string]: any } = {
@@ -48,14 +47,24 @@ const colormapsInterpolators: { [name: string]: any } = {
4847

4948

5049
export
51-
function getColorMapTexture (colorMapName: string): THREE.DataTexture {
52-
// Add support for log scales scaleSequentialLog
53-
const colorScale = d3Scale.scaleSequential(colormapsInterpolators[colorMapName]);
50+
enum ScaleType {
51+
linear='linear',
52+
log='log',
53+
}
54+
55+
56+
export
57+
function getColorInterpolator (colorMapName: string): (v: number) => string {
58+
return colormapsInterpolators[colorMapName];
59+
}
5460

61+
62+
export
63+
function getColorMapTexture (colorInterpolator: (value: number) => string): THREE.DataTexture {
5564
const nColors = 1024;
5665
const colorsArray = new Uint8Array(nColors * 3);
5766
for (let i = 0; i < nColors; i++) {
58-
const color = d3Color.color(colorScale(i / (nColors - 1)));
67+
const color = d3Color.color(colorInterpolator(i / (nColors - 1)));
5968

6069
const colorIndex = 3 * i;
6170

@@ -66,3 +75,103 @@ function getColorMapTexture (colorMapName: string): THREE.DataTexture {
6675

6776
return new THREE.DataTexture(colorsArray, nColors, 1, THREE.RGBFormat);
6877
}
78+
79+
80+
export
81+
function getColorBar (
82+
colorInterpolator: (v: number) => string,
83+
range: number[],
84+
type: ScaleType,
85+
format: (v: number) => string): HTMLCanvasElement {
86+
const canvas = document.createElement('canvas');
87+
88+
updateColorBar(canvas, colorInterpolator, range, type, format);
89+
90+
return canvas;
91+
}
92+
93+
94+
export
95+
function updateColorBar (
96+
canvas: HTMLCanvasElement,
97+
colorInterpolator: (v: number) => string,
98+
range: number[],
99+
type: ScaleType,
100+
format: (v: number) => string): void {
101+
const ctx = canvas.getContext('2d');
102+
103+
const width = 1024;
104+
105+
canvas.width = width;
106+
canvas.height = 100;
107+
108+
if (ctx === null) {
109+
throw 'Failed to create canvas context for the colorbar';
110+
}
111+
112+
ctx.save();
113+
114+
ctx.clearRect(0, 0, canvas.width, canvas.height);
115+
116+
ctx.fillStyle = 'black';
117+
ctx.font = '35px Open Sans';
118+
119+
ctx.textAlign = 'start'
120+
ctx.fillText(format(range[0]), 0, 100);
121+
122+
ctx.textAlign = 'center';
123+
if (type == ScaleType.linear) {
124+
ctx.fillText(format(range[1] + (range[0] - range[1]) * 0.75), width / 4, 100);
125+
ctx.fillText(format(range[1] + (range[0] - range[1]) * 0.5), width / 2, 100);
126+
ctx.fillText(format(range[1] + (range[0] - range[1]) * 0.25), width * 3 / 4, 100);
127+
} else {
128+
ctx.fillText(format(Math.pow(Math.E, (Math.log(range[0])+(Math.log(range[1])-Math.log(range[0]))*0.25))), width / 4, 100);
129+
ctx.fillText(format(Math.pow(Math.E, (Math.log(range[0])+(Math.log(range[1])-Math.log(range[0]))*0.5))), width / 2, 100);
130+
ctx.fillText(format(Math.pow(Math.E, (Math.log(range[0])+(Math.log(range[1])-Math.log(range[0]))*0.75))), width * 3 / 4, 100);
131+
}
132+
133+
ctx.textAlign = 'end'
134+
ctx.fillText(format(range[1]), width, 100);
135+
136+
for (let i = 0; i < width; ++i) {
137+
ctx.fillStyle = colorInterpolator(i / (width - 1));
138+
ctx.fillRect(i, 0, 1, 60);
139+
}
140+
141+
// Draw outline and tick lines
142+
ctx.lineWidth = 4;
143+
ctx.fillStyle = 'black';
144+
ctx.strokeRect(0, 0, width, 60);
145+
146+
ctx.beginPath();
147+
ctx.moveTo(0, 52);
148+
ctx.lineTo(0, 68);
149+
ctx.closePath();
150+
ctx.stroke();
151+
152+
ctx.beginPath();
153+
ctx.moveTo(width / 4, 52);
154+
ctx.lineTo(width / 4, 68);
155+
ctx.closePath();
156+
ctx.stroke();
157+
158+
ctx.beginPath();
159+
ctx.moveTo(width / 2, 52);
160+
ctx.lineTo(width / 2, 68);
161+
ctx.closePath();
162+
ctx.stroke();
163+
164+
ctx.beginPath();
165+
ctx.moveTo(width * 3 / 4, 52);
166+
ctx.lineTo(width * 3 / 4, 68);
167+
ctx.closePath();
168+
ctx.stroke();
169+
170+
ctx.beginPath();
171+
ctx.moveTo(width, 52);
172+
ctx.lineTo(width, 68);
173+
ctx.closePath();
174+
ctx.stroke();
175+
176+
ctx.restore();
177+
}

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ d3-color@1, d3-color@^1.4.0:
3636
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e"
3737
integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==
3838

39-
"d3-format@1 - 2":
39+
"d3-format@1 - 2", d3-format@^2.0.0:
4040
version "2.0.0"
4141
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767"
4242
integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==

0 commit comments

Comments
 (0)