Преглед изворни кода

feat(dashed lines): Implementing dashed lines

Adding support for dashed lines using jquery.flot.dashes.js
smalik пре 9 година
родитељ
комит
1a3bc60e69

+ 14 - 1
public/app/core/time_series2.ts

@@ -35,6 +35,7 @@ export default class TimeSeries {
   isOutsideRange: boolean;
 
   lines: any;
+  dashes: any;
   bars: any;
   points: any;
   yaxis: any;
@@ -61,6 +62,9 @@ export default class TimeSeries {
 
   applySeriesOverrides(overrides) {
     this.lines = {};
+    this.dashes = {
+      dashLength: []
+    };
     this.points = {};
     this.bars = {};
     this.yaxis = 1;
@@ -74,11 +78,20 @@ export default class TimeSeries {
         continue;
       }
       if (override.lines !== void 0) { this.lines.show = override.lines; }
+      if (override.dashes !== void 0) {
+         this.dashes.show = override.dashes;
+         this.lines.lineWidth = 0;
+      }
       if (override.points !== void 0) { this.points.show = override.points; }
       if (override.bars !== void 0) { this.bars.show = override.bars; }
       if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); }
       if (override.stack !== void 0) { this.stack = override.stack; }
-      if (override.linewidth !== void 0) { this.lines.lineWidth = override.linewidth; }
+      if (override.linewidth !== void 0) {
+         this.lines.lineWidth = override.linewidth;
+         this.dashes.lineWidth = override.linewidth;
+      }
+      if (override.dashLength !== void 0) { this.dashes.dashLength[0] = override.dashLength; }
+      if (override.spaceLength !== void 0) { this.dashes.dashLength[1] = override.spaceLength; }
       if (override.nullPointMode !== void 0) { this.nullPointMode = override.nullPointMode; }
       if (override.pointradius !== void 0) { this.points.radius = override.pointradius; }
       if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; }

+ 10 - 1
public/app/plugins/panel/graph/graph.ts

@@ -7,6 +7,7 @@ import 'jquery.flot.stack';
 import 'jquery.flot.stackpercent';
 import 'jquery.flot.fillbelow';
 import 'jquery.flot.crosshair';
+import 'jquery.flot.dashes';
 import './jquery.flot.events';
 
 import $ from 'jquery';
@@ -215,6 +216,9 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
         // give space to alert editing
         thresholdManager.prepare(elem, data);
 
+        // un-check dashes if lines are unchecked
+        panel.dashes = panel.lines ? panel.dashes : false;
+
         var stack = panel.stack ? true : null;
 
         // Populate element
@@ -231,9 +235,14 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
               show: panel.lines,
               zero: false,
               fill: translateFillOption(panel.fill),
-              lineWidth: panel.linewidth,
+              lineWidth: panel.dashes ? 0 : panel.linewidth,
               steps: panel.steppedLine
             },
+            dashes: {
+              show: panel.dashes,
+              lineWidth: panel.linewidth,
+              dashLength: [panel.dashLength, panel.spaceLength]
+            },
             bars: {
               show: panel.bars,
               fill: 1,

+ 6 - 0
public/app/plugins/panel/graph/module.ts

@@ -67,6 +67,12 @@ class GraphCtrl extends MetricsPanelCtrl {
     fill          : 1,
     // line width in pixels
     linewidth     : 1,
+    // show/hide dashed line
+    dashes        : false,
+    // length of a dash
+    dashLength    : 10,
+    // length of space between two dashes
+    spaceLength   : 10,
     // show hide points
     points        : false,
     // point radius in pixels

+ 3 - 0
public/app/plugins/panel/graph/series_overrides_ctrl.js

@@ -100,6 +100,9 @@ define([
     $scope.addOverrideOption('Null point mode', 'nullPointMode', ['connected', 'null', 'null as zero']);
     $scope.addOverrideOption('Fill below to', 'fillBelowTo', $scope.getSeriesNames());
     $scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]);
+    $scope.addOverrideOption('Dashes', 'dashes', [true, false]);
+    $scope.addOverrideOption('Dash Length', 'dashLength', [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]);
+    $scope.addOverrideOption('Dash Space', 'spaceLength', [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]);
     $scope.addOverrideOption('Points', 'points', [true, false]);
     $scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]);
     $scope.addOverrideOption('Stack', 'stack', [true, false, 'A', 'B', 'C', 'D']);

+ 14 - 0
public/app/plugins/panel/graph/specs/graph_specs.ts

@@ -153,6 +153,20 @@ describe('grafanaGraph', function() {
     });
   });
 
