Merge branch 'master' into valuepanel
Conflicts: src/app/components/settings.js
This commit is contained in:
@@ -3,8 +3,13 @@
|
||||
**UI Improvements*
|
||||
- [Issue #770](https://github.com/grafana/grafana/issues/770). UI: Panel dropdown menu replaced with a new panel menu
|
||||
|
||||
**Graph**
|
||||
- [Issue #877](https://github.com/grafana/grafana/issues/877). Graph: Smart auto decimal precision when using scaled unit formats
|
||||
- [Issue #850](https://github.com/grafana/grafana/issues/850). Graph: Shared tooltip that shows multiple series & crosshair line, thx @toni-moreno
|
||||
- [Issue #940](https://github.com/grafana/grafana/issues/940). Graph: New series style override option "Fill below to", useful to visualize max & min as a shadow for the mean
|
||||
|
||||
**Misc**
|
||||
- [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory
|
||||
|
||||
**Fixes**
|
||||
- [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other)
|
||||
|
||||
@@ -57,7 +57,6 @@ function (angular, $, _, appLevelRequire, config) {
|
||||
register_fns.factory = $provide.factory;
|
||||
register_fns.service = $provide.service;
|
||||
register_fns.filter = $filterProvider.register;
|
||||
|
||||
});
|
||||
|
||||
var apps_deps = [
|
||||
|
||||
@@ -40,6 +40,7 @@ require.config({
|
||||
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
|
||||
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
|
||||
'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair',
|
||||
'jquery.flot.fillbelow': '../vendor/jquery/jquery.flot.fillbelow',
|
||||
|
||||
modernizr: '../vendor/modernizr-2.6.1',
|
||||
|
||||
@@ -83,6 +84,7 @@ require.config({
|
||||
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.time': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.crosshair':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.fillbelow':['jquery', 'jquery.flot'],
|
||||
'angular-cookies': ['angular'],
|
||||
'angular-dragdrop': ['jquery', 'angular'],
|
||||
'angular-loader': ['angular'],
|
||||
|
||||
@@ -15,7 +15,10 @@ function (_, crypto) {
|
||||
var defaults = {
|
||||
datasources : {},
|
||||
window_title_prefix : 'Grafana - ',
|
||||
panels : ['graph', 'text', 'stats'],
|
||||
panels : {
|
||||
'graph': { path: 'panels/graph' },
|
||||
'text': { path: 'panels/text' }
|
||||
},
|
||||
plugins : {},
|
||||
default_route : '/dashboard/file/default.json',
|
||||
playlist_timespan : "1m",
|
||||
@@ -76,7 +79,7 @@ function (_, crypto) {
|
||||
});
|
||||
|
||||
if (settings.plugins.panels) {
|
||||
settings.panels = _.union(settings.panels, settings.plugins.panels);
|
||||
_.extend(settings.panels, settings.plugins.panels);
|
||||
}
|
||||
|
||||
if (!settings.plugins.dependencies) {
|
||||
|
||||
@@ -9,6 +9,7 @@ function (_, kbn) {
|
||||
this.datapoints = opts.datapoints;
|
||||
this.info = opts.info;
|
||||
this.label = opts.info.alias;
|
||||
this.id = opts.info.alias;
|
||||
this.valueFormater = kbn.valueFormats.none;
|
||||
this.stats = {};
|
||||
}
|
||||
@@ -50,6 +51,8 @@ function (_, kbn) {
|
||||
if (override.pointradius !== void 0) { this.points.radius = override.pointradius; }
|
||||
if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; }
|
||||
if (override.zindex !== void 0) { this.zindex = override.zindex; }
|
||||
if (override.fillBelowTo !== void 0) { this.fillBelowTo = override.fillBelowTo; }
|
||||
|
||||
if (override.yaxis !== void 0) {
|
||||
this.info.yaxis = override.yaxis;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ function (angular, $, config, _) {
|
||||
$timeout) {
|
||||
|
||||
$scope.editor = { index: 0 };
|
||||
$scope.panelNames = config.panels;
|
||||
$scope.panelNames = _.map(config.panels, function(value, key) { return key; });
|
||||
var resizeEventTimeout;
|
||||
|
||||
this.init = function(dashboardData) {
|
||||
@@ -90,21 +90,12 @@ function (angular, $, config, _) {
|
||||
};
|
||||
};
|
||||
|
||||
$scope.edit_path = function(type) {
|
||||
var p = $scope.panel_path(type);
|
||||
if(p) {
|
||||
return p+'/editor.html';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
$scope.panelEditorPath = function(type) {
|
||||
return 'app/' + config.panels[type].path + '/editor.html';
|
||||
};
|
||||
|
||||
$scope.panel_path =function(type) {
|
||||
if(type) {
|
||||
return 'app/panels/'+type.replace(".","/");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
$scope.pulldownEditorPath = function(type) {
|
||||
return 'app/panels/'+type+'/editor.html';
|
||||
};
|
||||
|
||||
$scope.showJsonEditor = function(evt, options) {
|
||||
|
||||
@@ -201,7 +201,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
|
||||
$scope.targetTextChanged = function() {
|
||||
parseTarget();
|
||||
$scope.$parent.get_data();
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.targetChanged = function() {
|
||||
|
||||
@@ -18,5 +18,6 @@ define([
|
||||
'./templateParamSelector',
|
||||
'./graphiteSegment',
|
||||
'./grafanaVersionCheck',
|
||||
'./dropdown.typeahead',
|
||||
'./influxdbFuncEditor'
|
||||
], function () {});
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash',
|
||||
'jquery',
|
||||
],
|
||||
function (angular, app, _, $) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('dropdownTypeahead', function($compile) {
|
||||
|
||||
var inputTemplate = '<input type="text"'+
|
||||
' class="grafana-target-segment-input input-medium grafana-target-segment-input"' +
|
||||
' spellcheck="false" style="display:none"></input>';
|
||||
|
||||
var buttonTemplate = '<a class="grafana-target-segment grafana-target-function dropdown-toggle"' +
|
||||
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
|
||||
' data-placement="top"><i class="icon-plus"></i></a>';
|
||||
|
||||
return {
|
||||
scope: {
|
||||
"menuItems": "=dropdownTypeahead",
|
||||
"dropdownTypeaheadOnSelect": "&dropdownTypeaheadOnSelect"
|
||||
},
|
||||
link: function($scope, elem) {
|
||||
var $input = $(inputTemplate);
|
||||
var $button = $(buttonTemplate);
|
||||
$input.appendTo(elem);
|
||||
$button.appendTo(elem);
|
||||
|
||||
var typeaheadValues = _.reduce($scope.menuItems, function(memo, value) {
|
||||
_.each(value.submenu, function(item) {
|
||||
memo.push(value.text + ' ' + item.text);
|
||||
});
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
$input.typeahead({
|
||||
source: typeaheadValues,
|
||||
minLength: 1,
|
||||
items: 10,
|
||||
updater: function (value) {
|
||||
var result = {};
|
||||
_.each($scope.menuItems, function(menuItem, optionIndex) {
|
||||
_.each(menuItem.submenu, function(submenuItem, valueIndex) {
|
||||
if (value === (menuItem.text + ' ' + submenuItem.text)) {
|
||||
result.$item = submenuItem;
|
||||
result.$optionIndex = optionIndex;
|
||||
result.$valueIndex = valueIndex;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (result.$item) {
|
||||
$scope.$apply(function() {
|
||||
$scope.dropdownTypeaheadOnSelect(result);
|
||||
});
|
||||
}
|
||||
|
||||
$input.trigger('blur');
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
$button.click(function() {
|
||||
$button.hide();
|
||||
$input.show();
|
||||
$input.focus();
|
||||
});
|
||||
|
||||
$input.keyup(function() {
|
||||
elem.toggleClass('open', $input.val() === '');
|
||||
});
|
||||
|
||||
$input.blur(function() {
|
||||
$input.hide();
|
||||
$input.val('');
|
||||
$button.show();
|
||||
$button.focus();
|
||||
// clicking the function dropdown menu wont
|
||||
// work if you remove class at once
|
||||
setTimeout(function() {
|
||||
elem.removeClass('open');
|
||||
}, 200);
|
||||
});
|
||||
|
||||
$compile(elem.contents())($scope);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -177,6 +177,7 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
|
||||
var series = data[i];
|
||||
series.applySeriesOverrides(panel.seriesOverrides);
|
||||
series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats);
|
||||
|
||||
// if hidden remove points and disable stack
|
||||
if (scope.hiddenSeries[series.info.alias]) {
|
||||
series.data = [];
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'config',
|
||||
'./panelMenu',
|
||||
],
|
||||
function (angular, $) {
|
||||
function (angular, $, config) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
@@ -68,10 +69,12 @@ function (angular, $) {
|
||||
|
||||
elem.addClass('ng-cloak');
|
||||
|
||||
var panelPath = config.panels[panelType].path;
|
||||
|
||||
$scope.require([
|
||||
'jquery',
|
||||
'text!panels/'+panelType+'/module.html',
|
||||
'panels/' + panelType + "/module",
|
||||
'text!'+panelPath+'/module.html',
|
||||
panelPath + "/module",
|
||||
], function ($, moduleTemplate) {
|
||||
var $module = $(moduleTemplate);
|
||||
$module.prepend(panelHeader);
|
||||
|
||||
@@ -16,6 +16,7 @@ define([
|
||||
'jquery.flot.time',
|
||||
'jquery.flot.stack',
|
||||
'jquery.flot.stackpercent',
|
||||
'jquery.flot.fillbelow',
|
||||
'jquery.flot.crosshair'
|
||||
],
|
||||
function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
@@ -340,8 +341,8 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.addSeriesOverride = function() {
|
||||
$scope.panel.seriesOverrides.push({});
|
||||
$scope.addSeriesOverride = function(override) {
|
||||
$scope.panel.seriesOverrides.push(override || {});
|
||||
};
|
||||
|
||||
$scope.removeSeriesOverride = function(override) {
|
||||
|
||||
@@ -34,6 +34,14 @@ define([
|
||||
var option = $scope.overrideMenu[optionIndex];
|
||||
var value = option.values[valueIndex];
|
||||
$scope.override[option.propertyName] = value;
|
||||
|
||||
// automatically disable lines for this series and the fill bellow to series
|
||||
// can be removed by the user if they still want lines
|
||||
if (option.propertyName === 'fillBelowTo') {
|
||||
$scope.override['lines'] = false;
|
||||
$scope.addSeriesOverride({ alias: value, lines: false });
|
||||
}
|
||||
|
||||
$scope.updateCurrentOverrides();
|
||||
$scope.render();
|
||||
};
|
||||
@@ -67,6 +75,7 @@ define([
|
||||
$scope.addOverrideOption('Lines', 'lines', [true, false]);
|
||||
$scope.addOverrideOption('Line fill', 'fill', [0,1,2,3,4,5,6,7,8,9,10]);
|
||||
$scope.addOverrideOption('Line width', 'linewidth', [0,1,2,3,4,5,6,7,8,9,10]);
|
||||
$scope.addOverrideOption('Fill below to', 'fillBelowTo', $scope.getSeriesNames());
|
||||
$scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]);
|
||||
$scope.addOverrideOption('Points', 'points', [true, false]);
|
||||
$scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]);
|
||||
|
||||
@@ -88,11 +88,10 @@
|
||||
<i class="pointer icon-remove" ng-click="removeOverride(option)"></i>
|
||||
{{option.name}}: {{option.value}}
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle grafana-target-segment" data-toggle="dropdown" gf-dropdown="overrideMenu" bs-tooltip="'set option to override'" data-placement="top">
|
||||
<i class="icon-plus"></i>
|
||||
</a>
|
||||
|
||||
<li class="dropdown" dropdown-typeahead="overrideMenu" dropdown-typeahead-on-select="setOverride($optionIndex, $valueIndex)">
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
</div>
|
||||
|
||||
<div ng-repeat="pulldown in dashboard.nav" ng-controller="SubmenuCtrl" ng-show="editor.index == 4+$index">
|
||||
<ng-include ng-show="pulldown.enable" src="edit_path(pulldown.type)"></ng-include>
|
||||
<ng-include ng-show="pulldown.enable" src="pulldownEditorPath(pulldown.type)"></ng-include>
|
||||
<button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
<div ng-show="editorTabs[editor.index] == 'Panel'">
|
||||
<div ng-include src="edit_path(panel.type)"></div>
|
||||
<div ng-include src="panelEditorPath(panel.type)"></div>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="tab in panelMeta.editorTabs" ng-show="editorTabs[editor.index] == tab.title">
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<div>
|
||||
<div class="row-fluid">
|
||||
<div class="span4">
|
||||
<label class="small">Mode</label> <select class="input-medium" ng-model="panel.mode" ng-options="f for f in ['html','markdown','text']"></select>
|
||||
</div>
|
||||
<div class="span2" ng-show="panel.mode == 'text'">
|
||||
<label class="small">Font Size</label> <select class="input-mini" ng-model="panel.style['font-size']" ng-options="f for f in ['6pt','7pt','8pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<div ng-controller='CustomPanelCtrl'>
|
||||
<h2>Custom panel</h2>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash',
|
||||
'require',
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.panels.custom', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('CustomPanelCtrl', function($scope, panelSrv) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
description : "Example plugin panel",
|
||||
};
|
||||
|
||||
// set and populate defaults
|
||||
var _d = {
|
||||
};
|
||||
|
||||
_.defaults($scope.panel, _d);
|
||||
|
||||
$scope.init = function() {
|
||||
panelSrv.init($scope);
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
});
|
||||
});
|
||||
@@ -70,6 +70,17 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
describe('series option overrides, fill below to', function() {
|
||||
beforeEach(function() {
|
||||
series.info.alias = 'test';
|
||||
series.applySeriesOverrides([{ alias: 'test', fillBelowTo: 'min' }]);
|
||||
});
|
||||
|
||||
it('should disable line fill and add fillBelowTo', function() {
|
||||
expect(series.fillBelowTo).to.be('min');
|
||||
});
|
||||
});
|
||||
|
||||
describe('series option overrides, pointradius, steppedLine', function() {
|
||||
beforeEach(function() {
|
||||
series.info.alias = 'test';
|
||||
|
||||
@@ -42,6 +42,7 @@ require.config({
|
||||
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
|
||||
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
|
||||
'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair',
|
||||
'jquery.flot.fillbelow': '../vendor/jquery/jquery.flot.fillbelow',
|
||||
|
||||
modernizr: '../vendor/modernizr-2.6.1',
|
||||
},
|
||||
@@ -68,7 +69,6 @@ require.config({
|
||||
exports: 'Crypto'
|
||||
},
|
||||
|
||||
'jquery-ui': ['jquery'],
|
||||
'jquery.flot': ['jquery'],
|
||||
'jquery.flot.pie': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.events': ['jquery', 'jquery.flot'],
|
||||
@@ -77,6 +77,7 @@ require.config({
|
||||
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.time': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.crosshair':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.fillbelow':['jquery', 'jquery.flot'],
|
||||
|
||||
'angular-route': ['angular'],
|
||||
'angular-cookies': ['angular'],
|
||||
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
(function($) {
|
||||
"use strict";
|
||||
|
||||
var options = {
|
||||
series: {
|
||||
fillBelowTo: null
|
||||
}
|
||||
};
|
||||
|
||||
function init(plot) {
|
||||
function findBelowSeries( series, allseries ) {
|
||||
|
||||
var i;
|
||||
|
||||
for ( i = 0; i < allseries.length; ++i ) {
|
||||
if ( allseries[ i ].id === series.fillBelowTo ) {
|
||||
return allseries[ i ];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* top and bottom doesn't actually matter for this, we're just using it to help make this easier to think about */
|
||||
/* this is a vector cross product operation */
|
||||
function segmentIntersection(top_left_x, top_left_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y, bottom_right_x, bottom_right_y) {
|
||||
var top_delta_x, top_delta_y, bottom_delta_x, bottom_delta_y,
|
||||
s, t;
|
||||
|
||||
top_delta_x = top_right_x - top_left_x;
|
||||
top_delta_y = top_right_y - top_left_y;
|
||||
bottom_delta_x = bottom_right_x - bottom_left_x;
|
||||
bottom_delta_y = bottom_right_y - bottom_left_y;
|
||||
|
||||
s = (
|
||||
(-top_delta_y * (top_left_x - bottom_left_x)) + (top_delta_x * (top_left_y - bottom_left_y))
|
||||
) / (
|
||||
-bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y
|
||||
);
|
||||
|
||||
t = (
|
||||
(bottom_delta_x * (top_left_y - bottom_left_y)) - (bottom_delta_y * (top_left_x - bottom_left_x))
|
||||
) / (
|
||||
-bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y
|
||||
);
|
||||
|
||||
// Collision detected
|
||||
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
|
||||
return [
|
||||
top_left_x + (t * top_delta_x), // X
|
||||
top_left_y + (t * top_delta_y) // Y
|
||||
];
|
||||
}
|
||||
|
||||
// No collision
|
||||
return null;
|
||||
}
|
||||
|
||||
function plotDifferenceArea(plot, ctx, series) {
|
||||
if ( series.fillBelowTo === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var otherseries,
|
||||
|
||||
ps,
|
||||
points,
|
||||
|
||||
otherps,
|
||||
otherpoints,
|
||||
|
||||
plotOffset,
|
||||
fillStyle;
|
||||
|
||||
function openPolygon(x, y) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(
|
||||
series.xaxis.p2c(x) + plotOffset.left,
|
||||
series.yaxis.p2c(y) + plotOffset.top
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function closePolygon() {
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
function validateInput() {
|
||||
if (points.length/ps !== otherpoints.length/otherps) {
|
||||
console.error("Refusing to graph inconsistent number of points");
|
||||
return false;
|
||||
}
|
||||
|
||||
var i;
|
||||
for (i = 0; i < (points.length / ps); i++) {
|
||||
if (
|
||||
points[i * ps] !== null &&
|
||||
otherpoints[i * otherps] !== null &&
|
||||
points[i * ps] !== otherpoints[i * otherps]
|
||||
) {
|
||||
console.error("Refusing to graph points without matching value");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function findNextStart(start_i, end_i) {
|
||||
console.assert(end_i > start_i, "expects the end index to be greater than the start index");
|
||||
|
||||
var start = (
|
||||
start_i === 0 ||
|
||||
points[start_i - 1] === null ||
|
||||
otherpoints[start_i - 1] === null
|
||||
),
|
||||
equal = false,
|
||||
i,
|
||||
intersect;
|
||||
|
||||
for (i = start_i; i < end_i; i++) {
|
||||
// Take note of null points
|
||||
if (
|
||||
points[(i * ps) + 1] === null ||
|
||||
otherpoints[(i * ps) + 1] === null
|
||||
) {
|
||||
equal = false;
|
||||
start = true;
|
||||
}
|
||||
|
||||
// Take note of equal points
|
||||
else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) {
|
||||
equal = true;
|
||||
start = false;
|
||||
}
|
||||
|
||||
|
||||
else if (points[(i * ps) + 1] > otherpoints[(i * otherps) + 1]) {
|
||||
// If we begin above the desired point
|
||||
if (start) {
|
||||
openPolygon(points[i * ps], points[(i * ps) + 1]);
|
||||
}
|
||||
|
||||
// If an equal point preceeds this, start the polygon at that equal point
|
||||
else if (equal) {
|
||||
openPolygon(points[(i - 1) * ps], points[((i - 1) * ps) + 1]);
|
||||
}
|
||||
|
||||
// Otherwise, find the intersection point, and start it there
|
||||
else {
|
||||
intersect = intersectionPoint(i);
|
||||
openPolygon(intersect[0], intersect[1]);
|
||||
}
|
||||
|
||||
topTraversal(i, end_i);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we go below equal, equal at any preceeding point is irrelevant
|
||||
else {
|
||||
start = false;
|
||||
equal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function intersectionPoint(right_i) {
|
||||
console.assert(right_i > 0, "expects the second point in the series line segment");
|
||||
|
||||
var i, intersect;
|
||||
|
||||
for (i = 1; i < (otherpoints.length/otherps); i++) {
|
||||
intersect = segmentIntersection(
|
||||
points[(right_i - 1) * ps], points[((right_i - 1) * ps) + 1],
|
||||
points[right_i * ps], points[(right_i * ps) + 1],
|
||||
|
||||
otherpoints[(i - 1) * otherps], otherpoints[((i - 1) * otherps) + 1],
|
||||
otherpoints[i * otherps], otherpoints[(i * otherps) + 1]
|
||||
);
|
||||
|
||||
if (intersect !== null) {
|
||||
return intersect;
|
||||
}
|
||||
}
|
||||
|
||||
console.error("intersectionPoint() should only be called when an intersection happens");
|
||||
}
|
||||
|
||||
function bottomTraversal(start_i, end_i) {
|
||||
console.assert(start_i >= end_i, "the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)");
|
||||
|
||||
var i;
|
||||
|
||||
for (i = start_i; i >= end_i; i--) {
|
||||
ctx.lineTo(
|
||||
otherseries.xaxis.p2c(otherpoints[i * otherps]) + plotOffset.left,
|
||||
otherseries.yaxis.p2c(otherpoints[(i * otherps) + 1]) + plotOffset.top
|
||||
);
|
||||
}
|
||||
|
||||
closePolygon();
|
||||
}
|
||||
|
||||
function topTraversal(start_i, end_i) {
|
||||
console.assert(start_i <= end_i, "the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)");
|
||||
|
||||
var i,
|
||||
intersect;
|
||||
|
||||
for (i = start_i; i < end_i; i++) {
|
||||
if (points[(i * ps) + 1] === null && i > start_i) {
|
||||
bottomTraversal(i - 1, start_i);
|
||||
findNextStart(i, end_i);
|
||||
return;
|
||||
}
|
||||
|
||||
else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) {
|
||||
bottomTraversal(i, start_i);
|
||||
findNextStart(i, end_i);
|
||||
return;
|
||||
}
|
||||
|
||||
else if (points[(i * ps) + 1] < otherpoints[(i * otherps) + 1]) {
|
||||
intersect = intersectionPoint(i);
|
||||
ctx.lineTo(
|
||||
series.xaxis.p2c(intersect[0]) + plotOffset.left,
|
||||
series.yaxis.p2c(intersect[1]) + plotOffset.top
|
||||
);
|
||||
bottomTraversal(i, start_i);
|
||||
findNextStart(i, end_i);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
ctx.lineTo(
|
||||
series.xaxis.p2c(points[i * ps]) + plotOffset.left,
|
||||
series.yaxis.p2c(points[(i * ps) + 1]) + plotOffset.top
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bottomTraversal(end_i, start_i);
|
||||
}
|
||||
|
||||
|
||||
// Begin processing
|
||||
|
||||
otherseries = findBelowSeries( series, plot.getData() );
|
||||
|
||||
if ( !otherseries ) {
|
||||
return;
|
||||
}
|
||||
|
||||
ps = series.datapoints.pointsize;
|
||||
points = series.datapoints.points;
|
||||
otherps = otherseries.datapoints.pointsize;
|
||||
otherpoints = otherseries.datapoints.points;
|
||||
plotOffset = plot.getPlotOffset();
|
||||
|
||||
if (!validateInput()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Flot's getFillStyle() should probably be exposed somewhere
|
||||
fillStyle = $.color.parse(series.color);
|
||||
fillStyle.a = 0.4;
|
||||
fillStyle.normalize();
|
||||
ctx.fillStyle = fillStyle.toString();
|
||||
|
||||
|
||||
// Begin recursive bi-directional traversal
|
||||
findNextStart(0, points.length/ps);
|
||||
}
|
||||
|
||||
plot.hooks.drawSeries.push(plotDifferenceArea);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: "fillbelow",
|
||||
version: "0.1.0"
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
Reference in New Issue
Block a user