Commit 1a3bc60e by smalik Committed by Daniel Lee

feat(dashed lines): Implementing dashed lines

Adding support for dashed lines using jquery.flot.dashes.js
parent 9e13c933
...@@ -35,6 +35,7 @@ export default class TimeSeries { ...@@ -35,6 +35,7 @@ export default class TimeSeries {
isOutsideRange: boolean; isOutsideRange: boolean;
lines: any; lines: any;
dashes: any;
bars: any; bars: any;
points: any; points: any;
yaxis: any; yaxis: any;
...@@ -61,6 +62,9 @@ export default class TimeSeries { ...@@ -61,6 +62,9 @@ export default class TimeSeries {
applySeriesOverrides(overrides) { applySeriesOverrides(overrides) {
this.lines = {}; this.lines = {};
this.dashes = {
dashLength: []
};
this.points = {}; this.points = {};
this.bars = {}; this.bars = {};
this.yaxis = 1; this.yaxis = 1;
...@@ -74,11 +78,20 @@ export default class TimeSeries { ...@@ -74,11 +78,20 @@ export default class TimeSeries {
continue; continue;
} }
if (override.lines !== void 0) { this.lines.show = override.lines; } 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.points !== void 0) { this.points.show = override.points; }
if (override.bars !== void 0) { this.bars.show = override.bars; } if (override.bars !== void 0) { this.bars.show = override.bars; }
if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); } if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); }
if (override.stack !== void 0) { this.stack = override.stack; } 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.nullPointMode !== void 0) { this.nullPointMode = override.nullPointMode; }
if (override.pointradius !== void 0) { this.points.radius = override.pointradius; } if (override.pointradius !== void 0) { this.points.radius = override.pointradius; }
if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; } if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; }
......
...@@ -7,6 +7,7 @@ import 'jquery.flot.stack'; ...@@ -7,6 +7,7 @@ import 'jquery.flot.stack';
import 'jquery.flot.stackpercent'; import 'jquery.flot.stackpercent';
import 'jquery.flot.fillbelow'; import 'jquery.flot.fillbelow';
import 'jquery.flot.crosshair'; import 'jquery.flot.crosshair';
import 'jquery.flot.dashes';
import './jquery.flot.events'; import './jquery.flot.events';
import $ from 'jquery'; import $ from 'jquery';
...@@ -215,6 +216,9 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { ...@@ -215,6 +216,9 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
// give space to alert editing // give space to alert editing
thresholdManager.prepare(elem, data); thresholdManager.prepare(elem, data);
// un-check dashes if lines are unchecked
panel.dashes = panel.lines ? panel.dashes : false;
var stack = panel.stack ? true : null; var stack = panel.stack ? true : null;
// Populate element // Populate element
...@@ -231,9 +235,14 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { ...@@ -231,9 +235,14 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
show: panel.lines, show: panel.lines,
zero: false, zero: false,
fill: translateFillOption(panel.fill), fill: translateFillOption(panel.fill),
lineWidth: panel.linewidth, lineWidth: panel.dashes ? 0 : panel.linewidth,
steps: panel.steppedLine steps: panel.steppedLine
}, },
dashes: {
show: panel.dashes,
lineWidth: panel.linewidth,
dashLength: [panel.dashLength, panel.spaceLength]
},
bars: { bars: {
show: panel.bars, show: panel.bars,
fill: 1, fill: 1,
......
...@@ -67,6 +67,12 @@ class GraphCtrl extends MetricsPanelCtrl { ...@@ -67,6 +67,12 @@ class GraphCtrl extends MetricsPanelCtrl {
fill : 1, fill : 1,
// line width in pixels // line width in pixels
linewidth : 1, 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 // show hide points
points : false, points : false,
// point radius in pixels // point radius in pixels
......
...@@ -100,6 +100,9 @@ define([ ...@@ -100,6 +100,9 @@ define([
$scope.addOverrideOption('Null point mode', 'nullPointMode', ['connected', 'null', 'null as zero']); $scope.addOverrideOption('Null point mode', 'nullPointMode', ['connected', 'null', 'null as zero']);
$scope.addOverrideOption('Fill below to', 'fillBelowTo', $scope.getSeriesNames()); $scope.addOverrideOption('Fill below to', 'fillBelowTo', $scope.getSeriesNames());
$scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]); $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', 'points', [true, false]);
$scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]); $scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]);
$scope.addOverrideOption('Stack', 'stack', [true, false, 'A', 'B', 'C', 'D']); $scope.addOverrideOption('Stack', 'stack', [true, false, 'A', 'B', 'C', 'D']);
......
...@@ -153,6 +153,20 @@ describe('grafanaGraph', function() { ...@@ -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) { graphScenario('should use timeStep for barWidth', function(ctx) {
ctx.setup(function(ctrl, data) { ctx.setup(function(ctrl, data) {
ctrl.panel.bars = true; ctrl.panel.bars = true;
......
...@@ -32,12 +32,28 @@ ...@@ -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> <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> </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> <label class="gf-form-label width-8">Line Width</label>
<div class="gf-form-select-wrapper max-width-5"> <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> <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>
</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 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> </gf-form-switch>
<div class="gf-form" ng-show="ctrl.panel.points"> <div class="gf-form" ng-show="ctrl.panel.points">
......
...@@ -31,7 +31,8 @@ System.config({ ...@@ -31,7 +31,8 @@ System.config({
"jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair", "jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair",
"jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow", "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow",
"jquery.flot.gauge": "vendor/flot/jquery.flot.gauge", "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: { packages: {
......
...@@ -82,6 +82,9 @@ ...@@ -82,6 +82,9 @@
"lines": true, "lines": true,
"fill": 1, "fill": 1,
"linewidth": 2, "linewidth": 2,
"dashes": false,
"dashLength": 10,
"spaceLength": 10,
"points": false, "points": false,
"pointradius": 5, "pointradius": 5,
"bars": false, "bars": false,
......
...@@ -50,6 +50,9 @@ ...@@ -50,6 +50,9 @@
"lines": true, "lines": true,
"fill": 1, "fill": 1,
"linewidth": 1, "linewidth": 1,
"dashes": false,
"dashLength": 10,
"spaceLength": 10,
"points": false, "points": false,
"pointradius": 5, "pointradius": 5,
"bars": false, "bars": false,
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
"jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow", "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow",
"jquery.flot.gauge": "vendor/flot/jquery.flot.gauge", "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: { packages: {
......
/*
* 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)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment