├── .gitignore
├── dynamic-highcharts-plugin-1.png
├── dynamic-highcharts-plugin-2.png
├── LICENSE
├── README.md
├── plugins_highcharts.min.js
└── plugin_highcharts.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.pyc
3 | venv/
4 | calendar.py
5 | config.ini
6 |
--------------------------------------------------------------------------------
/dynamic-highcharts-plugin-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinux/freeboard-dynamic-highcharts-plugin/HEAD/dynamic-highcharts-plugin-1.png
--------------------------------------------------------------------------------
/dynamic-highcharts-plugin-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinux/freeboard-dynamic-highcharts-plugin/HEAD/dynamic-highcharts-plugin-2.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # freeboard-dynamic-highcharts-plugin
2 |
Dynamic highcharts widget plugin for freeboard.io
3 | A damn-sexy, open source real-time dashboard builder for IOT and other web mashups.
4 |
5 | This widget plugin is highly based on the interactive-indicator created by Hugo Sequeira.
6 |
7 | Github repository: https://github.com/onlinux/freeboard-dynamic-highcharts-plugin
8 |
9 |
10 |
11 |
12 |
13 |
14 | INSTALLATION
15 | Copy the file (plugin_highcharts.js from https://github.com/onlinux/freeboard-dynamic-highcharts-plugin) to your freeboard installation, for example:
16 |
17 | $ cp ./plugin_highcharts.js /freeboard/plugins/thirdparty
18 |
19 | edit the freeboard index.html file and add a link to the plugin near the end of the head.js script loader, like:
20 |
21 | head.js(
22 | 'js/freeboard_plugins.min.js',
23 | 'plugins/actuator.js',
24 | $(function() {
25 | //DOM Ready
26 | freeboard.initialize(true);
27 | })head.js(
28 | 'js/freeboard_plugins.min.js',
29 | 'plugins/thirdparty/plugin_highcharts.js',
30 | $(function() {
31 | //DOM Ready
32 | freeboard.initialize(true);
33 | })
34 |
--------------------------------------------------------------------------------
/plugins_highcharts.min.js:
--------------------------------------------------------------------------------
1 | !function(){function e(e){return!isNaN(parseFloat(e))&&isFinite(e)}var t=0,o=1e3,r=3,l=[{name:"timeframe",display_name:"Timeframe (s)",type:"text",description:"Specify the last number of seconds you want to see.",default_value:60},{name:"blocks",display_name:"Height (No. Blocks)",type:"text",default_value:4},{name:"chartType",display_name:"Chart Type",type:"option",options:[{name:"Area",value:"area"},{name:"Spline",value:"spline"}]},{name:"title",display_name:"Title",type:"text"},{name:"xaxis",display_name:"X-Axis",type:"calculated",default_value:'{"title":{"text" : "Time"}, "type": "datetime", "floor":0}'},{name:"yaxis",display_name:"Y-Axis",type:"calculated",default_value:'{"title":{"text" : "Values"}, "minorTickInterval":"auto", "floor":0}'}];for(i=1;i<=r;i++){var a={name:"series"+i,display_name:"Series "+i+" - Datasource",type:"calculated"},s={name:"series"+i+"label",display_name:"Series "+i+" - Label",type:"text"};l.push(a),l.push(s)}freeboard.loadWidgetPlugin({type_name:"highcharts-timeseries",display_name:"Time series (Highcharts)",description:"Time series line chart.",external_scripts:["https://code.highcharts.com/8.0.0/highcharts.js","https://code.highcharts.com/8.0.0/modules/exporting.js"],fill_size:!0,settings:l,newInstance:function(e,t){t(new n(e))}});var n=function(l){function a(){Highcharts.theme={global:{useUTC:!1},colors:["#2b908f","#90ee7e","#f45b5b","#7798BF","#aaeeee","#ff0066","#eeaaee","#55BF3B","#DF5353","#7798BF","#aaeeee"],chart:{backgroundColor:null,style:{fontFamily:"'Open Sans', sans-serif"},plotBorderColor:"#606063"},title:{style:{color:"#E0E0E3",fontSize:"20px"}},subtitle:{style:{color:"#E0E0E3",textTransform:"uppercase"}},xAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",title:{style:{color:"#A0A0A3"}}},yAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",tickWidth:1,title:{style:{color:"#A0A0A3"}}},tooltip:{backgroundColor:"rgba(0, 0, 0, 0.85)",style:{color:"#F0F0F0"}},plotOptions:{series:{dataLabels:{color:"#B0B0B3"},marker:{lineColor:"#333"}},boxplot:{fillColor:"#505053"},candlestick:{lineColor:"white"},errorbar:{color:"white"}},legend:{itemStyle:{color:"#E0E0E3"},itemHoverStyle:{color:"#FFF"},itemHiddenStyle:{color:"#606063"}},credits:{style:{color:"#666"}},labels:{style:{color:"#707073"}},drilldown:{activeAxisLabelStyle:{color:"#F0F0F3"},activeDataLabelStyle:{color:"#F0F0F3"}},navigation:{buttonOptions:{symbolStroke:"#DDDDDD",theme:{fill:"#505053"}}},rangeSelector:{buttonTheme:{fill:"#505053",stroke:"#000000",style:{color:"#CCC"},states:{hover:{fill:"#707073",stroke:"#000000",style:{color:"white"}},select:{fill:"#000003",stroke:"#000000",style:{color:"white"}}}},inputBoxBorderColor:"#505053",inputStyle:{backgroundColor:"#333",color:"silver"},labelStyle:{color:"silver"}},navigator:{handles:{backgroundColor:"#666",borderColor:"#AAA"},outlineColor:"#CCC",maskFill:"rgba(255,255,255,0.1)",series:{color:"#7798BF",lineColor:"#A6C7ED"},xAxis:{gridLineColor:"#505053"}},scrollbar:{barBackgroundColor:"#808083",barBorderColor:"#808083",buttonArrowColor:"#CCC",buttonBackgroundColor:"#606063",buttonBorderColor:"#606063",rifleColor:"#FFF",trackBackgroundColor:"#404043",trackBorderColor:"#404043"},legendBackgroundColor:"rgba(0, 0, 0, 0.5)",background2:"#505053",dataLabelsColor:"#B0B0B3",textColor:"#C0C0C0",contrastTextColor:"#F0F0F3",maskColor:"rgba(255,255,255,0.3)"},Highcharts.setOptions(Highcharts.theme);var e=JSON.parse(n.xaxis),t=JSON.parse(n.yaxis),o=n.title,l=n.chartType,a=[];for(i=1;i<=r;i++){var c=n["series"+i];if(c){var d="series"+i+"label",g=n[d];console.log("label: ",g);var m={id:"series"+i,name:g,fillColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,Highcharts.getOptions().colors[i-1]],[1,Highcharts.Color(Highcharts.getOptions().colors[i-1]).setOpacity(0).get("rgba")]]},data:[],connectNulls:!0};a.push(m)}}h.css("height",60*s.getHeight()-10+"px"),h.css("width","100%"),h.highcharts({chart:{type:l,animation:Highcharts.svg,marginRight:20},title:{text:o},xAxis:e,yAxis:t,plotOptions:{area:{marker:{enabled:!1,symbol:"circle",radius:2,hover:{enabled:!0}},lineWidth:2,states:{hover:{lineWidth:2}},threshold:null}},tooltip:{formatter:function(){return""+this.series.name+"
"+Highcharts.dateFormat("%Y-%m-%d %H:%M:%S",this.x)+"
"+Highcharts.numberFormat(this.y,1)}},series:a})}var s=this,n=l,c="highcharts-widget-timeseries-"+t++,h=$('');s.render=function(e){$(e).append(h),a()},s.getHeight=function(){return n.blocks},s.onSettingsChanged=function(e){n=e,a()},s.onCalculatedValueChanged=function(t,r){var i=h.highcharts(),l=i.get(t);if(l){var a=n.timeframe*o,s=(l.data.length,!1);if(l.data.length>1){var c=l.data[0].x,d=(new Date).getTime();d-c>a&&(s=!0)}if(e(r)){var g=(new Date).getTime(),m=[g,Number(r)];l.addPoint(m,!0,s)}}},s.onDispose=function(){}}}();
2 |
--------------------------------------------------------------------------------
/plugin_highcharts.js:
--------------------------------------------------------------------------------
1 | // ┌────────────────────────────────────────────────────────────────────┐ \\
2 | // │ freeboard-dynamic-highcharts-plugin │ \\
3 | // ├────────────────────────────────────────────────────────────────────┤ \\
4 | // │ https://blog.onlinux.fr/?tag=freeboard │ \\
5 | // ├────────────────────────────────────────────────────────────────────┤ \\
6 | // │ Licensed under the MIT license. │ \\
7 | // ├────────────────────────────────────────────────────────────────────┤ \\
8 | // │ Freeboard widget plugin for Highcharts. │ \\
9 | // └────────────────────────────────────────────────────────────────────┘ \\
10 | (function() {
11 |
12 | //
13 | // DECLARATIONS
14 | //
15 | var HIGHCHARTS_ID = 0;
16 | var ONE_SECOND_IN_MILIS = 1000;
17 | var MAX_NUM_SERIES = 3;
18 |
19 | //
20 | // HELPERS
21 | //
22 |
23 | // Get coordinates of point
24 | function xy(obj, x, y) {
25 | return [obj[x], obj[y]]
26 | }
27 |
28 | function isNumber(n) {
29 | return !isNaN(parseFloat(n)) && isFinite(n);
30 | }
31 | //
32 | // TIME SERIES CHARTS
33 | //
34 | var highchartsLineWidgetSettings = [{
35 | "name": "timeframe",
36 | "display_name": "Timeframe (s)",
37 | "type": "text",
38 | "description": "Specify the last number of seconds you want to see.",
39 | "default_value": 60
40 | }, {
41 | "name": "blocks",
42 | "display_name": "Height (No. Blocks)",
43 | "type": "text",
44 | "default_value": 4
45 | }, {
46 | "name": "chartType",
47 | "display_name": "Chart Type",
48 | "type": "option",
49 | "options": [{
50 | "name": "Area",
51 | "value": "area"
52 | }, {
53 | "name": "Spline",
54 | "value": "spline"
55 | }]
56 | }, {
57 | "name": "title",
58 | "display_name": "Title",
59 | "type": "text"
60 | }, {
61 | "name": "xaxis",
62 | "display_name": "X-Axis",
63 | "type": "calculated",
64 | "default_value": "{\"title\":{\"text\" : \"Time\"}, \"type\": \"datetime\", \"floor\":0}"
65 | }, {
66 | "name": "yaxis",
67 | "display_name": "Y-Axis",
68 | "type": "calculated",
69 | "default_value": "{\"title\":{\"text\" : \"Values\"}, \"minorTickInterval\":\"auto\", \"floor\":0}"
70 | }];
71 |
72 | for (i = 1; i <= MAX_NUM_SERIES; i++) {
73 | var dataSource = {
74 | "name": "series" + i,
75 | "display_name": "Series " + i + " - Datasource",
76 | "type": "calculated"
77 | };
78 |
79 | var xField = {
80 | "name": "series" + i + "label",
81 | "display_name": "Series " + i + " - Label",
82 | "type": "text",
83 | };
84 |
85 | highchartsLineWidgetSettings.push(dataSource);
86 | highchartsLineWidgetSettings.push(xField);
87 | }
88 |
89 | freeboard
90 | .loadWidgetPlugin({
91 | "type_name": "highcharts-timeseries",
92 | "display_name": "Time series (Highcharts)",
93 | "description": "Time series line chart.",
94 | "external_scripts": [
95 | "https://code.highcharts.com/8.0.0/highcharts.js",
96 | "https://code.highcharts.com/8.0.0/modules/exporting.js"
97 | ],
98 | "fill_size": true,
99 | "settings": highchartsLineWidgetSettings,
100 | newInstance: function(settings, newInstanceCallback) {
101 | newInstanceCallback(new highchartsTimeseriesWidgetPlugin(
102 | settings));
103 | }
104 | });
105 |
106 | var highchartsTimeseriesWidgetPlugin = function(settings) {
107 |
108 | var self = this;
109 | var currentSettings = settings;
110 |
111 | var thisWidgetId = "highcharts-widget-timeseries-" + HIGHCHARTS_ID++;
112 | var thisWidgetContainer = $('');
113 |
114 | function createWidget() {
115 |
116 | Highcharts.theme = {
117 | global: {
118 | useUTC: false
119 | },
120 | colors: ["#2b908f", "#90ee7e", "#f45b5b", "#7798BF", "#aaeeee",
121 | "#ff0066", "#eeaaee", "#55BF3B", "#DF5353", "#7798BF", "#aaeeee"
122 | ],
123 | chart: {
124 | backgroundColor: null,
125 | style: {
126 | fontFamily: "'Open Sans', sans-serif"
127 | },
128 | plotBorderColor: '#606063'
129 | },
130 | title: {
131 | style: {
132 | color: '#E0E0E3',
133 | fontSize: '20px'
134 | }
135 | },
136 | subtitle: {
137 | style: {
138 | color: '#E0E0E3',
139 | textTransform: 'uppercase'
140 | }
141 | },
142 | xAxis: {
143 | gridLineColor: '#707073',
144 | labels: {
145 | style: {
146 | color: '#E0E0E3'
147 | }
148 | },
149 | lineColor: '#707073',
150 | minorGridLineColor: '#505053',
151 | tickColor: '#707073',
152 | title: {
153 | style: {
154 | color: '#A0A0A3'
155 |
156 | }
157 | }
158 | },
159 | yAxis: {
160 | gridLineColor: '#707073',
161 | labels: {
162 | style: {
163 | color: '#E0E0E3'
164 | }
165 | },
166 | lineColor: '#707073',
167 | minorGridLineColor: '#505053',
168 | tickColor: '#707073',
169 | tickWidth: 1,
170 | title: {
171 | style: {
172 | color: '#A0A0A3'
173 | }
174 | }
175 | },
176 | tooltip: {
177 | backgroundColor: 'rgba(0, 0, 0, 0.85)',
178 | style: {
179 | color: '#F0F0F0'
180 | }
181 | },
182 | plotOptions: {
183 | series: {
184 | dataLabels: {
185 | color: '#B0B0B3'
186 | },
187 | marker: {
188 | lineColor: '#333'
189 | }
190 | },
191 | boxplot: {
192 | fillColor: '#505053'
193 | },
194 | candlestick: {
195 | lineColor: 'white'
196 | },
197 | errorbar: {
198 | color: 'white'
199 | }
200 | },
201 | legend: {
202 | itemStyle: {
203 | color: '#E0E0E3'
204 | },
205 | itemHoverStyle: {
206 | color: '#FFF'
207 | },
208 | itemHiddenStyle: {
209 | color: '#606063'
210 | }
211 | },
212 | credits: {
213 | style: {
214 | color: '#666'
215 | }
216 | },
217 | labels: {
218 | style: {
219 | color: '#707073'
220 | }
221 | },
222 |
223 | drilldown: {
224 | activeAxisLabelStyle: {
225 | color: '#F0F0F3'
226 | },
227 | activeDataLabelStyle: {
228 | color: '#F0F0F3'
229 | }
230 | },
231 |
232 | navigation: {
233 | buttonOptions: {
234 | symbolStroke: '#DDDDDD',
235 | theme: {
236 | fill: '#505053'
237 | }
238 | }
239 | },
240 |
241 | // scroll charts
242 | rangeSelector: {
243 | buttonTheme: {
244 | fill: '#505053',
245 | stroke: '#000000',
246 | style: {
247 | color: '#CCC'
248 | },
249 | states: {
250 | hover: {
251 | fill: '#707073',
252 | stroke: '#000000',
253 | style: {
254 | color: 'white'
255 | }
256 | },
257 | select: {
258 | fill: '#000003',
259 | stroke: '#000000',
260 | style: {
261 | color: 'white'
262 | }
263 | }
264 | }
265 | },
266 | inputBoxBorderColor: '#505053',
267 | inputStyle: {
268 | backgroundColor: '#333',
269 | color: 'silver'
270 | },
271 | labelStyle: {
272 | color: 'silver'
273 | }
274 | },
275 |
276 | navigator: {
277 | handles: {
278 | backgroundColor: '#666',
279 | borderColor: '#AAA'
280 | },
281 | outlineColor: '#CCC',
282 | maskFill: 'rgba(255,255,255,0.1)',
283 | series: {
284 | color: '#7798BF',
285 | lineColor: '#A6C7ED'
286 | },
287 | xAxis: {
288 | gridLineColor: '#505053'
289 | }
290 | },
291 |
292 | scrollbar: {
293 | barBackgroundColor: '#808083',
294 | barBorderColor: '#808083',
295 | buttonArrowColor: '#CCC',
296 | buttonBackgroundColor: '#606063',
297 | buttonBorderColor: '#606063',
298 | rifleColor: '#FFF',
299 | trackBackgroundColor: '#404043',
300 | trackBorderColor: '#404043'
301 | },
302 |
303 | // special colors for some of the
304 | legendBackgroundColor: 'rgba(0, 0, 0, 0.5)',
305 | background2: '#505053',
306 | dataLabelsColor: '#B0B0B3',
307 | textColor: '#C0C0C0',
308 | contrastTextColor: '#F0F0F3',
309 | maskColor: 'rgba(255,255,255,0.3)'
310 | };
311 |
312 | Highcharts.setOptions(Highcharts.theme);
313 |
314 | // Get widget configurations
315 | var thisWidgetXAxis = JSON.parse(currentSettings.xaxis);
316 | var thisWidgetYAxis = JSON.parse(currentSettings.yaxis);
317 | var thisWidgetTitle = currentSettings.title;
318 | var thisWidgetChartType = currentSettings.chartType;
319 | //console.log('chartType:' + currentSettings.chartType + ' ' + thisWidgetChartType);
320 | var thisWidgetSeries = [];
321 |
322 | for (i = 1; i <= MAX_NUM_SERIES; i++) {
323 | var datasource = currentSettings['series' + i];
324 | if (datasource) {
325 | var serieno = "series" + i + "label";
326 | var label = currentSettings[serieno];
327 | console.log('label: ', label);
328 | var newSeries = {
329 | id: 'series' + i,
330 | name: label,
331 | fillColor: {
332 | linearGradient: {
333 | x1: 0,
334 | y1: 0,
335 | x2: 0,
336 | y2: 1
337 | },
338 | stops: [
339 | [0, Highcharts.getOptions().colors[i - 1]],
340 | //[1, 'rgba(2,0,0,0)']
341 | [1, Highcharts.Color(Highcharts.getOptions().colors[i - 1]).setOpacity(0).get('rgba')]
342 | ]
343 | },
344 |
345 | data: [],
346 | connectNulls: true
347 | };
348 |
349 | thisWidgetSeries.push(newSeries);
350 | }
351 | }
352 |
353 | // Create widget
354 | thisWidgetContainer
355 | .css('height', 60 * self.getHeight() - 10 + 'px');
356 | thisWidgetContainer.css('width', '100%');
357 |
358 | thisWidgetContainer.highcharts({
359 | chart: {
360 | type: thisWidgetChartType,
361 | animation: Highcharts.svg,
362 | marginRight: 20
363 | },
364 | title: {
365 | text: thisWidgetTitle
366 | },
367 | xAxis: thisWidgetXAxis,
368 | yAxis: thisWidgetYAxis,
369 |
370 | plotOptions: {
371 | area: {
372 | marker: {
373 | enabled: false,
374 | symbol: 'circle',
375 | radius: 2,
376 | hover: {
377 | enabled: true
378 | }
379 | },
380 | lineWidth: 2,
381 | states: {
382 | hover: {
383 | lineWidth: 2
384 | }
385 | },
386 | threshold: null
387 | }
388 | },
389 |
390 | tooltip: {
391 | formatter: function() {
392 | return '' + this.series.name + '
' + Highcharts.dateFormat('%Y-%m-%d %H:%M:%S',
393 | this.x) + '
' + Highcharts.numberFormat(this.y, 1);
394 | }
395 | },
396 | series: thisWidgetSeries
397 | });
398 | }
399 |
400 | self.render = function(containerElement) {
401 | $(containerElement).append(thisWidgetContainer);
402 | createWidget();
403 | }
404 |
405 | self.getHeight = function() {
406 | return currentSettings.blocks;
407 | }
408 |
409 | self.onSettingsChanged = function(newSettings) {
410 | currentSettings = newSettings;
411 | createWidget();
412 | }
413 |
414 | self.onCalculatedValueChanged = function(settingName, newValue) {
415 | // console.log(settingName, 'newValue:', newValue);
416 |
417 | var chart = thisWidgetContainer.highcharts();
418 | var series = chart.get(settingName);
419 | if (series) {
420 | var timeframeMS = currentSettings.timeframe * ONE_SECOND_IN_MILIS;
421 | var seriesno = settingName;
422 | var len = series.data.length;
423 | var shift = false;
424 |
425 | // Check if it should shift the series
426 | if (series.data.length > 1) {
427 |
428 | var first = series.data[0].x;
429 | //var last = series.data[series.data.length-1].x;
430 | var last = new Date().getTime();
431 | // Check if time frame is complete
432 | var diff = last - first;
433 | // console.log('last :', last);
434 | // console.log('first:', first);
435 | // console.log('diff :', diff);
436 |
437 | if (last - first > timeframeMS) {
438 | shift = true;
439 | }
440 | }
441 |
442 | if (isNumber(newValue)) { //check if it is a real number and not text
443 | var x = (new Date()).getTime();
444 | // console.log('addPoint:', x,currentSettings[seriesno], Number(newValue));
445 | var plotMqtt = [x, Number(newValue)]; //create the array+ "Y"
446 | series.addPoint(plotMqtt, true, shift);
447 | };
448 | }
449 | }
450 |
451 | self.onDispose = function() {
452 | return;
453 | }
454 | }
455 |
456 | }());
457 |
--------------------------------------------------------------------------------