From d9cdcb82f1621a6d89ab2954321a0c14214f8cdc Mon Sep 17 00:00:00 2001 From: Ephraim Berkovitch Date: Sat, 31 Dec 2016 16:35:59 +0200 Subject: [PATCH] feat: #2451 Allow pass the color of points in 'dot-color' mode of Graph3D (#2489) * Allow pass the color of points in 'dot-color' mode of Graph3D explicitly - clear the source code and finalize * Add an onclick callback in Graph3D, to enable click and display customer user-defined information for single data point * Extra data type validation for string conversion functions in Setings * Disable onclick callback immediately after rotate/pan * Re-format the JavaScript file * No onclick event set by user - properly treat this case now * Further reformat - indentation * Handle the case where there is no closest data point --- examples/graph3d/07_dot_cloud_colors.html | 8 +- lib/graph3d/Graph3d.js | 103 +++++++++++++++------- lib/graph3d/Settings.js | 6 +- 3 files changed, 80 insertions(+), 37 deletions(-) diff --git a/examples/graph3d/07_dot_cloud_colors.html b/examples/graph3d/07_dot_cloud_colors.html index 07cab6de..44d554fc 100644 --- a/examples/graph3d/07_dot_cloud_colors.html +++ b/examples/graph3d/07_dot_cloud_colors.html @@ -13,6 +13,9 @@ var data = null; var graph = null; + function onclick(point) { + console.log(point); + } // Called when the Visualization API is loaded. function drawVisualization() { @@ -30,9 +33,9 @@ var x = pow(random(), 2); var y = pow(random(), 2); var z = pow(random(), 2); - var dist = sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)); + var style = (i%2==0) ? sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)) : "#00ffff"; - data.add({x:x,y:y,z:z,style:dist}); + data.add({x:x,y:y,z:z,style:style}); } // specify options @@ -45,6 +48,7 @@ keepAspectRatio: true, verticalRatio: 1.0, legendLabel: 'distance', + onclick: onclick, cameraPosition: { horizontal: -0.35, vertical: 0.22, diff --git a/lib/graph3d/Graph3d.js b/lib/graph3d/Graph3d.js index 61623687..df2fd885 100644 --- a/lib/graph3d/Graph3d.js +++ b/lib/graph3d/Graph3d.js @@ -693,6 +693,7 @@ Graph3d.prototype.create = function () { var ontouchstart = function (event) {me._onTouchStart(event);}; var onmousewheel = function (event) {me._onWheel(event);}; var ontooltip = function (event) {me._onTooltip(event);}; + var onclick = function(event) {me._onClick(event);}; // TODO: these events are never cleaned up... can give a 'memory leakage' util.addEventListener(this.frame.canvas, 'keydown', onkeydown); @@ -700,6 +701,7 @@ Graph3d.prototype.create = function () { util.addEventListener(this.frame.canvas, 'touchstart', ontouchstart); util.addEventListener(this.frame.canvas, 'mousewheel', onmousewheel); util.addEventListener(this.frame.canvas, 'mousemove', ontooltip); + util.addEventListener(this.frame.canvas, 'click', onclick); // add the new graph to the container element this.containerElement.appendChild(this.frame); @@ -956,52 +958,52 @@ Graph3d.prototype._dotSize = function() { /** - * Get legend width + * Get legend width */ Graph3d.prototype._getLegendWidth = function() { - var width; + var width; if (this.style === Graph3d.STYLE.DOTSIZE) { var dotSize = this._dotSize(); - width = dotSize / 2 + dotSize * 2; + width = dotSize / 2 + dotSize * 2; } else if (this.style === Graph3d.STYLE.BARSIZE) { width = this.xBarWidth ; } else { - width = 20; + width = 20; } return width; } /** - * Redraw the legend based on size, dot color, or surface height + * Redraw the legend based on size, dot color, or surface height */ Graph3d.prototype._redrawLegend = function() { - - //Return without drawing anything, if no legend is specified + + //Return without drawing anything, if no legend is specified if (this.showLegend !== true) { return; - } + } // Do not draw legend when graph style does not support if (this.style === Graph3d.STYLE.LINE - || this.style === Graph3d.STYLE.BARSIZE //TODO add legend support for BARSIZE + || this.style === Graph3d.STYLE.BARSIZE //TODO add legend support for BARSIZE ){ return; - } + } - // Legend types - size and color. Determine if size legend. - var isSizeLegend = (this.style === Graph3d.STYLE.BARSIZE + // Legend types - size and color. Determine if size legend. + var isSizeLegend = (this.style === Graph3d.STYLE.BARSIZE || this.style === Graph3d.STYLE.DOTSIZE) ; - // Legend is either tracking z values or style values. This flag if false means use z values. - var isValueLegend = (this.style === Graph3d.STYLE.DOTSIZE - || this.style === Graph3d.STYLE.DOTCOLOR + // Legend is either tracking z values or style values. This flag if false means use z values. + var isValueLegend = (this.style === Graph3d.STYLE.DOTSIZE + || this.style === Graph3d.STYLE.DOTCOLOR || this.style === Graph3d.STYLE.BARCOLOR); var height = Math.max(this.frame.clientHeight * 0.25, 100); var top = this.margin; - var width = this._getLegendWidth() ; // px - overwritten by size legend + var width = this._getLegendWidth() ; // px - overwritten by size legend var right = this.frame.clientWidth - this.margin; var left = right - width; var bottom = top + height; @@ -1031,14 +1033,14 @@ Graph3d.prototype._redrawLegend = function() { ctx.strokeRect(left, top, width, height); } else { - - // draw the size legend box + + // draw the size legend box var widthMin; - if (this.style === Graph3d.STYLE.DOTSIZE) { + if (this.style === Graph3d.STYLE.DOTSIZE) { var dotSize = this._dotSize(); widthMin = dotSize / 2; // px - } else if (this.style === Graph3d.STYLE.BARSIZE) { - //widthMin = this.xBarWidth * 0.2 this is wrong - barwidth measures in terms of xvalues + } else if (this.style === Graph3d.STYLE.BARSIZE) { + //widthMin = this.xBarWidth * 0.2 this is wrong - barwidth measures in terms of xvalues } ctx.strokeStyle = this.axisColor; ctx.fillStyle = this.dataColor.fill; @@ -1052,10 +1054,10 @@ Graph3d.prototype._redrawLegend = function() { ctx.stroke(); } - // print value text along the legend edge + // print value text along the legend edge var gridLineLen = 5; // px - - var legendMin = isValueLegend ? this.valueRange.min : this.zRange.min; + + var legendMin = isValueLegend ? this.valueRange.min : this.zRange.min; var legendMax = isValueLegend ? this.valueRange.max : this.zRange.max; var step = new StepNumber(legendMin, legendMax, (legendMax-legendMin)/5, true); step.start(true); @@ -1330,7 +1332,7 @@ Graph3d.prototype._redrawAxis = function() { xText = (armVector.y > 0) ? xRange.min : xRange.max; point3d = new Point3d(xText, y, zRange.min); - var msg = ' ' + this.yValueLabel(y) + ' '; + var msg = ' ' + this.yValueLabel(y) + ' '; this.drawAxisLabelY(ctx, point3d, msg, armAngle, textMargin); step.next(); @@ -1550,7 +1552,7 @@ Graph3d.prototype._redrawBar = function(ctx, point, xWidth, yWidth, color, borde /** * Draw a polygon using the passed points and fill it with the passed style and stroke. * - * @param points an array of points. + * @param points an array of points. * @param fillStyle optional; the fill style to set * @param strokeStyle optional; the stroke style to set */ @@ -1614,16 +1616,28 @@ Graph3d.prototype._getColorsRegular = function(point) { /** * Get the colors for the 'color' graph styles. * These styles are currently: 'bar-color' and 'dot-color' + * Color may be set as a string representation of HTML color, like #ff00ff, + * or calculated from a number, for example, distance from this point + * The first option is useful when we have some pre-given legend, to which we have to adjust ourselves + * The second option is useful when we are interested in automatically setting the color, from some value, + * using some color scale */ Graph3d.prototype._getColorsColor = function(point) { // calculate the color based on the value - var hue = (1 - (point.point.value - this.valueRange.min) * this.scale.value) * 240; - var color = this._hsv2rgb(hue, 1, 1); - var borderColor = this._hsv2rgb(hue, 1, 0.8); + var color, borderColor; + if (typeof point.point.value === "string") { + color = point.point.value; + borderColor = point.point.value; + } + else { + var hue = (1 - (point.point.value - this.valueRange.min) * this.scale.value) * 240; + color = this._hsv2rgb(hue, 1, 1); + borderColor = this._hsv2rgb(hue, 1, 0.8); + } return { fill : color, - border : borderColor + border : borderColor }; }; @@ -1897,7 +1911,7 @@ Graph3d.prototype._storeMousePosition = function(event) { // get mouse position (different code for IE and all other browsers) this.startMouseX = getMouseX(event); this.startMouseY = getMouseY(event); - + this._startCameraOffset = this.camera.getOffset(); }; @@ -1946,12 +1960,13 @@ Graph3d.prototype._onMouseDown = function(event) { * @param {Event} event Well, eehh, the event */ Graph3d.prototype._onMouseMove = function (event) { + this.moving = true; event = event || window.event; // calculate change in mouse position var diffX = parseFloat(getMouseX(event)) - this.startMouseX; var diffY = parseFloat(getMouseY(event)) - this.startMouseY; - + // move with ctrl or rotate by other if (event && event.ctrlKey === true) { // calculate change in mouse position @@ -1960,7 +1975,7 @@ Graph3d.prototype._onMouseMove = function (event) { var offXNew = (this._startCameraOffset.x || 0) - ((diffX / scaleX) * this.camera.armLength) * 0.8; var offYNew = (this._startCameraOffset.y || 0) + ((diffY / scaleY) * this.camera.armLength) * 0.8; - + this.camera.setOffset(offXNew, offYNew); this._storeMousePosition(event); } else { @@ -2014,6 +2029,26 @@ Graph3d.prototype._onMouseUp = function (event) { util.preventDefault(event); }; +/** + * @param {event} event The event + */ +Graph3d.prototype._onClick = function (event) { + if (!this.onclick_callback) + return; + if (!this.moving) { + var boundingRect = this.frame.getBoundingClientRect(); + var mouseX = getMouseX(event) - boundingRect.left; + var mouseY = getMouseY(event) - boundingRect.top; + var dataPoint = this._dataPointFromXY(mouseX, mouseY); + if (dataPoint) + this.onclick_callback(dataPoint.point.data); + } + else { // disable onclick callback, if it came immediately after rotate/pan + this.moving = false; + } + util.preventDefault(event); +}; + /** * After having moved the mouse, a tooltip should pop up when the mouse is resting on a data point * @param {Event} event A mouse move event @@ -2403,4 +2438,4 @@ Graph3d.prototype.setSize = function(width, height) { // ----------------------------------------------------------------------------- -module.exports = Graph3d; +module.exports = Graph3d; \ No newline at end of file diff --git a/lib/graph3d/Settings.js b/lib/graph3d/Settings.js index 7a702580..edb41538 100644 --- a/lib/graph3d/Settings.js +++ b/lib/graph3d/Settings.js @@ -119,7 +119,7 @@ function isEmpty(obj) { * Source: http://stackoverflow.com/a/1026087 */ function capitalize(str) { - if (str === undefined || str === "") { + if (str === undefined || str === "" || typeof str != "string") { return str; } @@ -221,6 +221,7 @@ function setDefaults(src, dst) { dst.margin = 10; // px dst.showGrayBottom = false; // TODO: this does not work correctly dst.showTooltip = false; + dst.onclick_callback = null; dst.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window? } @@ -267,6 +268,9 @@ function setSpecialSettings(src, dst) { if (src.tooltip !== undefined) { dst.showTooltip = src.tooltip; } + if (src.onclick != undefined) { + dst.onclick_callback = src.onclick; + } }