diff --git a/src/dat/controllers/PlotterController.js b/src/dat/controllers/PlotterController.js new file mode 100644 index 00000000..4735b16b --- /dev/null +++ b/src/dat/controllers/PlotterController.js @@ -0,0 +1,64 @@ +/** + * dat-gui JavaScript Controller Library + * http://code.google.com/p/dat-gui + * + * Copyright 2011 Data Arts Team, Google Creative Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +import Controller from './Controller'; +import Plotter from '../utils/plotter'; + +/** + * @class Provides a canvas that graphically displays the value of the object property at the specified interval + * + * @extends dat.controllers.Controller + * + * @param {Object} object The object to be manipulated + * @param {string} property The name of the property to be manipulated + * @param {Object} params Contains the max and period properties + */ +class PlotterController extends Controller { + constructor(object, property, params) { + super(object, property); + + /** The graph will be these many units high */ + this.max = params.max; + + /** Refresh rate. Value of 0 disables auto-refresh */ + this.period = params.period; + + /** Stores the current value for comparison during animation frame */ + this.prevValue = this.getValue(); + + /** Allows acurate timing for the period to be checked during animation frame */ + this.lastUpdate = Date.now(); + + this.__panel = new Plotter(params.fgColor, params.bgColor, params.type); + this.domElement.appendChild(this.__panel.dom); + } + + updateDisplay() { + const value = this.getValue(); + if (this.period < 1 && value !== this.prevValue) { + /* Update only on value change when auto-refresh is off */ + this.__panel.update(value, this.max); + } else if ((Date.now() - this.lastUpdate) > this.period) { + /* Update if elapsed time since last update is greater than the period */ + this.__panel.update(value, this.max); + this.lastUpdate = Date.now() * 2 - this.lastUpdate - this.period; + } + + this.prevValue = value; + + return super.updateDisplay(); + } + +} + +export default PlotterController; diff --git a/src/dat/gui/GUI.js b/src/dat/gui/GUI.js index 972c3073..2e1a2ff8 100644 --- a/src/dat/gui/GUI.js +++ b/src/dat/gui/GUI.js @@ -20,6 +20,7 @@ import FunctionController from '../controllers/FunctionController'; import NumberControllerBox from '../controllers/NumberControllerBox'; import NumberControllerSlider from '../controllers/NumberControllerSlider'; import ColorController from '../controllers/ColorController'; +import PlotterController from '../controllers/PlotterController'; import requestAnimationFrame from '../utils/requestAnimationFrame'; import CenteredDiv from '../dom/CenteredDiv'; import dom from '../dom/dom'; @@ -556,6 +557,42 @@ common.extend( ); }, + /** + * Adds a new plotter controller to the GUI. + * + * @param object + * @param property + * @param max The maximum value that the plotter will display (default 10) + * @param period The update interval in ms or use 0 to only update on value change (default 500) + * @param type Type of graph to render - line or bar (default line) + * @param fgColor Foreground color of the graph in hex (default #fff) + * @param bgColor Background color of the graph in hex (default #000) + * @returns {Controller} The controller that was added to the GUI. + * @instance + * + * @example + * var obj = { + * value: 5 + * }; + * gui.addPlotter(obj, 'value', 10, 100); + * gui.addPlotter(obj, 'value', 10, 0); + */ + addPlotter: function(object, property, max, period, type, fgColor, bgColor) { + return add( + this, + object, + property, + { + plotter: true, + max: max || 10, + period: (typeof period === 'number') ? period : 500, + type: type || 'line', + fgColor: fgColor || '#fff', + bgColor: bgColor || '#000' + } + ); + }, + /** * Removes the given controller from the GUI. * @param {Controller} controller @@ -1139,6 +1176,9 @@ function add(gui, object, property, params) { if (params.color) { controller = new ColorController(object, property); + } else if (params.plotter) { + controller = new PlotterController(object, property, params); + gui.listen(controller); } else { const factoryArgs = [object, property].concat(params.factoryArgs); controller = ControllerFactory.apply(gui, factoryArgs); @@ -1165,6 +1205,8 @@ function add(gui, object, property, params) { dom.addClass(li, GUI.CLASS_CONTROLLER_ROW); if (controller instanceof ColorController) { dom.addClass(li, 'color'); + } else if (controller instanceof PlotterController) { + dom.addClass(li, 'plotter'); } else { dom.addClass(li, typeof controller.getValue()); } diff --git a/src/dat/gui/_structure.scss b/src/dat/gui/_structure.scss index 3193f8e0..ba430e78 100644 --- a/src/dat/gui/_structure.scss +++ b/src/dat/gui/_structure.scss @@ -180,7 +180,7 @@ $button-height: 20px; } /** Controller placement */ - .c input[type=text] { + .c input[type=text], .c canvas { border: 0; margin-top: 4px; padding: 3px; @@ -195,6 +195,12 @@ $button-height: 20px; margin-left: 0; } + /** Reduce margin and set height for plotter */ + .c canvas { + padding: 0; + height: 53px; + } + .slider { float: left; width: 66%; diff --git a/src/dat/gui/style.scss b/src/dat/gui/style.scss index 89705cd4..3d5f0248 100644 --- a/src/dat/gui/style.scss +++ b/src/dat/gui/style.scss @@ -138,6 +138,10 @@ $input-color: lighten($background-color, 8.5%); border-left: 3px solid; } + &.plotter { + height: 58px; + } + &.function { border-left: 3px solid $function-color; } diff --git a/src/dat/utils/plotter.js b/src/dat/utils/plotter.js new file mode 100644 index 00000000..f2a09e71 --- /dev/null +++ b/src/dat/utils/plotter.js @@ -0,0 +1,112 @@ +/** + * dat-gui JavaScript Controller Library + * http://code.google.com/p/dat-gui + * + * Copyright 2011 Data Arts Team, Google Creative Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +/** + * @author mrdoob / http://mrdoob.com/ + * Original code from stats.js r17: https://github.com/mrdoob/stats.js + * Modified by MacroMan + * Licence from that project: + + The MIT License + +Copyright (c) 2009-2016 stats.js authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +const plotter = function(fg, bg, type) { + let min = Infinity; + let max = 0; + const round = Math.round; + const PR = round(window.devicePixelRatio || 1); + + const WIDTH = 160 * PR; + const HEIGHT = 60 * PR; + const GRAPH_X = 3 * PR; + const GRAPH_Y = 3 * PR; + const GRAPH_WIDTH = 154 * PR; + const GRAPH_HEIGHT = 54 * PR; + + const canvas = document.createElement('canvas'); + canvas.width = WIDTH; + canvas.height = HEIGHT; + + const context = canvas.getContext('2d'); + context.font = 'bold ' + (9 * PR) + 'px Helvetica,Arial,sans-serif'; + context.textBaseline = 'top'; + + context.fillStyle = bg; + context.fillRect(0, 0, WIDTH, HEIGHT); + + context.fillStyle = fg; + context.fillRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT); + + context.fillStyle = bg; + context.globalAlpha = 0.9; + context.fillRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT); + + return { + dom: canvas, + update: function (value, maxValue) { + min = Math.min(min, value); + max = Math.max(max, value); + + context.globalAlpha = 1; + context.fillStyle = fg; + + // Move graph over 1px + context.drawImage(canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT); + + // Draw fg color + context.fillRect(GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT); + + context.fillStyle = bg; + context.globalAlpha = 0.9; + + // Blank out above the value + context.fillRect( + GRAPH_X + GRAPH_WIDTH - PR, + GRAPH_Y, + PR, + round((1 - (value / maxValue)) * GRAPH_HEIGHT) + ); + + // Blank out below the value if line + if (type === 'line') { + context.fillRect( + GRAPH_X + GRAPH_WIDTH - PR, + round((1 - (value / maxValue)) * GRAPH_HEIGHT) + PR + 3, + PR, + round((value / maxValue) * GRAPH_HEIGHT) + ); + } + } + }; +}; + +export default plotter;