From 152375d04cd3660c089445b09c8de2481df993eb Mon Sep 17 00:00:00 2001 From: Philipp Klaus Date: Wed, 23 Aug 2017 13:04:28 +0200 Subject: [PATCH] DASH: first working version of sparklines --- python_suite/dashboard/dashboard.py | 4 + python_suite/dashboard/static/js/sparkline.js | 139 ++++++++++++++++++ .../dashboard/views/pv_overview.jinja2 | 48 +++++- 3 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 python_suite/dashboard/static/js/sparkline.js diff --git a/python_suite/dashboard/dashboard.py b/python_suite/dashboard/dashboard.py index 927a688..1420280 100755 --- a/python_suite/dashboard/dashboard.py +++ b/python_suite/dashboard/dashboard.py @@ -114,6 +114,10 @@ def gview(name): def index(): return CONFIG +@route('/api/history/.json') +def api_history(name): + return {'history': HISTORY[name]} + @route('/static/') def static_content(path): return static_file(path, root='./static/') diff --git a/python_suite/dashboard/static/js/sparkline.js b/python_suite/dashboard/static/js/sparkline.js new file mode 100644 index 0000000..94cc089 --- /dev/null +++ b/python_suite/dashboard/static/js/sparkline.js @@ -0,0 +1,139 @@ +// https://github.com/DKirwan/reusable-d3-sparkline/blob/master/sparkline.js + +// Built to closely follow reusable charts best practices doc: http://bost.ocks.org/mike/chart/ + +function sparkline() { + // defaults + var width = 200; + var height = 40; + var dataSource = ''; + var dataSourceType = ''; + var selector = 'body'; + var gradientColors = ['green', 'orange', 'red']; + + // setters and getters + chart.width = function(value) { + if (!arguments.length) return width; + width = value; + return chart; + }; + + chart.height = function(value) { + if (!arguments.length) return height; + height = value; + return chart; + }; + + chart.dataSource = function(value) { + if (!arguments.length) return dataSource; + dataSource = value; + return chart; + }; + + chart.dataSourceType = function(value) { + if (!arguments.length) return dataSourceType; + dataSourceType = value; + return chart; + }; + + chart.selector = function(value) { + if (!arguments.length) return selector; + selector = value; + return chart; + }; + + chart.gradientColors = function(value) { + if (!arguments.length) return gradientColors; + gradientColors = value; + return chart; + }; + + function chart() { + var margin = { + top: 5, + right: 5, + bottom: 5, + left: 5 + }; + + var width = chart.width(); + var height = chart.height(); + var gradient; + + var x = d3.time.scale().range([0, width]); + var y = d3.scale.linear().range([height, 0]); + + // Define the line + var valueline = d3.svg.line() + .x(function (d) { return x(d[0]); }) + .y(function (d) { return y(d[1]); }); + + // Adds the svg canvas to the selector - 'body' by default + var svg = d3.select(chart.selector()) + .append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (chart.gradientColors() && chart.gradientColors().length) { + // this defines the gradient used + gradient = svg.append("defs") + .append("linearGradient") + .attr("id", "gradient") + .attr("x1", "0%") // starting x point + .attr("y1", "0%") // starting y point + .attr("x2", "0%") // ending x point + .attr("y2", "100%") // ending y point + .attr("spreadMethod", "pad"); + + chart.gradientColors().forEach(function (color, index) { + gradient.append("stop") + .attr("offset", ((index * 100) / chart.gradientColors().length) + '%') + .attr("stop-color", color) + .attr("stop-opacity", 0.7); + }) + } + + if (chart.dataSourceType().toLowerCase() === 'csv') { + d3.csv(chart.dataSource(), drawChart); + } else if (chart.dataSourceType().toLowerCase() === 'tsv') { + d3.tsv(chart.dataSource(), drawChart); + } else { + d3.json(chart.dataSource(), function(error, json) { + if (error) return drawChart(error, json); + drawChart(error, json.history); + }); + } + + /* + * formats chart data and appends the sparkline + */ + function drawChart(error, data) { + if (error) { console.log(error); return; } + + data.forEach(function (d) { + d[0] = new Date(d[0]); + d[1] = +d[1]; + }); + + // Scale the range of the data + x.domain(d3.extent(data, function (d) { return d[0]; })); + y.domain([0, d3.max(data, function (d) { return d[1]; })]); + + // Add the valueline path. + svg.append('path') + .attr('class', 'line') + .attr('stroke', function () { + if (gradient) { + return 'url(#gradient)'; + } + return '#444444'; + }) + .attr('d', valueline(data)); + + } + } + + return chart; +} diff --git a/python_suite/dashboard/views/pv_overview.jinja2 b/python_suite/dashboard/views/pv_overview.jinja2 index ac40c00..d0ec60a 100644 --- a/python_suite/dashboard/views/pv_overview.jinja2 +++ b/python_suite/dashboard/views/pv_overview.jinja2 @@ -4,6 +4,20 @@ {% block header %} + + + {% endblock %} {% block content %} @@ -15,14 +29,16 @@

{{ this_group.name }}

- - + + + + {% for pv_name in this_group.PVs %} {% set PV = config.PVs[config.PV_lookup[pv_name]] %} @@ -43,6 +59,7 @@ {% endif %} + {% endfor %}
Process Variable Value UnitSparkline
{{ PV.unit }}
@@ -50,3 +67,30 @@ {% endfor %} {% endblock %} + + +{% block js_end_of_page %} + +function updateSparklines() { + var sparklineItems = document.getElementsByClassName("sparkline"); + [].forEach.call(sparklineItems, function (el) { + var el_id = el.id; + var pvName = el_id.substring(el_id.indexOf("-")+1, el_id.length); + var pvName = pvName.replace(/-/g, ':'); + + var sparklineChart = sparkline() + .width(155) + .height(50) + .gradientColors(['green', 'orange', 'red']) // top -> bottom + .dataSource('/api/history/' + pvName + '.json') + .dataSourceType('JSON') + .selector('#'+el.id); + sparklineChart(); // render the chart + }); +}; + +$(function() { + // on page load + updateSparklines(); +}); +{% endblock %} -- 2.43.0