From 1330488e130d5fec126f29e9fd23cb7005214701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 14 Oct 2014 16:57:33 -0400 Subject: [PATCH 1/4] Panel: plugins panels can now reside outsude the app/panels directory, added example plugin panel --- CHANGELOG.md | 2 ++ src/app/app.js | 1 - src/app/components/settings.js | 7 +++-- src/app/controllers/dashboardCtrl.js | 19 ++++-------- src/app/controllers/graphiteTarget.js | 2 +- src/app/directives/grafanaPanel.js | 9 ++++-- src/app/partials/dasheditor.html | 2 +- src/app/partials/paneleditor.html | 2 +- src/plugins/custom.panel.example/editor.html | 10 +++++++ src/plugins/custom.panel.example/module.html | 3 ++ src/plugins/custom.panel.example/module.js | 31 ++++++++++++++++++++ 11 files changed, 65 insertions(+), 23 deletions(-) create mode 100644 src/plugins/custom.panel.example/editor.html create mode 100644 src/plugins/custom.panel.example/module.html create mode 100644 src/plugins/custom.panel.example/module.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a158907f5..5a3a7f40560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,10 @@ **UI Improvements* - [Issue #770](https://github.com/grafana/grafana/issues/770). UI: Panel dropdown menu replaced with a new panel menu +**Misc** - [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 #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) diff --git a/src/app/app.js b/src/app/app.js index ab5c8fd6355..0b75d459435 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -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 = [ diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 825712f619f..01f40c2e761 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -15,7 +15,10 @@ function (_, crypto) { var defaults = { datasources : {}, window_title_prefix : 'Grafana - ', - panels : ['graph', 'text'], + 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) { diff --git a/src/app/controllers/dashboardCtrl.js b/src/app/controllers/dashboardCtrl.js index dc2d880ca19..294862d5fae 100644 --- a/src/app/controllers/dashboardCtrl.js +++ b/src/app/controllers/dashboardCtrl.js @@ -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) { diff --git a/src/app/controllers/graphiteTarget.js b/src/app/controllers/graphiteTarget.js index 27299474bc0..3d6368a7793 100644 --- a/src/app/controllers/graphiteTarget.js +++ b/src/app/controllers/graphiteTarget.js @@ -201,7 +201,7 @@ function (angular, _, config, gfunc, Parser) { $scope.targetTextChanged = function() { parseTarget(); - $scope.$parent.get_data(); + $scope.get_data(); }; $scope.targetChanged = function() { diff --git a/src/app/directives/grafanaPanel.js b/src/app/directives/grafanaPanel.js index e7587ace5c5..9e3c87fde3d 100644 --- a/src/app/directives/grafanaPanel.js +++ b/src/app/directives/grafanaPanel.js @@ -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); diff --git a/src/app/partials/dasheditor.html b/src/app/partials/dasheditor.html index 0831c243a8a..d5896b55512 100644 --- a/src/app/partials/dasheditor.html +++ b/src/app/partials/dasheditor.html @@ -84,7 +84,7 @@
- +
diff --git a/src/app/partials/paneleditor.html b/src/app/partials/paneleditor.html index ebc8a40ac63..d1a5c5eaab8 100644 --- a/src/app/partials/paneleditor.html +++ b/src/app/partials/paneleditor.html @@ -17,7 +17,7 @@
-
+
diff --git a/src/plugins/custom.panel.example/editor.html b/src/plugins/custom.panel.example/editor.html new file mode 100644 index 00000000000..b121e866850 --- /dev/null +++ b/src/plugins/custom.panel.example/editor.html @@ -0,0 +1,10 @@ +
+
+
+ +
+
+ +
+
+
diff --git a/src/plugins/custom.panel.example/module.html b/src/plugins/custom.panel.example/module.html new file mode 100644 index 00000000000..08ecbe30daa --- /dev/null +++ b/src/plugins/custom.panel.example/module.html @@ -0,0 +1,3 @@ +
+

Custom panel

+
diff --git a/src/plugins/custom.panel.example/module.js b/src/plugins/custom.panel.example/module.js new file mode 100644 index 00000000000..d04d8fca959 --- /dev/null +++ b/src/plugins/custom.panel.example/module.js @@ -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(); + }); +}); From 22db28d3e7532d38365f9b7a141cc20372d52449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 15 Oct 2014 10:55:46 -0400 Subject: [PATCH 2/4] Graph: New series style override option 'Fill below to', useful to visualize max & min as shadow for the mean, #940 --- CHANGELOG.md | 5 +- src/app/components/require.config.js | 2 + src/app/components/timeSeries.js | 3 + src/app/directives/grafanaGraph.js | 1 + src/app/panels/graph/module.js | 1 + src/app/panels/graph/seriesOverridesCtrl.js | 1 + src/test/specs/timeSeries-specs.js | 11 + src/test/test-main.js | 3 +- src/vendor/jquery/jquery.flot.fillbelow.js | 289 ++++++++++++++++++++ 9 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 src/vendor/jquery/jquery.flot.fillbelow.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a3a7f40560..682904a08d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,12 @@ **UI Improvements* - [Issue #770](https://github.com/grafana/grafana/issues/770). UI: Panel dropdown menu replaced with a new panel menu -**Misc** +**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** diff --git a/src/app/components/require.config.js b/src/app/components/require.config.js index a478fc76ff2..5d8da32846f 100644 --- a/src/app/components/require.config.js +++ b/src/app/components/require.config.js @@ -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'], diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index 79032fd69dd..4422e744d45 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -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; } diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 635a287dfa3..bf7264cc16f 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -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 = []; diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 6f5a174056f..cea1d824f2c 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -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) { diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index 1b6b1dfc144..d9681241ad3 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -67,6 +67,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]); diff --git a/src/test/specs/timeSeries-specs.js b/src/test/specs/timeSeries-specs.js index cf28c1aa450..a521ac3debc 100644 --- a/src/test/specs/timeSeries-specs.js +++ b/src/test/specs/timeSeries-specs.js @@ -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'; diff --git a/src/test/test-main.js b/src/test/test-main.js index cfb5c4a4a23..cab40157677 100644 --- a/src/test/test-main.js +++ b/src/test/test-main.js @@ -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'], diff --git a/src/vendor/jquery/jquery.flot.fillbelow.js b/src/vendor/jquery/jquery.flot.fillbelow.js new file mode 100644 index 00000000000..0153dd85e17 --- /dev/null +++ b/src/vendor/jquery/jquery.flot.fillbelow.js @@ -0,0 +1,289 @@ +(function($) { + "use strict"; + + var options = { + series: { + fillBelowTo: null + } + }; + + function init(plot) { + function findBelowSeries( series, allseries ) { + + var i; + + debugger; + 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); From fe620d8e443b01e97118c7f9b6e6ac96482faf0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 15 Oct 2014 11:07:51 -0400 Subject: [PATCH 3/4] Graph: fill below to series override option not automatically adds lines=false, as overrides, if you want lines just for the fill below series just remove the overrides, #940 --- src/app/panels/graph/module.js | 4 ++-- src/app/panels/graph/seriesOverridesCtrl.js | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index cea1d824f2c..a1dbc942553 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -342,8 +342,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) { diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index d9681241ad3..bf46b086bea 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -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(); }; From 61f6bd2c803d96025b98a98e9dc34aeaefcb0d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 15 Oct 2014 14:13:53 -0400 Subject: [PATCH 4/4] Graph: added typehead dropdown menu combination for the series override selection --- src/app/directives/all.js | 1 + src/app/directives/dropdown.typeahead.js | 94 ++++++++++++++++++++++ src/app/panels/graph/styleEditor.html | 7 +- src/vendor/jquery/jquery.flot.fillbelow.js | 1 - 4 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 src/app/directives/dropdown.typeahead.js diff --git a/src/app/directives/all.js b/src/app/directives/all.js index 35d718fc942..7f7836728a6 100644 --- a/src/app/directives/all.js +++ b/src/app/directives/all.js @@ -18,5 +18,6 @@ define([ './templateParamSelector', './graphiteSegment', './grafanaVersionCheck', + './dropdown.typeahead', './influxdbFuncEditor' ], function () {}); diff --git a/src/app/directives/dropdown.typeahead.js b/src/app/directives/dropdown.typeahead.js new file mode 100644 index 00000000000..e02b90b2576 --- /dev/null +++ b/src/app/directives/dropdown.typeahead.js @@ -0,0 +1,94 @@ +define([ + 'angular', + 'app', + 'lodash', + 'jquery', +], +function (angular, app, _, $) { + 'use strict'; + + angular + .module('grafana.directives') + .directive('dropdownTypeahead', function($compile) { + + var inputTemplate = ''; + + var buttonTemplate = ''; + + 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); + } + }; + }); +}); diff --git a/src/app/panels/graph/styleEditor.html b/src/app/panels/graph/styleEditor.html index 494d6bc1f7f..f230c2ba20a 100644 --- a/src/app/panels/graph/styleEditor.html +++ b/src/app/panels/graph/styleEditor.html @@ -88,11 +88,10 @@ {{option.name}}: {{option.value}} - +
diff --git a/src/vendor/jquery/jquery.flot.fillbelow.js b/src/vendor/jquery/jquery.flot.fillbelow.js index 0153dd85e17..ede2cb5e2d0 100644 --- a/src/vendor/jquery/jquery.flot.fillbelow.js +++ b/src/vendor/jquery/jquery.flot.fillbelow.js @@ -12,7 +12,6 @@ var i; - debugger; for ( i = 0; i < allseries.length; ++i ) { if ( allseries[ i ].id === series.fillBelowTo ) { return allseries[ i ];