├── .gitignore ├── LICENSE ├── README.md ├── dynamic-highcharts-plugin-1.png ├── dynamic-highcharts-plugin-2.png ├── plugin_highcharts.js └── plugins_highcharts.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.pyc 3 | venv/ 4 | calendar.py 5 | config.ini 6 | -------------------------------------------------------------------------------- /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 | dynamic-highcharts-plugin-1 10 | 11 | 12 | dynamic-highcharts-plugin-2 13 |

See it in action @ https://goo.gl/hbAXXU

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 | -------------------------------------------------------------------------------- /dynamic-highcharts-plugin-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onlinux/freeboard-dynamic-highcharts-plugin/14e5c014702353fe43f1418cdee1459fb307d48f/dynamic-highcharts-plugin-1.png -------------------------------------------------------------------------------- /dynamic-highcharts-plugin-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onlinux/freeboard-dynamic-highcharts-plugin/14e5c014702353fe43f1418cdee1459fb307d48f/dynamic-highcharts-plugin-2.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------