+  graphScenario('dashed lines options', function(ctx) {
+    ctx.setup(function(ctrl) {
+      ctrl.panel.lines = true;
+      ctrl.panel.linewidth = 2;
+      ctrl.panel.dashes = true;
+    });
+
+    it('should configure dashed plot with correct options', function() {
+      expect(ctx.plotOptions.series.lines.show).to.be(true);
+      expect(ctx.plotOptions.series.dashes.lineWidth).to.be(2);
+      expect(ctx.plotOptions.series.dashes.show).to.be(true);
+    });
+  });
+
   graphScenario('should use timeStep for barWidth', function(ctx) {
     ctx.setup(function(ctrl, data) {
       ctrl.panel.bars = true;

+ 17 - 1
public/app/plugins/panel/graph/tab_display.html

@@ -32,12 +32,28 @@
 					<select class="gf-form-input" ng-model="ctrl.panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
 				</div>
 			</div>
-			<div class="gf-form" ng-show="ctrl.panel.lines">
+			<div class="gf-form" ng-show="(ctrl.panel.lines || ctrl.panel.dashes)">
 				<label class="gf-form-label width-8">Line Width</label>
 				<div class="gf-form-select-wrapper max-width-5">
 					<select class="gf-form-input" ng-model="ctrl.panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
 				</div>
 			</div>
+			<gf-form-switch class="gf-form" ng-show="ctrl.panel.lines"
+	                               label="Dashes" label-class="width-8"
+	                               checked="ctrl.panel.dashes" on-change="ctrl.render()">
+			</gf-form-switch>
+			<div class="gf-form" ng-show="ctrl.panel.dashes">
+			  <label class="gf-form-label width-8">Dash Length</label>
+			  <div class="gf-form-select-wrapper max-width-5">
+			    <select class="gf-form-input" ng-model="ctrl.panel.dashLength" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]" ng-change="ctrl.render()"></select>
+			  </div>
+			</div>
+			<div class="gf-form" ng-show="ctrl.panel.dashes">
+			  <label class="gf-form-label width-8">Dash Space</label>
+			  <div class="gf-form-select-wrapper max-width-5">
+			    <select class="gf-form-input" ng-model="ctrl.panel.spaceLength" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]" ng-change="ctrl.render()"></select>
+			  </div>
+			</div>
 			<gf-form-switch ng-show="ctrl.panel.lines" class="gf-form" label="Staircase" label-class="width-8" checked="ctrl.panel.steppedLine" on-change="ctrl.render()">
 			</gf-form-switch>
 			<div class="gf-form" ng-show="ctrl.panel.points">

+ 2 - 1
public/app/system.conf.js

@@ -31,7 +31,8 @@ System.config({
     "jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair",
     "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow",
     "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge",
-    "d3": "vendor/d3/d3.js"
+    "d3": "vendor/d3/d3.js",
+    "jquery.flot.dashes": "vendor/flot/jquery.flot.dashes"
   },
 
   packages: {

+ 3 - 0
public/dashboards/default.json

@@ -82,6 +82,9 @@
           "lines": true,
           "fill": 1,
           "linewidth": 2,
+          "dashes": false,
+          "dashLength": 10,
+          "spaceLength": 10,
           "points": false,
           "pointradius": 5,
           "bars": false,

+ 3 - 0
public/dashboards/template_vars.json

@@ -50,6 +50,9 @@
           "lines": true,
           "fill": 1,
           "linewidth": 1,
+          "dashes": false,
+          "dashLength": 10,
+          "spaceLength": 10,
           "points": false,
           "pointradius": 5,
           "bars": false,

+ 1 - 0
public/test/test-main.js

@@ -40,6 +40,7 @@
       "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow",
       "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge",
       "d3": "vendor/d3/d3.js",
+      "jquery.flot.dashes": "vendor/flot/jquery.flot.dashes"
     },
 
     packages: {

+ 236 - 0
public/vendor/flot/jquery.flot.dashes.js

@@ -0,0 +1,236 @@
+/*
+ * jQuery.flot.dashes
+ *
+ * options = {
+ *   series: {
+ *     dashes: {
+ *
+ *       // show
+ *       // default: false
+ *       // Whether to show dashes for the series.
+ *       show: <boolean>,
+ *
+ *       // lineWidth
+ *       // default: 2
+ *       // The width of the dashed line in pixels.
+ *       lineWidth: <number>,
+ *
+ *       // dashLength
+ *       // default: 10
+ *       // Controls the length of the individual dashes and the amount of
+ *       // space between them.
+ *       // If this is a number, the dashes and spaces will have that length.
+ *       // If this is an array, it is read as [ dashLength, spaceLength ]
+ *       dashLength: <number> or <array[2]>
+ *     }
+ *   }
+ * }
+ */
+(function($){
+
+  function init(plot) {
+
+    plot.hooks.processDatapoints.push(function(plot, series, datapoints) {
+
+      if (!series.dashes.show) return;
+
+      plot.hooks.draw.push(function(plot, ctx) {
+
+        var plotOffset = plot.getPlotOffset(),
+          axisx = series.xaxis,
+          axisy = series.yaxis;
+
+        function plotDashes(xoffset, yoffset) {
+
+          var points = datapoints.points,
+            ps = datapoints.pointsize,
+            prevx = null,
+            prevy = null,
+            dashRemainder = 0,
+            dashOn = true,
+            dashOnLength,
+            dashOffLength;
+
+          if (series.dashes.dashLength[0]) {
+            dashOnLength = series.dashes.dashLength[0];
+            if (series.dashes.dashLength[1]) {
+              dashOffLength = series.dashes.dashLength[1];
+            } else {
+              dashOffLength = dashOnLength;
+            }
+          } else {
+            dashOffLength = dashOnLength = series.dashes.dashLength;
+          }
+
+          ctx.beginPath();
+
+          for (var i = ps; i < points.length; i += ps) {
+
+            var x1 = points[i - ps],
+              y1 = points[i - ps + 1],
+              x2 = points[i],
+              y2 = points[i + 1];
+
+            if (x1 == null || x2 == null) continue;
+
+            // clip with ymin
+            if (y1 <= y2 && y1 < axisy.min) {
+              if (y2 < axisy.min) continue;   // line segment is outside
+              // compute new intersection point
+              x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+              y1 = axisy.min;
+            } else if (y2 <= y1 && y2 < axisy.min) {
+              if (y1 < axisy.min) continue;
+              x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+              y2 = axisy.min;
+            }
+
+            // clip with ymax
+            if (y1 >= y2 && y1 > axisy.max) {
+              if (y2 > axisy.max) continue;
+              x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+              y1 = axisy.max;
+            } else if (y2 >= y1 && y2 > axisy.max) {
+              if (y1 > axisy.max) continue;
+              x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+              y2 = axisy.max;
+            }
+
+            // clip with xmin
+            if (x1 <= x2 && x1 < axisx.min) {
+              if (x2 < axisx.min) continue;
+              y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+              x1 = axisx.min;
+            } else if (x2 <= x1 && x2 < axisx.min) {
+              if (x1 < axisx.min) continue;
+              y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+              x2 = axisx.min;
+            }
+
+            // clip with xmax
+            if (x1 >= x2 && x1 > axisx.max) {
+              if (x2 > axisx.max) continue;
+              y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+              x1 = axisx.max;
+            } else if (x2 >= x1 && x2 > axisx.max) {
+              if (x1 > axisx.max) continue;
+              y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+              x2 = axisx.max;
+            }
+
+            if (x1 != prevx || y1 != prevy) {
+              ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
+            }
+
+            var ax1 = axisx.p2c(x1) + xoffset,
+              ay1 = axisy.p2c(y1) + yoffset,
+              ax2 = axisx.p2c(x2) + xoffset,
+              ay2 = axisy.p2c(y2) + yoffset,
+              dashOffset;
+
+            function lineSegmentOffset(segmentLength) {
+
+              var c = Math.sqrt(Math.pow(ax2 - ax1, 2) + Math.pow(ay2 - ay1, 2));
+
+              if (c <= segmentLength) {
+                return {
+                  deltaX: ax2 - ax1,
+                  deltaY: ay2 - ay1,
+                  distance: c,
+                  remainder: segmentLength - c
+                }
+              } else {
+                var xsign = ax2 > ax1 ? 1 : -1,
+                  ysign = ay2 > ay1 ? 1 : -1;
+                return {
+                  deltaX: xsign * Math.sqrt(Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
+                  deltaY: ysign * Math.sqrt(Math.pow(segmentLength, 2) - Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
+                  distance: segmentLength,
+                  remainder: 0
+                };
+              }
+            }
+            //-end lineSegmentOffset
+
+            do {
+
+              dashOffset = lineSegmentOffset(
+                dashRemainder > 0 ? dashRemainder :
+                  dashOn ? dashOnLength : dashOffLength);
+
+              if (dashOffset.deltaX != 0 || dashOffset.deltaY != 0) {
+                if (dashOn) {
+                  ctx.lineTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
+                } else {
+                  ctx.moveTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
+                }
+              }
+
+              dashOn = !dashOn;
+              dashRemainder = dashOffset.remainder;
+              ax1 += dashOffset.deltaX;
+              ay1 += dashOffset.deltaY;
+
+            } while (dashOffset.distance > 0);
+
+            prevx = x2;
+            prevy = y2;
+          }
+
+          ctx.stroke();
+        }
+        //-end plotDashes
+
+        ctx.save();
+        ctx.translate(plotOffset.left, plotOffset.top);
+        ctx.lineJoin = 'round';
+
+        var lw = series.dashes.lineWidth,
+          sw = series.shadowSize;
+
+        // FIXME: consider another form of shadow when filling is turned on
+        if (lw > 0 && sw > 0) {
+          // draw shadow as a thick and thin line with transparency
+          ctx.lineWidth = sw;
+          ctx.strokeStyle = "rgba(0,0,0,0.1)";
+          // position shadow at angle from the mid of line
+          var angle = Math.PI/18;
+          plotDashes(Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2));
+          ctx.lineWidth = sw/2;
+          plotDashes(Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4));
+        }
+
+        ctx.lineWidth = lw;
+        ctx.strokeStyle = series.color;
+
+        if (lw > 0) {
+          plotDashes(0, 0);
+        }
+
+        ctx.restore();
+
+      });
+      //-end draw hook
+
+    });
+    //-end processDatapoints hook
+
+  }
+  //-end init
+
+  $.plot.plugins.push({
+    init: init,
+    options: {
+      series: {
+        dashes: {
+          show: false,
+          lineWidth: 2,
+          dashLength: 10
+        }
+      }
+    },
+    name: 'dashes',
+    version: '0.1'
+  });
+
+})(jQuery)