@ccp-nc/crystvis-js 0.4.13

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 (100) hide show
  1. package/.eslintrc.json +16 -0
  2. package/.github/workflows/test-mocha.yml +30 -0
  3. package/.vscode/settings.json +4 -0
  4. package/LICENSE +21 -0
  5. package/README.html +1127 -0
  6. package/README.md +76 -0
  7. package/demo/demo.css +30 -0
  8. package/demo/index.html +76 -0
  9. package/demo/main.js +143 -0
  10. package/docs/.nojekyll +0 -0
  11. package/docs-tutorials/Events.md +57 -0
  12. package/docs-tutorials/Queries.md +50 -0
  13. package/fonts/Rubik/OFL.txt +93 -0
  14. package/fonts/Rubik/README.txt +77 -0
  15. package/fonts/Rubik/Rubik-Italic-VariableFont_wght.ttf +0 -0
  16. package/fonts/Rubik/Rubik-VariableFont_wght.ttf +0 -0
  17. package/fonts/Rubik/static/Rubik-Black.ttf +0 -0
  18. package/fonts/Rubik/static/Rubik-BlackItalic.ttf +0 -0
  19. package/fonts/Rubik/static/Rubik-Bold.ttf +0 -0
  20. package/fonts/Rubik/static/Rubik-BoldItalic.ttf +0 -0
  21. package/fonts/Rubik/static/Rubik-ExtraBold.ttf +0 -0
  22. package/fonts/Rubik/static/Rubik-ExtraBoldItalic.ttf +0 -0
  23. package/fonts/Rubik/static/Rubik-Italic.ttf +0 -0
  24. package/fonts/Rubik/static/Rubik-Light.ttf +0 -0
  25. package/fonts/Rubik/static/Rubik-LightItalic.ttf +0 -0
  26. package/fonts/Rubik/static/Rubik-Medium.ttf +0 -0
  27. package/fonts/Rubik/static/Rubik-MediumItalic.ttf +0 -0
  28. package/fonts/Rubik/static/Rubik-Regular.ttf +0 -0
  29. package/fonts/Rubik/static/Rubik-SemiBold.ttf +0 -0
  30. package/fonts/Rubik/static/Rubik-SemiBoldItalic.ttf +0 -0
  31. package/index.html +25 -0
  32. package/index.js +11 -0
  33. package/jsconf.json +14 -0
  34. package/lib/assets/fonts/Rubik-Medium.fnt +297 -0
  35. package/lib/assets/fonts/Rubik-Medium.png +0 -0
  36. package/lib/assets/fonts/bmpfonts.in.js +16 -0
  37. package/lib/assets/fonts/bmpfonts.js +9 -0
  38. package/lib/assets/fonts/font.js +82 -0
  39. package/lib/assets/fonts/index.js +14 -0
  40. package/lib/assets/fonts/threebmfont.js +28 -0
  41. package/lib/data.js +125 -0
  42. package/lib/formats/cell.js +114 -0
  43. package/lib/formats/cif.js +22 -0
  44. package/lib/formats/magres.js +337 -0
  45. package/lib/formats/xyz.js +124 -0
  46. package/lib/loader.js +87 -0
  47. package/lib/model.js +2076 -0
  48. package/lib/modelview.js +382 -0
  49. package/lib/nmrdata.js +2898 -0
  50. package/lib/orbit.js +1233 -0
  51. package/lib/primitives/atoms.js +261 -0
  52. package/lib/primitives/cell.js +160 -0
  53. package/lib/primitives/dither.js +156 -0
  54. package/lib/primitives/ellipsoid.js +183 -0
  55. package/lib/primitives/geometries.js +20 -0
  56. package/lib/primitives/index.js +48 -0
  57. package/lib/primitives/isosurface.js +171 -0
  58. package/lib/primitives/shapes.js +100 -0
  59. package/lib/primitives/sprites.js +172 -0
  60. package/lib/query.js +158 -0
  61. package/lib/render.js +440 -0
  62. package/lib/selbox.js +361 -0
  63. package/lib/shaders/aura.frag +26 -0
  64. package/lib/shaders/aura.vert +37 -0
  65. package/lib/shaders/dither.frag +42 -0
  66. package/lib/shaders/dither.vert +8 -0
  67. package/lib/shaders/index.in.js +17 -0
  68. package/lib/shaders/index.js +25 -0
  69. package/lib/shaders/msdf300.frag +25 -0
  70. package/lib/shaders/msdf300.vert +45 -0
  71. package/lib/tensor.js +227 -0
  72. package/lib/utils.js +168 -0
  73. package/lib/visualizer.js +480 -0
  74. package/package.json +106 -0
  75. package/scripts/build-bundle.js +17 -0
  76. package/scripts/build-fonts.js +43 -0
  77. package/scripts/build-resources.js +46 -0
  78. package/scripts/plugins-shim.js +10 -0
  79. package/test/chemdata.js +69 -0
  80. package/test/data/CHA.cif +74 -0
  81. package/test/data/H2O.xyz +8 -0
  82. package/test/data/H2_bound.xyz +4 -0
  83. package/test/data/ethanol.cell +25 -0
  84. package/test/data/ethanol.magres +238 -0
  85. package/test/data/example_single.cif +789 -0
  86. package/test/data/frac.cell +8 -0
  87. package/test/data/org.cif +427 -0
  88. package/test/data/pyridine.xyz +13 -0
  89. package/test/data/si8.xyz +10 -0
  90. package/test/loader.js +107 -0
  91. package/test/model.js +368 -0
  92. package/test/query.js +135 -0
  93. package/test/tensor.js +133 -0
  94. package/test/test-html/examples.js +1485 -0
  95. package/test/test-html/index.html +33 -0
  96. package/test/test-html/index.js +279 -0
  97. package/tools/compile_colors.py +120 -0
  98. package/tools/compile_periodic.py +96 -0
  99. package/tools/ptable.json +497 -0
  100. package/tools/test +5844 -0
