diff --git a/src/canvas/canvasRenderer.js b/src/canvas/canvasRenderer.js index e7160b320f..4f8a584a50 100644 --- a/src/canvas/canvasRenderer.js +++ b/src/canvas/canvasRenderer.js @@ -89,6 +89,7 @@ var canvasRenderer = function (arg) { var canvas = m_this.canvas(); if (parseInt(canvas.attr('width'), 10) !== w || parseInt(canvas.attr('height'), 10) !== h) { + m_this._setWidthHeight(w, h); canvas.attr('width', w); canvas.attr('height', h); m_this._render(); diff --git a/src/d3/d3Renderer.js b/src/d3/d3Renderer.js index 6ab51b586b..6d5823fcd7 100644 --- a/src/d3/d3Renderer.js +++ b/src/d3/d3Renderer.js @@ -43,8 +43,6 @@ var d3Renderer = function (arg) { m_sticky = null, m_features = {}, m_corners = null, - m_width = null, - m_height = null, m_diagonal = null, m_scale = 1, m_transform = {dx: 0, dy: 0, rx: 0, ry: 0, rotation: 0}, @@ -209,11 +207,10 @@ var d3Renderer = function (arg) { width = map.size().width, height = map.size().height; - m_width = width; - m_height = height; - if (!m_width || !m_height) { + if (!width || !height) { throw new Error('Map layer has size 0'); } + m_this._setWidthHeight(width, height); m_diagonal = Math.pow(width * width + height * height, 0.5); m_corners = { upperLeft: map.displayToGcs({x: 0, y: 0}, null), @@ -249,8 +246,8 @@ var d3Renderer = function (arg) { Math.pow(lowerRight.x - upperLeft.x, 2)) / m_diagonal; // calculate the translation rotation = map.rotation(); - rx = -m_width / 2; - ry = -m_height / 2; + rx = -m_this.width() / 2; + ry = -m_this.height() / 2; dx = scale * rx + center.x; dy = scale * ry + center.y; @@ -454,6 +451,7 @@ var d3Renderer = function (arg) { m_svg.attr('width', w); m_svg.attr('height', h); m_this._setTransform(); + m_this._setWidthHeight(w, h); m_this.layer().geoTrigger(d3Rescale, { scale: m_scale }, true); return m_this; }; diff --git a/src/d3/pointFeature.js b/src/d3/pointFeature.js index 0cf2733ab0..d3ec680401 100644 --- a/src/d3/pointFeature.js +++ b/src/d3/pointFeature.js @@ -108,7 +108,10 @@ var d3_pointFeature = function (arg) { inherit(d3_pointFeature, pointFeature); +var capabilities = {}; +capabilities[pointFeature.capabilities.stroke] = true; + // Now register it -registerFeature('d3', 'point', d3_pointFeature); +registerFeature('d3', 'point', d3_pointFeature, capabilities); module.exports = d3_pointFeature; diff --git a/src/gl/pointFeature.js b/src/gl/pointFeature.js index 5975db6c05..fd8cb95e92 100644 --- a/src/gl/pointFeature.js +++ b/src/gl/pointFeature.js @@ -658,7 +658,10 @@ var gl_pointFeature = function (arg) { inherit(gl_pointFeature, pointFeature); +var capabilities = {}; +capabilities[pointFeature.capabilities.stroke] = true; + // Now register it -registerFeature('vgl', 'point', gl_pointFeature); +registerFeature('vgl', 'point', gl_pointFeature, capabilities); module.exports = gl_pointFeature; diff --git a/src/gl/vglRenderer.js b/src/gl/vglRenderer.js index 7abd298dae..96c6b56cbd 100644 --- a/src/gl/vglRenderer.js +++ b/src/gl/vglRenderer.js @@ -33,32 +33,11 @@ var vglRenderer = function (arg) { var m_this = this, m_contextRenderer = null, m_viewer = null, - m_width = 0, - m_height = 0, m_lastZoom, m_updateCamera = false, s_init = this._init, s_exit = this._exit; - // TODO: Move this API to the base class - /** - * Return width of the renderer. - * - * @returns {number} The width of the current canvas. - */ - this.width = function () { - return m_width; - }; - - /** - * Return height of the renderer. - * - * @returns {number} The height of the current canvas. - */ - this.height = function () { - return m_height; - }; - /** * Get context specific renderer. * @@ -140,9 +119,8 @@ var vglRenderer = function (arg) { if (x !== renderWindow.windowPosition[0] || y !== renderWindow.windowPosition[1] || - w !== m_width || h !== m_height) { - m_width = w; - m_height = h; + w !== m_this.width() || h !== m_this.height()) { + m_this._setWidthHeight(w, h); m_this.canvas().attr('width', w); m_this.canvas().attr('height', h); renderWindow.positionAndResize(x, y, w, h); diff --git a/src/pointFeature.js b/src/pointFeature.js index c4bcda7458..c79df21505 100644 --- a/src/pointFeature.js +++ b/src/pointFeature.js @@ -444,7 +444,9 @@ pointFeature.create = function (layer, spec) { pointFeature.capabilities = { /* core feature name -- support in any manner */ - feature: 'point' + feature: 'point', + /* support for stroke properties */ + stroke: 'line.stroke' }; inherit(pointFeature, feature); diff --git a/src/renderer.js b/src/renderer.js index 07b1a979fb..4b1839f000 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -23,6 +23,8 @@ var renderer = function (arg) { arg = arg || {}; var m_this = this, + m_width = 0, + m_height = 0, m_layer = arg.layer === undefined ? null : arg.layer, m_canvas = arg.canvas === undefined ? null : arg.canvas, m_initialized = false; @@ -94,6 +96,37 @@ var renderer = function (arg) { throw new Error('Should be implemented by derived classes'); }; + /** + * Get the width of the renderer. + * + * @returns {number} The width of the renderer. + */ + this.width = function () { + return m_width; + }; + + /** + * Get the height of the renderer. + * + * @returns {number} The height of the renderer. + */ + this.height = function () { + return m_height; + }; + + /** + * Set the width and height of the renderer. + * + * @param {number} width The new width. + * @param {number} height The new height. + * @returns {this} + */ + this._setWidthHeight = function (width, height) { + m_width = width; + m_height = height; + return m_this; + }; + /** * Initialize. * @@ -113,6 +146,7 @@ var renderer = function (arg) { * @returns {this} */ this._resize = function (x, y, w, h) { + m_this._setWidthHeight(w, h); return m_this; }; diff --git a/src/vtkjs/object.js b/src/vtkjs/object.js new file mode 100644 index 0000000000..ff83b0c98e --- /dev/null +++ b/src/vtkjs/object.js @@ -0,0 +1,41 @@ +/** + * Vtk.js specific subclass of object which rerenders when the object is drawn. + * + * @class + * @alias geo.vtkjs.object + * @extends geo.sceneObject + * @param {object} arg Options for the object. + * @returns {geo.vtkjs.object} + */ +var vtkjs_object = function (arg) { + 'use strict'; + + var object = require('../object'); + + // this is used to extend other geojs classes, so only generate + // a new object when that is not the case... like if this === window + if (!(this instanceof object)) { + return new vtkjs_object(arg); + } + + var m_this = this, + s_draw = this.draw; + + /** + * Redraw the object. + * + * @returns {this} + */ + this.draw = function () { + if (m_this.ready) { + m_this._update(); + m_this.renderer()._render(); + s_draw(); + } + return m_this; + }; + + return this; +}; + +module.exports = vtkjs_object; diff --git a/src/vtkjs/pointFeature.js b/src/vtkjs/pointFeature.js index 8c5d52755b..7369964e39 100644 --- a/src/vtkjs/pointFeature.js +++ b/src/vtkjs/pointFeature.js @@ -20,22 +20,23 @@ var vtkjs_pointFeature = function (arg) { pointFeature.call(this, arg); var transform = require('../transform'); - var object = require('../object'); + var object = require('./object'); var vtk = require('./vtkjsRenderer').vtkjs; var vtkActor = vtk.Rendering.Core.vtkActor; - var vtkMapper = vtk.Rendering.Core.vtkGlyph3DMapper; + var vtkDataArray = vtk.Common.Core.vtkDataArray; + var vtkGlyph3DMapper = vtk.Rendering.Core.vtkGlyph3DMapper; + var vtkMapper = vtk.Rendering.Core.vtkMapper; var vtkPointSet = vtk.Common.DataModel.vtkPointSet; var vtkSphereSource = vtk.Filters.Sources.vtkSphereSource; object.call(this); - /** - * @private - */ var m_this = this, m_actor, m_pointSet, m_source, + m_colorArray, + m_diamArray, s_init = this._init, s_exit = this._exit, s_update = this._update; @@ -48,7 +49,20 @@ var vtkjs_pointFeature = function (arg) { m_source = vtkSphereSource.newInstance(); m_source.setThetaResolution(30); m_source.setPhiResolution(30); - var mapper = vtkMapper.newInstance(); + var mapper = vtkGlyph3DMapper.newInstance({ + // Orientation + orient: false, + + // Color and Opacity + colorByArrayName: 'color', + scalarMode: vtkMapper.ScalarMode.USE_POINT_FIELD_DATA, + colorMode: vtkMapper.ColorMode.DIRECT_SCALARS, + + // Scaling + scaling: true, + scaleArray: 'diam', + scaleMode: vtkGlyph3DMapper.ScaleModes.SCALE_BY_MAGNITUDE + }); mapper.setInputData(m_pointSet, 0); mapper.setInputConnection(m_source.getOutputPort(), 1); m_actor = vtkActor.newInstance(); @@ -70,26 +84,40 @@ var vtkjs_pointFeature = function (arg) { * Build this feature. */ this._build = function () { - var i, i3, posVal, + var i, i3, i4, posVal, clrVal, nonzeroZ, numPts = m_this.data().length, position = new Array(numPts * 3), data = m_this.data(), posFunc = m_this.position(), radFunc = m_this.style.get('radius'), + fillFunc = m_this.style.get('fill'), colorFunc = m_this.style.get('fillColor'), - opacityFunc = m_this.style.get('fillOpacity'); + opacityFunc = m_this.style.get('fillOpacity'), + unitsPerPixel = m_this.layer().map().unitsPerPixel(m_this.layer().map().zoom()); + + if (!m_diamArray || m_diamArray.length !== numPts) { + m_diamArray = new Float32Array(numPts); + } + if (!m_colorArray || m_colorArray.length !== numPts * 4) { + m_colorArray = new Uint8Array(numPts * 4); + } /* It is more efficient to do a transform on a single array rather than on - * an array of arrays or an array of objects. */ - for (i = i3 = 0; i < numPts; i += 1, i3 += 3) { + * an array of arrays or an array of objects. */ + for (i = i3 = i4 = 0; i < numPts; i += 1, i3 += 3, i4 += 4) { posVal = posFunc(data[i], i); position[i3] = posVal.x; position[i3 + 1] = posVal.y; position[i3 + 2] = posVal.z || 0; nonzeroZ = nonzeroZ || position[i3 + 2]; - // TODO: fix the opacity per point. - m_actor.getProperty().setOpacity(opacityFunc(data[i], i)); + + m_diamArray[i] = radFunc(data[i], i) * unitsPerPixel * 2; + clrVal = colorFunc(data[i], i); + m_colorArray[i4] = clrVal.r * 255; + m_colorArray[i4 + 1] = clrVal.g * 255; + m_colorArray[i4 + 2] = clrVal.b * 255; + m_colorArray[i4 + 3] = fillFunc(data[i], i) ? opacityFunc(data[i], i) * 255 : 0; } position = transform.transformCoordinates( m_this.gcs(), m_this.layer().map().gcs(), @@ -104,15 +132,12 @@ var vtkjs_pointFeature = function (arg) { } } - // TODO: points can vary, this needs to be refactors to have a distinct - // size per point. What should be done with the strokeColor, strokeWidth, - // and strokeOpacity? Honor the fill/stroke options or document that we - // don't honot them. Handle the zero-data-itmes condition. - var rad = radFunc(data[0], 0), clr = colorFunc(data[0], 0); - rad *= m_this.layer().map().unitsPerPixel(m_this.layer().map().zoom()); m_pointSet.getPoints().setData(position, 3); - m_source.setRadius(rad); - m_actor.getProperty().setColor(clr.r, clr.g, clr.b); + + // Attach fields + m_pointSet.getPointData().addArray(vtkDataArray.newInstance({name: 'color', values: m_colorArray, numberOfComponents: 4})); + m_pointSet.getPointData().addArray(vtkDataArray.newInstance({name: 'diam', values: m_diamArray})); + m_this.buildTime().modified(); }; @@ -127,10 +152,14 @@ var vtkjs_pointFeature = function (arg) { m_this._build(); } else { var data = m_this.data(), - radFunc = m_this.style.get('radius'), - rad = radFunc(data[0], 0); - rad *= m_this.layer().map().unitsPerPixel(m_this.layer().map().zoom()); - m_source.setRadius(rad); + radFunc = m_this.style.get('radius'); + + const scalingFactor = m_this.layer().map().unitsPerPixel(m_this.layer().map().zoom()); + const dataArray = m_pointSet.getPointData().getArray('diam'); + const newScaleArray = dataArray.getData().map((v, i) => radFunc(data[i], i) * scalingFactor * 2); + + dataArray.setData(newScaleArray); + m_pointSet.modified(); } m_this.updateTime().modified(); @@ -150,7 +179,10 @@ var vtkjs_pointFeature = function (arg) { inherit(vtkjs_pointFeature, pointFeature); +var capabilities = {}; +capabilities[pointFeature.capabilities.stroke] = false; + // Now register it -registerFeature('vtkjs', 'point', vtkjs_pointFeature); +registerFeature('vtkjs', 'point', vtkjs_pointFeature, capabilities); module.exports = vtkjs_pointFeature; diff --git a/src/vtkjs/vtkjsRenderer.js b/src/vtkjs/vtkjsRenderer.js index e8ba9cfab7..2d683679e8 100644 --- a/src/vtkjs/vtkjsRenderer.js +++ b/src/vtkjs/vtkjsRenderer.js @@ -29,8 +29,6 @@ var vtkjsRenderer = function (arg) { var vtkGenericRenderWindow = vtkjs.Rendering.Misc.vtkGenericRenderWindow; var m_this = this, - m_width = 0, - m_height = 0, s_init = this._init; var vtkRenderer = vtkGenericRenderWindow.newInstance({ @@ -42,24 +40,6 @@ var vtkjsRenderer = function (arg) { var vtkjsren = vtkRenderer.getRenderer(); var renderWindow = vtkRenderer.getRenderWindow(); - /** - * Return width of the renderer. - * - * @returns {number} The width of the current canvas. - */ - this.width = function () { - return m_width; - }; - - /** - * Return height of the renderer. - * - * @returns {number} The height of the current canvas. - */ - this.height = function () { - return m_height; - }; - /** * Get context specific renderer. * @@ -110,6 +90,7 @@ var vtkjsRenderer = function (arg) { * @returns {this} */ this._resize = function (x, y, w, h) { + m_this._setWidthHeight(w, h); vtkRenderer.resize(); m_this._render(); return m_this; diff --git a/tests/gl-cases/vtkjsPointFeature.js b/tests/gl-cases/vtkjsPointFeature.js index 57320142e1..405ad058d8 100644 --- a/tests/gl-cases/vtkjsPointFeature.js +++ b/tests/gl-cases/vtkjsPointFeature.js @@ -27,7 +27,10 @@ describe('geo.vtkjs.pointFeature', function () { } } }).data(testPoints); + sinon.spy(point, '_update'); point.draw(); + expect(point._update.calledOnce).toBe(true); + point._update.restore(); expect($('#map div canvas').length).toBe(1); }); waitForIt('points to be generated', function () {