]> arthur.barton.de Git - netdata.git/commitdiff
new zoom functionality for dygraphs - now it happens first on the client and then...
authorCosta Tsaousis (ktsaou) <costa@tsaousis.gr>
Thu, 17 Dec 2015 02:26:09 +0000 (04:26 +0200)
committerCosta Tsaousis (ktsaou) <costa@tsaousis.gr>
Thu, 17 Dec 2015 02:26:09 +0000 (04:26 +0200)
web/dashboard.js

index a58de6f9bbeb00f2d039430b60eb95d16a575df0..8cccbdbe15be1f8b331427f22ccbdb0263e173b9 100755 (executable)
        chartState.prototype.globalSelectionSyncAbility = function() {
                if(NETDATA.options.current.sync_selection === false)
                        return false;
-               
+
                if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime()) return false;
                return true;
        }
                        this.log('mode set to ' + this.current.name);
        }
 
-       chartState.prototype._minPanOrZoomStep = function() {
-               return (((this.current.before_ms - this.current.after_ms) / this.current.points) * ((this.current.points * 5 / 100) + 1) );
-               // return this.current.view_update_every * 10;
-       }
+       chartState.prototype.updateChartPanOrZoom = function(after, before) {
+               if(before < after) return false;
 
-       chartState.prototype._shouldBeMoved = function(old_after, old_before, new_after, new_before) {
-               var dt_after = Math.abs(old_after - new_after);
-               var dt_before = Math.abs(old_before - new_before);
-               var old_range = old_before - old_after;
+               var min_duration = Math.round((this.chartWidth() / 30 * this.chart.update_every * 1000));
 
-               var new_range = new_before - new_after;
-               var dt = Math.abs(old_range - new_range);
-               var step = Math.max(dt_after, dt_before, dt);
+               if(this.debug === true)
+                       this.log('requested duration of ' + ((before - after) / 1000).toString() + ' (' + after + ' - ' + before + '), minimum ' + min_duration / 1000);
 
-               var min_step = this._minPanOrZoomStep();
-               if(new_range < old_range && new_range / this.chartWidth() < 100) {
-                       if(this.debug === true)
-                               this.log('_shouldBeMoved(' + (new_after / 1000).toString() + ' - ' + (new_before / 1000).toString() + '): minimum point size: 0.10, wanted point size: ' + (new_range / this.chartWidth() / 1000).toString() + ': TOO SMALL RANGE');
+               if((before - after) < min_duration) return false;
 
-                       return false;
-               }
+               var current_duration = this.current.before_ms - this.current.after_ms;
+               var wanted_duration = before - after;
+               var tolerance = this.current.view_update_every * 2;
+               var movement = Math.abs(before - this.current.before_ms);
 
-               if(step >= min_step) {
-                       if(this.debug === true)
-                               this.log('_shouldBeMoved(' + (new_after / 1000).toString() + ' - ' + (new_before / 1000).toString() + '): minimum step: ' + (min_step / 1000).toString() + ', this step: ' + (step / 1000).toString() + ': YES');
+               if(this.debug === true)
+                       this.log('current duration: ' + current_duration / 1000 + ', wanted duration: ' + wanted_duration / 1000 + ', movement: ' + movement / 1000 + ', tolerance: ' + tolerance / 1000);
 
-                       return true;
-               }
-               else {
+               if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance) {
                        if(this.debug === true)
-                               this.log('_shouldBeMoved(' + (new_after / 1000).toString() + ' - ' + (new_before / 1000).toString() + '): minimum step: ' + (min_step / 1000).toString() + ', this step: ' + (step / 1000).toString() + ': NO');
+                               this.log('IGNORED');
 
                        return false;
                }
-       }
-
-       chartState.prototype.updateChartPanOrZoom = function(after, before) {
-               var move = false;
 
                if(this.current.name === 'auto') {
-                       if(this.debug === true)
-                               this.log('updateChartPanOrZoom(): caller did not set proper mode');
-
                        this.setMode('pan');
-               }
-
-               if(this.current.force_after_ms === null || this.current.force_before_ms === null) {
-                       if(this.debug === true)
-                               this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): INIT');
 
-                       move = true;
-               }
-               else if(this._shouldBeMoved(this.current.force_after_ms, this.current.force_before_ms, after, before) && this._shouldBeMoved(this.current.after_ms, this.current.before_ms, after, before)) {
-                       if(this.debug === true)
-                               this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): FORCE CHANGE from ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
-
-                       move = true;
-               }
-               else if(this._shouldBeMoved(this.current.requested_after_ms, this.current.requested_before_ms, after, before) && this._shouldBeMoved(this.current.after_ms, this.current.before_ms, after, before)) {
                        if(this.debug === true)
-                               this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): REQUESTED CHANGE from ' + (this.current.requested_after_ms / 1000).toString() + ' - ' + (this.current.requested_before_ms / 1000).toString());
-
-                       move = true;
-               }
-
-               if(move) {
-                       this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
-                       this.current.force_after_ms = after;
-                       this.current.force_before_ms = before;
-                       NETDATA.globalPanAndZoom.setMaster(this, after, before);
-                       return true;
+                               this.log('updateChartPanOrZoom(): caller did not set proper mode');
                }
 