package/lib/query.js ADDED
@@ -0,0 +1,158 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @fileoverview Utility functions for queries across Models
5
+ * @module
6
+ */
7
+
8
+ import _ from 'lodash';
9
+
10
+ class QueryParser {
11
+
12
+ /**
13
+ * A parser for queries of any sort; it will recognize all queries whose names
14
+ * are contained in 'methods', which optionally can belong to context;
15
+ * will also recognize logical operators $and, $or and $xor.
16
+ * @class
17
+ * @param {Object} methods Dictionary of methods to be used for querying.
18
+ * @param {*} context Context for the methods above (namely, parent instance if they're class methods)
19
+ * @param {Function} comparison Function used to compare two elements and check that they're identical. Must take
20
+ * (x1, x2) as argument and return a bool.
21
+ */
22
+ constructor(methods, context, comparison) {
23
+
24
+ this._methods = methods;
25
+
26
+ this._context = context;
27
+ this._comparison = comparison || function(x1, x2) {
28
+ return x1 == x2;
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Parses and applies a query. The query comes in the form of a list, like:
34
+ *
35
+ * ['name', '<argument1>', '<argument2>', ...]
36
+ *
37
+ * where 'name' has to be contained in the methods table, and return a list of
38
+ * results. Logical operators can be used, for example:
39
+ *
40
+ * ['$and', [query1], [query2], ...]
41
+ *
42
+ * will return the intersection of the various queries. The logical operators
43
+ * are:
44
+ *
45
+ * - $and : intersection of the results of the arguments
46
+ * - $or : sum of the results of the arguments
47
+ * - $xor : sum minus intersection of the results of the arguments
48
+ *
49
+ * @param {Array} query The query to traverse
50
+ *
51
+ * @returns {Array} The outcome of the query
52
+ */
53
+ parse(query) {
54
+
55
+ var names = Object.keys(query);
56
+
57
+ switch (names.length) {
58
+ case 0:
59
+ return [];
60
+ case 1:
61
+ let name = names[0];
62
+ let args = query[name];
63
+
64
+ if (!(args instanceof Array)) {
65
+ args = [args]; // Just for a single argument
66
+ }
67
+
68
+ if (name in this._methods) {
69
+ // Then call the method
70
+ return this._methods[name].apply(this._context, args);
71
+ } else switch (name) {
72
+ case '$and':
73
+ return this.and(args);
74
+ case '$or':
75
+ return this.or(args);
76
+ case '$xor':
77
+ return this.xor(args);
78
+ default:
79
+ throw new Error('Invalid query: query name not recognized');
80
+ }
81
+ default:
82
+ let queries = _.map(query, (v, k) => {
83
+ var q = {};
84
+ q[k] = v;
85
+ return q;
86
+ });
87
+ return this.and(queries);
88
+ }
89
+
90
+
91
+ for (let i = 0; i < names.length; ++i) {
92
+ let name = names[i];
93
+ let args = query[name];
94
+
95
+ if (!(args instanceof Array)) {
96
+ args = [args]; // Just for a single argument
97
+ }
98
+
99
+ if (name in this._methods) {
100
+ // Then call the method
101
+ return this._methods[name].apply(this._context, args);
102
+ } else switch (name) {
103
+ case '$and':
104
+ return this.and(args);
105
+ case '$or':
106
+ return this.or(args);
107
+ case '$xor':
108
+ return this.xor(args);
109
+ default:
110
+ throw new Error('Invalid query: query name not recognized');
111
+ }
112
+
113
+ }
114
+
115
+ var name = query[0];
116
+ }
117
+
118
+ and(queries) {
119
+ var that = this;
120
+ var results = _.map(queries, function(q) {
121
+ return that.parse(q)
122
+ });
123
+ var result = _.reduce(results.slice(1), function(r, v) {
124
+ return _.intersectionWith(r, v, that._comparison);
125
+ }, results[0]);
126
+
127
+ return result;
128
+ }
129
+
130
+ or(queries) {
131
+ var that = this;
132
+ var results = _.map(queries, function(q) {
133
+ return that.parse(q)
134
+ });
135
+ var result = _.reduce(results, function(r, v) {
136
+ return _.unionWith(r, v, that._comparison);
137
+ }, []);
138
+
139
+ return result;
140
+ }
141
+
142
+ xor(queries) {
143
+ var that = this;
144
+ var results = _.map(queries, function(q) {
145
+ return that.parse(q)
146
+ });
147
+ var result = _.reduce(results, function(r, v) {
148
+ return _.xorWith(r, v, that._comparison);
149
+ }, []);
150
+
151
+ return result;
152
+ }
153
+
154
+ }
155
+
156
+ export {
157
+ QueryParser
158
+ }
package/lib/render.js ADDED
@@ -0,0 +1,440 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @fileoverview Classes and methods for rendering using THREE.js
5
+ * @module
6
+ */
7
+
8
+ // NPM imports
9
+ import $ from 'jquery';
10
+ import _ from 'lodash';
11
+ import * as THREE from 'three';
12
+
13
+ // Internal imports
14
+ import {
15
+ OrbitControls
16
+ } from './orbit.js';
17
+ import {
18
+ SelectionBox,
19
+ SelectionHelper
20
+ } from './selbox.js';
21
+
22
+ import * as Primitives from './primitives/index.js';
23
+
24
+
25
+ class Renderer {
26
+
27
+ /**
28
+ * An object representing the THREE.js graphical renderer for atomic models
29
+ * @class
30
+ * @param {string} target CSS selector for the target HTML element in which to put the renderer
31
+ * @param {int} width Desired width for the renderer
32
+ * @param {int} height Desired height for the renderer. If both this and width are zero, automatically
33
+ * resizes with the container
34
+ */
35
+ constructor(target, width, height) {
36
+
37
+ // Grab the target element
38
+ this._div = $(target);
39
+ this._autoResize = (width == 0) && (height == 0);
40
+
41
+ this._w = width;
42
+ this._h = height;
43
+ this._updateSize();
44
+
45
+ // Renderer
46
+ this._r = new THREE.WebGLRenderer();
47
+ this._r.autoClear = true;
48
+ this._r.setPixelRatio(window.devicePixelRatio);
49
+ this._r.setSize(this._w, this._h);
50
+ this._div.append(this._r.domElement);
51
+
52
+ // Scene
53
+ this._s = new THREE.Scene();
54
+
55
+ // Camera
56
+ var ratio = this._w * 1.0 / this._h;
57
+ this._depth = 1000; // For now we use a constant
58
+ this._c = new THREE.OrthographicCamera(-20, 20,
59
+ 20 / ratio, -20 / ratio,
60
+ 0.1, 2 * this._depth);
61
+ this._s.add(this._c);
62
+
63
+ // Lights
64
+ this._l = {}
65
+ this._l._amb = new THREE.AmbientLight(0xffffff, 0.2);
66
+ this._l._amb.name = 'ambLight';
67
+ this._l._dir = new THREE.DirectionalLight(0xffffff, 0.5);
68
+ this._l._dir.position.set(0, 1, -1);
69
+ this._l._dir.name = 'dirLight';
70
+ this._s.add(this._l._amb); // Added to scene
71
+ this._c.add(this._l._dir); // Added to camera (rotates with it)
72
+
73
+ // Controls
74
+ this._oc = new OrbitControls(this._c, this._r.domElement);
75
+ this.resetOrbitCenter(0, 0, 0);
76
+
77
+ // Raycast for clicks
78
+ this._rcastlist = [];
79
+ this._r.domElement.addEventListener('pointerdown', this._raycastClick.bind(this));
80
+
81
+ // Groups
82
+ this._groups = {
83
+ model: new THREE.Group(),
84
+ primitives: new THREE.Group()
85
+ };
86
+
87
+ this._s.add(this._groups.model);
88
+ this._s.add(this._groups.primitives);
89
+
90
+ // Selection box (multiple raycast)
91
+ this._sboxlist = [];
92
+ this._sbox = new SelectionBox(this._c, this._s);
93
+ this._sboxhelp = new SelectionHelper(this._sbox, this, 'crystvis-selbox-helper');
94
+ this._sboxhelp.selectOverCallback = this._selectBoxEnd.bind(this);
95
+
96
+ // Color scheme
97
+ this._cell_line_color = 0xaaaaaa;
98
+ this._cell_x_color = 0xff0000;
99
+ this._cell_y_color = 0x00ff00;
100
+ this._cell_z_color = 0x0000ff;
101
+
102
+ this.selbox_bkg_color = 0x1111aa;
103
+ this.selbox_border_color = 0x5555dd;
104
+ this.selbox_opacity = 0.5;
105
+
106
+ // Set up the animation
107
+ this._animate();
108
+
109
+ // Resizing in case it's required
110
+ if (this._autoResize) {
111
+ this._resizeObs = new ResizeObserver(this._resize.bind(this)
112
+ );
113
+ this._resizeObs.observe(this._div[0]);
114
+ }
115
+
116
+ }
117
+
118
+ _render() {
119
+ this._r.render(this._s, this._c);
120
+ }
121
+
122
+ _animate() {
123
+ requestAnimationFrame(this._animate.bind(this));
124
+ this._render();
125
+ }
126
+
127
+ _updateSize() {
128
+ this._w = this._w || this._div.innerWidth();
129
+ this._h = this._h || this._div.innerHeight();
130
+ this._offset = this._div.offset();
131
+ }
132
+
133
+ _resize() {
134
+ // Resize event handler
135
+ this._w = 0;
136
+ this._h = 0;
137
+ this._updateSize()
138
+ this._r.setSize(this._w, this._h);
139
+
140
+ var ratio = this._w * 1.0 / this._h;
141
+ this._c.top = 20/ratio;
142
+ this._c.bottom = -20/ratio;
143
+ this._c.updateProjectionMatrix();
144
+ }
145
+
146
+ _raycastClick(e) {
147
+
148
+ // We create a 2D vector
149
+ var vector = this.documentToWorld(e.clientX, e.clientY);
150
+
151
+ // We create a raycaster, which is some kind of laser going through your scene
152
+ var raycaster = new THREE.Raycaster();
153
+ // We apply two parameters to the 'laser', its origin (where the user clicked)
154
+ // and the direction (what the camera 'sees')
155
+ raycaster.setFromCamera(vector, this._c);
156
+
157
+ // We get all the objects the 'laser' find on its way (it returns an array containing the objects)
158
+ for (var i = 0; i < this._rcastlist.length; ++i) {
159
+
160
+ var func = this._rcastlist[i][0];
161
+ var targ = this._rcastlist[i][1];
162
+ var filter = this._rcastlist[i][2];
163
+
164
+ targ = targ || this._s;
165
+ var intersects = raycaster.intersectObjects(targ.children);
166
+
167
+ var objects = [];
168
+ for (var j = 0; j < intersects.length; ++j) {
169
+ var o = intersects[j].object;
170
+ if (!filter || intersects[j].object instanceof filter) {
171
+ objects.push(o);
172
+ }
173
+ }
174
+
175
+ func(objects, e);
176
+
177
+ }
178
+ }
179
+
180
+ _selectBoxEnd(p1, p2) {
181
+ for (var i = 0; i < this._sboxlist.length; ++i) {
182
+ var func = this._sboxlist[i][0];
183
+ var targ = this._sboxlist[i][1];
184
+ var filter = this._sboxlist[i][2];
185
+
186
+ var selected = this._sbox.select(p1, p2, targ);
187
+
188
+ selected = _.filter(selected, function(o) {
189
+ return (!filter || o instanceof filter)
190
+ });
191
+
192
+ func(selected);
193
+ }
194
+ }
195
+
196
+ add(object, group = 'primitives') {
197
+ if (!(this._groups[group].children.includes(object)))
198
+ this._groups[group].add(object);
199
+ }
200
+
201
+ remove(object, group = 'primitives') {
202
+ this._groups[group].remove(object);
203
+ }
204
+
205
+ /**
206
+ * Add a listener for click events on a given group
207
+ *
208
+ * @param {Function} listener Listener function. Will receive a list of objects, sorted by distance.
209
+ * @param {THREE.Group} group Group on which to detect clicks
210
+ * @param {Function} filtertype If present, only pass objects of this class
211
+ *
212
+ * @returns {Array} Reference to the created listener
213
+ */
214
+ addClickListener(listener, group, filtertype = null) {
215
+ var cl = [listener, group, filtertype];
216
+ this._rcastlist.push(cl);
217
+ return cl;
218
+ }
219
+
220
+ /**
221
+ * Remove a listener
222
+ * @param {Array} cl Reference to the listener to remove (as returned by addClickListener)
223
+ */
224
+ removeClickListener(cl) {
225
+ _.pull(this._rcastlist, cl);
226
+ }
227
+
228
+ /**
229
+ * Add a listener for selection box events
230
+ *
231
+ * @param {Function} listener Listener function
232
+ * @param {THREE.Group} group Group on which to detect clicks
233
+ * @param {Function} filtertype If present, only pass objects of this class
234
+ *
235
+ * @returns {Array} Reference to the created listener
236
+ */
237
+ addSelBoxListener(listener, group, filtertype = null) {
238
+ var sbl = [listener, group, filtertype];
239
+ this._sboxlist.push(sbl);
240
+ return sbl;
241
+ }
242
+
243
+ /**
244
+ * Remove a selection box listener
245
+ * @param {Array} sbl Reference to the listener to remove (as returned by addSelBoxListener)
246
+ */
247
+ removeSelBoxListener(sbl) {
248
+ _.pull(this._sboxlist, sbl);
249
+ }
250
+
251
+ /**
252
+ * Set properties of ambient light
253
+ *
254
+ * @param {float} intensity Intensity
255
+ */
256
+ setAmbientLight(intensity) {
257
+ this._l._amb.intensity = intensity;
258
+ }
259
+
260
+ /**
261
+ * Set properties of directional light
262
+ *
263
+ * @param {float} intensity Intensity
264
+ * @param {float} px Direction, x
265
+ * @param {float} py Direction, y
266
+ * @param {float} pz Direction, z
267
+ */
268
+ setDirectionalLight(intensity, px, py, pz) {
269
+ px = px === null ? this._l.dir.position.x : px;
270
+ py = py === null ? this._l.dir.position.y : py;
271
+ pz = pz === null ? this._l.dir.position.z : pz;
272
+ this._l._dir.intensity = intensity;
273
+ this._l._dir.position.set(px, py, pz);
274
+ }
275
+
276
+ /**
277
+ * Reset the camera's view so its central axis is rendered
278
+ * with an offset from the center of the canvas
279
+ *
280
+ * @param {float} fx Horizontal offset in the canvas (in fraction of its width, -0.5 to 0.5)
281
+ * @param {float} fy Vertical offset in the canvas (in fraction of its height, -0.5 to 0.5)
282
+ */
283
+ resetCameraCenter(fx=0, fy=0) {
284
+ this._c.setViewOffset(this._w, this._h, -fx*this._w, -fy*this._h,
285
+ this._w, this._h);
286
+ }
287
+
288
+ /**
289
+ * Reset the camera so it orbits around a given point
290
+ *
291
+ * @param {float} x X coordinate of the point
292
+ * @param {float} y Y coordinate of the point
293
+ * @param {float} z Z coordinate of the point
294
+ */
295
+ resetOrbitCenter(x=0, y=0, z=0) {
296
+
297
+ var p = new THREE.Vector3(x, y, z);
298
+
299
+ this._c.position.set(x, y, this._depth + 0.1);
300
+ this._c.lookAt(p);
301
+ this._oc.target = p;
302
+ }
303
+
304
+ /**
305
+ * Remove all currently rendered objects.
306
+ */
307
+ clear(model = true, primitives = true) {
308
+ if (model)
309
+ this._groups.model.clear();
310
+ if (primitives)
311
+ this._groups.primitives.clear();
312
+ // Reset camera position
313
+ this._oc.reset();
314
+ }
315
+
316
+ /**
317
+ * Convert coordinates in the dom element frame to coordinates in the world frame
318
+ */
319
+ documentToWorld(x, y) {
320
+ // We create a 2D vector
321
+ var vector = new THREE.Vector2();
322
+ // We set its position where the user clicked and we convert it to a number between -1 & 1
323
+ vector.set(
324
+ 2 * ((x - this._offset.left) / this._w) - 1,
325
+ 1 - 2 * ((y - this._offset.top) / this._h)
326
+ );
327
+
328
+ return vector;
329
+ }
330
+
331
+ // Style
332
+ get selbox_bkg_color() {
333
+ return this._selbox_bkg_color;
334
+ }
335
+
336
+ set selbox_bkg_color(c) {
337
+ c = new THREE.Color(c).getStyle();
338
+ this._selbox_bkg_color = c;
339
+ this._sboxhelp.element.css({
340
+ 'background-color': c
341
+ });
342
+ }
343
+
344
+ get selbox_border_color() {
345
+ return this._selbox_border_color;
346
+ }
347
+
348
+ set selbox_border_color(c) {
349
+ c = new THREE.Color(c).getStyle();
350
+ this._selbox_border_color = c;
351
+ this._sboxhelp.element.css({
352
+ 'border-color': c
353
+ });
354
+ }
355
+
356
+ get selbox_opacity() {
357
+ return this._selbox_opacity;
358
+ }
359
+
360
+ set selbox_opacity(o) {
361
+ this._selbox_opacity = o;
362
+ this._sboxhelp.element.css({
363
+ 'opacity': o
364
+ });
365
+ }
366
+
367
+ // This convenient wrapper is useful to keep the Primitives out of the Model's logic.
368
+ // This way, we don't suffer problems when testing purely mathematical/logical stuff
369
+ // by command line with mocha. Primitives are messy and don't build well for CLI with
370
+ // esbuild, for some reason.
371
+ get Primitives() {
372
+ return Primitives;
373
+ };
374
+
375
+ }
376
+
377
+ /**
378
+ * Add a vector field representation to the model
379
+ *
380
+ * @param {Array} points List of origins for the vectors
381
+ * @param {Array} vectors Vectors to plot
382
+ * @param {*} colors Colors for each vector. Can be a single color, an array of colors, or a function that takes
383
+ * origin, vector, and index, and returns a color.
384
+ * @param {float} scale Scaling factor
385
+ *
386
+ * @returns {THREE.Group} Rendered object
387
+ */
388
+
389
+ /*
390
+ _addVectorField: function(points, vectors, colors, scale) {
391
+
392
+ var N = points.length;
393
+ if (vectors.length != N)
394
+ throw 'Points and vectors arrays not matching for vector field';
395
+
396
+ // We always reduce colors to a function with signature (p, v, i) but
397
+ // it can also be an array or a single scalar.
398
+ colors = colors || 0xffffff;
399
+ switch (typeof colors) {
400
+ case 'function':
401
+ break;
402
+ case 'object':
403
+ if (colors.length != N)
404
+ throw 'Colors array not matching for vector field';
405
+ var colors_arr = colors.slice();
406
+ colors = function(p, v, i) {
407
+ return colors_arr[i];
408
+ }
409
+ break;
410
+ default:
411
+ var colors_val = colors;
412
+ colors = function(p, v, i) {
413
+ return colors_val;
414
+ }
415
+ }
416
+ scale = scale || 1;
417
+
418
+ var vfield = new THREE.Group();
419
+
420
+ for (var i = 0; i < N; ++i) {
421
+ var p = points[i];
422
+ var v = vectors[i];
423
+ var c = colors(p, v, i);
424
+ var l = v.length();
425
+ v.normalize();
426
+ var arr = new THREE.ArrowHelper(v, p, l, c, 0.2 * l, 0.1 * l);
427
+ vfield.add(arr);
428
+ }
429
+
430
+ this._g._plots.add(vfield);
431
+
432
+ return vfield;
433
+ },
434
+
435
+ */
436
+
437
+
438
+ export {
439
+ Renderer
440
+ };