-               if(this.debug === true)
-                       this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): IGNORE');
-
-               return false;
+               this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
+               this.current.force_after_ms = after;
+               this.current.force_before_ms = before;
+               NETDATA.globalPanAndZoom.setMaster(this, after, before);
+               return true;
        }
 
        chartState.prototype.legendFormatValue = function(value) {
 
                        this.log('STATUS: requested: ' + (this.current.requested_after_ms / 1000).toString() + ' - ' + (this.current.requested_before_ms / 1000).toString());
                        this.log('STATUS: rendered : ' + (this.current.after_ms / 1000).toString() + ' - ' + (this.current.before_ms / 1000).toString());
-                       this.log('STATUS: points   : ' + (this.current.points).toString() + ', min step: ' + (this._minPanOrZoomStep() / 1000).toString());
+                       this.log('STATUS: points   : ' + (this.current.points).toString());
                }
 
                if(data.points === 0) {
                                        if(NETDATA.options.debug.dygraph === true)
                                                state.log('dygraphDrawCallback()');
 
+                                       var first = state.current.data.first_entry_t * 1000;
+                                       var last = state.current.data.last_entry_t * 1000;
+
                                        var x_range = dygraph.xAxisRange();
                                        var after = Math.round(x_range[0]);
                                        var before = Math.round(x_range[1]);
 
-                                       state.updateChartPanOrZoom(after, before);
+                                       if(before <= last && after >= first)
+                                               state.updateChartPanOrZoom(after, before);
                                }
                        },
                        zoomCallback: function(minDate, maxDate, yRanges) {
 
                                state.globalSelectionSyncStop();
                                state.globalSelectionSyncDelay();
-                               state.updateChartPanOrZoom(minDate, maxDate);
+
+                               if(state.updateChartPanOrZoom(minDate, maxDate) == false) {
+                                       // we should not zoom that much
+                                       state.dygraph_instance.updateOptions({
+                                               dateWindow: null,
+                                               valueRange: null
+                                       });
+                               }
                        },
                        highlightCallback: function(event, x, points, row, seriesName) {
                                if(NETDATA.options.debug.dygraph === true || state.debug === true)
                                click: function(event, dygraph, context) {
                                        if(NETDATA.options.debug.dygraph === true || state.debug === true)
                                                state.log('interactionModel.click()');
-                                       /*Dygraph.cancelEvent(event);*/
+
+                                       event.preventDefault();
                                },
                                dblclick: function(event, dygraph, context) {
                                        if(NETDATA.options.debug.dygraph === true || state.debug === true)
                                        if(NETDATA.options.debug.dygraph === true || state.debug === true)
                                                state.log('interactionModel.mousewheel()');
 
+                                       // Take the offset of a mouse event on the dygraph canvas and
+                                       // convert it to a pair of percentages from the bottom left. 
+                                       // (Not top left, bottom is where the lower value is.)
+                                       function offsetToPercentage(g, offsetX, offsetY) {
+                                               // This is calculating the pixel offset of the leftmost date.
+                                               var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
+                                               var yar0 = g.yAxisRange(0);
+
+                                               // This is calculating the pixel of the higest value. (Top pixel)
+                                               var yOffset = g.toDomCoords(null, yar0[1])[1];
+
+                                               // x y w and h are relative to the corner of the drawing area,
+                                               // so that the upper corner of the drawing area is (0, 0).
+                                               var x = offsetX - xOffset;
+                                               var y = offsetY - yOffset;
+
+                                               // This is computing the rightmost pixel, effectively defining the
+                                               // width.
+                                               var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
+
+                                               // This is computing the lowest pixel, effectively defining the height.
+                                               var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
+
+                                               // Percentage from the left.
+                                               var xPct = w === 0 ? 0 : (x / w);
+                                               // Percentage from the top.
+                                               var yPct = h === 0 ? 0 : (y / h);
+
+                                               // The (1-) part below changes it from "% distance down from the top"
+                                               // to "% distance up from the bottom".
+                                               return [xPct, (1-yPct)];
+                                       }
+
+                                       // Adjusts [x, y] toward each other by zoomInPercentage%
+                                       // Split it so the left/bottom axis gets xBias/yBias of that change and
+                                       // tight/top gets (1-xBias)/(1-yBias) of that change.
+                                       //
+                                       // If a bias is missing it splits it down the middle.
+                                       function zoomRange(g, zoomInPercentage, xBias, yBias) {
+                                               xBias = xBias || 0.5;
+                                               yBias = yBias || 0.5;
+
+                                               function adjustAxis(axis, zoomInPercentage, bias) {
+                                                       var delta = axis[1] - axis[0];
+                                                       var increment = delta * zoomInPercentage;
+                                                       var foo = [increment * bias, increment * (1-bias)];
+
+                                                       return [ axis[0] + foo[0], axis[1] - foo[1] ];
+                                               }
+
+                                               var yAxes = g.yAxisRanges();
+                                               var newYAxes = [];
+                                               for (var i = 0; i < yAxes.length; i++) {
+                                                       newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
+                                               }
+
+                                               return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
+                                       }
+
                                        if(event.altKey || event.shiftKey) {
                                                state.globalSelectionSyncStop();
                                                state.globalSelectionSyncDelay();
 
                                                // http://dygraphs.com/gallery/interaction-api.js
                                                var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
-                                               var percentage = normal / 10;
+                                               var percentage = normal / 50;
+
+                                               if (!(event.offsetX && event.offsetY)){
+                                                       event.offsetX = event.layerX - event.target.offsetLeft;
+                                                       event.offsetY = event.layerY - event.target.offsetTop;
+                                               }
+
+                                               var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
+                                               var xPct = percentages[0];
+                                               var yPct = percentages[1];
 
-                                               var before_old = state.current.before_ms;
-                                               var after_old = state.current.after_ms;
-                                               var range_old = before_old - after_old;
+                                               var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
 
-                                               var range = range_old * ( 1 - percentage );
-                                               var dt = Math.round((range_old - range) / 2);
+                                               var after = new_x_range[0];
+                                               var before = new_x_range[1];
 
-                                               var before = before_old - dt;
-                                               var after  = after_old  + dt;
+                                               var first = (state.current.data.first_entry_t + state.current.data.view_update_every) * 1000;
+                                               var last = (state.current.data.last_entry_t + state.current.data.view_update_every) * 1000;
 
-                                               if(NETDATA.options.debug.dygraph === true)
-                                                       state.log('percent: ' + percentage + ' from ' + after_old + ' - ' + before_old + ' to ' + after + ' - ' + before + ', range from ' + (before_old - after_old).toString() + ' to ' + (before - after).toString());
+                                               if(before > last) {
+                                                       after -= (before - last);
+                                                       before = last;
+                                               }
+                                               if(after < first) {
+                                                       after = first;
+                                               }
 
                                                state.setMode('zoom');
-                                               state.updateChartPanOrZoom(after, before);
-                                       }                                       
+                                               if(state.updateChartPanOrZoom(after, before) === true)
+                                                       dygraph.updateOptions({ dateWindow: [ after, before ] });
+
+                                               event.preventDefault();
+                                       }
                                },
                                touchstart: function(event, dygraph, context) {
                                        if(NETDATA.options.debug.dygraph === true || state.debug === true)