mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-08 13:24:56 +00:00
2270 lines
104 KiB
JavaScript
2270 lines
104 KiB
JavaScript
/**
|
|
* Controller handling the charts
|
|
*/
|
|
Ext.define('FHEM.controller.ChartController', {
|
|
extend: 'Ext.app.Controller',
|
|
/**
|
|
* maxValue of Y Axis gets saved here as reference
|
|
*/
|
|
maxYValue: 0,
|
|
|
|
/**
|
|
* minValue of Y Axis gets saved here as reference
|
|
*/
|
|
minYValue: 9999999,
|
|
|
|
/**
|
|
* maxValue of Y2 Axis gets saved here as reference
|
|
*/
|
|
maxY2Value: 0,
|
|
|
|
/**
|
|
* minValue of Y2 Axis gets saved here as reference
|
|
*/
|
|
minY2Value: 9999999,
|
|
|
|
refs: [
|
|
{
|
|
selector: 'panel[name=chartformpanel]',
|
|
ref: 'chartformpanel' //this.getChartformpanel()
|
|
},
|
|
{
|
|
selector: 'datefield[name=starttimepicker]',
|
|
ref: 'starttimepicker' //this.getStarttimepicker()
|
|
},
|
|
{
|
|
selector: 'datefield[name=endtimepicker]',
|
|
ref: 'endtimepicker' //this.getEndtimepicker()
|
|
},
|
|
{
|
|
selector: 'button[name=requestchartdata]',
|
|
ref: 'requestchartdatabtn' //this.getRequestchartdatabtn()
|
|
},
|
|
{
|
|
selector: 'button[name=savechartdata]',
|
|
ref: 'savechartdatabtn' //this.getSavechartdatabtn()
|
|
},
|
|
{
|
|
selector: 'chart',
|
|
ref: 'chart' //this.getChart()
|
|
},
|
|
{
|
|
selector: 'chartformpanel',
|
|
ref: 'panel[name=chartformpanel]' //this.getChartformpanel()
|
|
},
|
|
{
|
|
selector: 'linechartpanel',
|
|
ref: 'linechartpanel' //this.getLinechartpanel()
|
|
},
|
|
{
|
|
selector: 'linechartpanel toolbar',
|
|
ref: 'linecharttoolbar' //this.getLinecharttoolbar()
|
|
},
|
|
{
|
|
selector: 'grid[name=chartdata]',
|
|
ref: 'chartdatagrid' //this.getChartdatagrid()
|
|
},
|
|
{
|
|
selector: 'panel[name=maintreepanel]',
|
|
ref: 'maintreepanel' //this.getMaintreepanel()
|
|
},
|
|
{
|
|
selector: 'radiogroup[name=datasourceradio]',
|
|
ref: 'datasourceradio' //this.getDatasourceradio()
|
|
}
|
|
],
|
|
|
|
/**
|
|
* init function to register listeners
|
|
*/
|
|
init: function() {
|
|
this.control({
|
|
'button[name=requestchartdata]': {
|
|
click: this.requestChartData
|
|
},
|
|
'button[name=savechartdata]': {
|
|
click: this.saveChartData
|
|
},
|
|
'button[name=stepback]': {
|
|
click: this.stepchange
|
|
},
|
|
'button[name=stepforward]': {
|
|
click: this.stepchange
|
|
},
|
|
'button[name=resetchartform]': {
|
|
click: this.resetFormFields
|
|
},
|
|
'menuitem[name=deletechartfromcontext]': {
|
|
click: this.deletechart
|
|
},
|
|
'menuitem[name=renamechartfromcontext]': {
|
|
click: this.renamechart
|
|
},
|
|
'treepanel[name=maintreepanel]': {
|
|
itemclick: this.loadsavedchart
|
|
},
|
|
'treeview': {
|
|
drop: this.movenodeintree
|
|
},
|
|
'grid[name=chartdata]': {
|
|
itemclick: this.highlightRecordInChart
|
|
},
|
|
'panel[name=chartpanel]': {
|
|
collapse: this.resizeChart,
|
|
expand: this.resizeChart
|
|
},
|
|
'panel[name=chartformpanel]': {
|
|
collapse: this.resizeChart,
|
|
expand: this.resizeChart
|
|
},
|
|
'panel[name=chartgridpanel]': {
|
|
collapse: this.resizeChart,
|
|
expand: this.resizeChart
|
|
},
|
|
'radiogroup[name=datasourceradio]': {
|
|
change: this.dataSourceChanged
|
|
}
|
|
});
|
|
|
|
},
|
|
|
|
/**
|
|
* reconfigure combos to handle dblog / filelog
|
|
*/
|
|
dataSourceChanged: function(radio, newval, oldval) {
|
|
|
|
var selection;
|
|
if (radio.getChecked()[0]) {
|
|
selection = radio.getChecked()[0].inputValue;
|
|
} else {
|
|
selection = "dblog";
|
|
}
|
|
var me = this,
|
|
devicecombo = radio.up().down('combobox[name=devicecombo]'),
|
|
readingscombo = radio.up().down("combobox[name=yaxiscombo]");
|
|
|
|
if (selection === "filelog") {
|
|
|
|
// disable statistics for the moment
|
|
radio.up().down("combobox[name=yaxisstatisticscombo]").setDisabled(true);
|
|
|
|
// Getting the FileLog Names to show them in Device-Combo
|
|
var fileLogNames = [];
|
|
|
|
Ext.each(FHEM.filelogs, function(log) {
|
|
if (log.REGEXP && log.REGEXP !== "fakelog" && log.NAME !== "") {
|
|
var devObj = { "DEVICE": log.NAME};
|
|
fileLogNames.push(devObj);
|
|
}
|
|
});
|
|
|
|
devicecombo.setValue("");
|
|
devicecombo.getStore().removeAll();
|
|
devicecombo.getStore().add(fileLogNames);
|
|
devicecombo.queryMode = 'local';
|
|
devicecombo.on("select", me.fileLogSelected);
|
|
|
|
readingscombo.setValue();
|
|
readingscombo.getStore().removeAll();
|
|
|
|
} else {
|
|
// enable statistics
|
|
radio.up().down("combobox[name=yaxisstatisticscombo]").setDisabled(false);
|
|
|
|
devicecombo.setValue();
|
|
devicecombo.getStore().removeAll();
|
|
devicecombo.un("select", me.fileLogSelected);
|
|
devicecombo.queryMode = 'remote';
|
|
devicecombo.getStore().load();
|
|
|
|
readingscombo.setValue();
|
|
readingscombo.getStore().removeAll();
|
|
readingscombo.queryMode = 'remote';
|
|
}
|
|
},
|
|
|
|
/**
|
|
* gather filelog information to fill combos
|
|
*/
|
|
fileLogSelected: function(combo, selectionArray) {
|
|
|
|
var readingscombo = combo.up().down("combobox[name=yaxiscombo]"),
|
|
currentlogfile;
|
|
if (selectionArray[0]) {
|
|
var logname = selectionArray[0].data.DEVICE;
|
|
Ext.each(FHEM.filelogs, function(log) {
|
|
if (log.NAME === logname) {
|
|
// found the filelog entry, getting the logfile to load values
|
|
currentlogfile = log.currentlogfile;
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
if (!Ext.isEmpty(currentlogfile)) {
|
|
// retrieve the filelog, parse its containing fields
|
|
readingscombo.setLoading(true);
|
|
|
|
var date = new Date(),
|
|
endtime = Ext.Date.format(date, 'Y-m-d_H:i:s');
|
|
starttime = Ext.Date.add(date, Ext.Date.HOUR, -24);
|
|
starttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s');
|
|
|
|
// logfiles can have absolute or relative pathes...
|
|
if (Ext.String.startsWith(currentlogfile, "/") === true) {
|
|
// we need to get out of the relative fhem directory,
|
|
// as the get command wont support absolute pathes by default...
|
|
currentlogfile = "../../../../../../../../" + currentlogfile;
|
|
}
|
|
|
|
Ext.Ajax.request({
|
|
method: 'GET',
|
|
disableCaching: false,
|
|
url: '../../../fhem?cmd=get%20Logfile%20' + currentlogfile + '%20-%20' + starttime + '%20' + endtime + '&XHR=1',
|
|
success: function(response){
|
|
if (response && response.responseText) {
|
|
var responseArr = response.responseText.split(/\n/),
|
|
keyObjArray = [],
|
|
keyArray = [];
|
|
|
|
Ext.each(responseArr, function(row) {
|
|
// the first column is always the timestamp, followed by device and multiple key:value
|
|
var splitArr = row.split(" ");
|
|
Ext.each(splitArr, function(key) {
|
|
if (key.lastIndexOf(":") + 1 === key.length &&
|
|
!Ext.Array.contains(keyArray, key.replace(":", ""))) {
|
|
keyArray.push(key.replace(":", ""));
|
|
}
|
|
});
|
|
});
|
|
|
|
Ext.Array.sort(keyArray);
|
|
Ext.each(keyArray, function(key) {
|
|
var obj = {"READING": key};
|
|
keyObjArray.push(obj);
|
|
});
|
|
|
|
//reconfigure readings store
|
|
if (keyObjArray.length > 0) {
|
|
readingscombo.getStore().add(keyObjArray);
|
|
readingscombo.queryMode = 'local';
|
|
|
|
} else {
|
|
Ext.create('Ext.window.Window', {
|
|
width: 400,
|
|
height: 400,
|
|
autoScroll: true,
|
|
title: 'Error',
|
|
html: "No Readings found in the last 24 hours for this Logfile! The used Command was: <br>" +
|
|
"get Logfile " + currentlogfile + " - " + starttime + " " + endtime + "<br>" +
|
|
"The Response was: <br><div>" + response.responseText + "</div>"
|
|
}).show();
|
|
}
|
|
readingscombo.setLoading(false);
|
|
} else {
|
|
readingscombo.setLoading(false);
|
|
Ext.create('Ext.window.Window', {
|
|
width: 400,
|
|
height: 400,
|
|
autoScroll: true,
|
|
title: 'Error',
|
|
html: "Could get no data for the requested Logfile! The used Command was: <br>" +
|
|
"get Logfile " + currentlogfile + " - " + starttime + " " + endtime + "<br>" +
|
|
"The Response was: <br><div>" + response.responseText + "</div>"
|
|
}).show();
|
|
}
|
|
}
|
|
});
|
|
|
|
} else {
|
|
Ext.Msg.alert("Error", "No valid LogFile was found for your selection!");
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* Triggers a request to FHEM Module to get the data from Database
|
|
*/
|
|
requestChartData: function(stepchangecalled) {
|
|
|
|
var me = this;
|
|
|
|
//show loadmask
|
|
me.getLinechartpanel().setLoading(true);
|
|
|
|
//timeout needed for loadmask to appear
|
|
window.setTimeout(function() {
|
|
|
|
//getting the necessary values
|
|
var devices = Ext.ComponentQuery.query('combobox[name=devicecombo]'),
|
|
yaxes = Ext.ComponentQuery.query('combobox[name=yaxiscombo]'),
|
|
yaxescolorcombos = Ext.ComponentQuery.query('combobox[name=yaxiscolorcombo]'),
|
|
yaxesfillchecks = Ext.ComponentQuery.query('checkbox[name=yaxisfillcheck]'),
|
|
yaxesstepcheck = Ext.ComponentQuery.query('checkbox[name=yaxisstepcheck]'),
|
|
yaxesstatistics = Ext.ComponentQuery.query('combobox[name=yaxisstatisticscombo]'),
|
|
axissideradio = Ext.ComponentQuery.query('radiogroup[name=axisside]');
|
|
|
|
var starttime = me.getStarttimepicker().getValue(),
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s'),
|
|
endtime = me.getEndtimepicker().getValue(),
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s'),
|
|
dynamicradio = Ext.ComponentQuery.query('radiogroup[name=dynamictime]')[0],
|
|
chartpanel = me.getLinechartpanel(),
|
|
chart = me.getChart();
|
|
|
|
//cleanup chartpanel
|
|
var existingwins = Ext.ComponentQuery.query('window[name=statisticswin]');
|
|
Ext.each(existingwins, function(existingwin) {
|
|
existingwin.destroy();
|
|
});
|
|
|
|
var existingchartgrid = Ext.ComponentQuery.query('panel[name=chartgridpanel]')[0];
|
|
if (!existingchartgrid) {
|
|
var chartdatagrid = Ext.create('FHEM.view.ChartGridPanel', {
|
|
name: 'chartgridpanel',
|
|
minHeight: 200,
|
|
maxHeight: 200,
|
|
collapsed: true
|
|
});
|
|
chartpanel.add(chartdatagrid);
|
|
} else {
|
|
existingchartgrid.down('grid').getStore().removeAll();
|
|
}
|
|
var existingchart = Ext.ComponentQuery.query('panel[name=chartpanel]')[0];
|
|
if (existingchart) {
|
|
existingchart.destroy();
|
|
}
|
|
var store = Ext.create('FHEM.store.ChartStore'),
|
|
proxy = store.getProxy();
|
|
chart = me.createChart(store);
|
|
chartpanel.add(chart);
|
|
|
|
//reset zoomValues
|
|
chartpanel.setLastYmax(null);
|
|
chartpanel.setLastYmin(null);
|
|
chartpanel.setLastXmax(null);
|
|
chartpanel.setLastXmin(null);
|
|
|
|
me.maxYValue = 0;
|
|
me.minYValue = 9999999;
|
|
me.maxY2Value = 0;
|
|
me.minY2Value = 9999999;
|
|
|
|
//check if timerange or dynamic time should be used
|
|
dynamicradio.eachBox(function(box, idx){
|
|
var date = new Date();
|
|
if (box.checked && stepchangecalled !== true) {
|
|
if (box.inputValue === "year") {
|
|
starttime = Ext.Date.parse(date.getUTCFullYear() + "-01-01", "Y-m-d");
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s');
|
|
endtime = Ext.Date.parse(date.getUTCFullYear() + 1 + "-01-01", "Y-m-d");
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s');
|
|
} else if (box.inputValue === "month") {
|
|
starttime = Ext.Date.getFirstDateOfMonth(date);
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s');
|
|
endtime = Ext.Date.getLastDateOfMonth(date);
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s');
|
|
} else if (box.inputValue === "week") {
|
|
date.setHours(0);
|
|
date.setMinutes(0);
|
|
date.setSeconds(0);
|
|
//monday starts with 0 till sat with 5, sund with -1
|
|
var dayoffset = date.getDay() - 1,
|
|
monday,
|
|
nextmonday;
|
|
if (dayoffset >= 0) {
|
|
monday = Ext.Date.add(date, Ext.Date.DAY, -dayoffset);
|
|
} else {
|
|
//we have a sunday
|
|
monday = Ext.Date.add(date, Ext.Date.DAY, -6);
|
|
}
|
|
nextmonday = Ext.Date.add(monday, Ext.Date.DAY, 7);
|
|
|
|
starttime = monday;
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s');
|
|
endtime = nextmonday;
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s');
|
|
|
|
} else if (box.inputValue === "day") {
|
|
date.setHours(0);
|
|
date.setMinutes(0);
|
|
date.setSeconds(0);
|
|
|
|
starttime = date;
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s');
|
|
endtime = Ext.Date.add(date, Ext.Date.DAY, 1);
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s');
|
|
|
|
} else if (box.inputValue === "hour") {
|
|
date.setMinutes(0);
|
|
date.setSeconds(0);
|
|
|
|
starttime = date;
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s');
|
|
endtime = Ext.Date.add(date, Ext.Date.HOUR, 1);
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s');
|
|
} else if (box.inputValue === "lasthour") {
|
|
endtime = date;
|
|
starttime = Ext.Date.add(date, Ext.Date.HOUR, -1);
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s');
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s');
|
|
} else if (box.inputValue === "last24h") {
|
|
endtime = date;
|
|
starttime = Ext.Date.add(date, Ext.Date.HOUR, -24);
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s');
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s');
|
|
} else if (box.inputValue === "last7days") {
|
|
endtime = date;
|
|
starttime = Ext.Date.add(date, Ext.Date.DAY, -7);
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s');
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s');
|
|
} else if (box.inputValue === "lastmonth") {
|
|
endtime = date;
|
|
starttime = Ext.Date.add(date, Ext.Date.DAY, -30);
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s');
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s');
|
|
} else {
|
|
Ext.Msg.alert("Error", "Could not setup the dynamic time.");
|
|
}
|
|
me.getStarttimepicker().setValue(starttime);
|
|
me.getEndtimepicker().setValue(endtime);
|
|
}
|
|
});
|
|
|
|
var i = 0;
|
|
Ext.each(yaxes, function(y) {
|
|
var device = devices[i].getValue(),
|
|
yaxis = yaxes[i].getValue(),
|
|
yaxiscolorcombo = yaxescolorcombos[i].getValue(),
|
|
yaxisfillcheck = yaxesfillchecks[i].checked,
|
|
yaxisstepcheck = yaxesstepcheck[i].checked,
|
|
yaxisstatistics = yaxesstatistics[i].getValue(),
|
|
axisside = axissideradio[i].getChecked()[0].getSubmitValue(),
|
|
logtype = axissideradio[i].up().down("radiogroup[name=datasourceradio]").getChecked()[0].inputValue;
|
|
if(yaxis === "" || yaxis === null) {
|
|
yaxis = yaxes[i].getRawValue();
|
|
}
|
|
|
|
me.populateAxis(i, yaxes.length, device, yaxis, yaxiscolorcombo, yaxisfillcheck,
|
|
yaxisstepcheck, axisside, yaxisstatistics, dbstarttime, dbendtime, logtype);
|
|
i++;
|
|
});
|
|
|
|
}, 300);
|
|
},
|
|
|
|
/**
|
|
* resize the chart to fit the centerpanel
|
|
*/
|
|
resizeChart: function() {
|
|
|
|
var lcp = Ext.ComponentQuery.query('linechartpanel')[0];
|
|
var lcv = Ext.ComponentQuery.query('chart')[0];
|
|
var cfp = Ext.ComponentQuery.query('form[name=chartformpanel]')[0];
|
|
var cdg = Ext.ComponentQuery.query('panel[name=chartgridpanel]')[0];
|
|
|
|
if (lcv) {
|
|
|
|
if (lcp && lcv && cfp && cdg) {
|
|
var lcph = lcp.getHeight(),
|
|
lcpw = lcp.getWidth(),
|
|
cfph = cfp.getHeight(),
|
|
cdgh = cdg.getHeight();
|
|
|
|
if (lcph && lcpw && cfph && cdgh) {
|
|
var chartheight = lcph - cfph - cdgh - 80;
|
|
var chartwidth = lcpw - 5;
|
|
lcv.height = chartheight;
|
|
lcv.width = chartwidth;
|
|
//render after 50ms to get component right
|
|
window.setTimeout(function() {
|
|
if (lcv.series.get(0).hideAll) {
|
|
lcv.series.get(0).hideAll();
|
|
}
|
|
lcv.doComponentLayout();
|
|
if (lcv.series.get(0).showAll) {
|
|
lcv.series.get(0).showAll();
|
|
}
|
|
lcv.redraw();
|
|
}, 50);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* create the base chart
|
|
*/
|
|
createChart: function(store) {
|
|
var me = this;
|
|
|
|
var chart = Ext.create('Ext.panel.Panel', {
|
|
title: 'Chart',
|
|
name: 'chartpanel',
|
|
collapsible: true,
|
|
titleCollapse: true,
|
|
animCollapse: false,
|
|
items: [
|
|
{
|
|
xtype: 'toolbar',
|
|
items: [
|
|
{
|
|
xtype: 'button',
|
|
width: 100,
|
|
text: 'Step back',
|
|
name: 'stepback',
|
|
icon: 'app/resources/icons/resultset_previous.png'
|
|
},
|
|
{
|
|
xtype: 'button',
|
|
width: 100,
|
|
text: 'Step forward',
|
|
name: 'stepforward',
|
|
icon: 'app/resources/icons/resultset_next.png'
|
|
},
|
|
{
|
|
xtype: 'button',
|
|
width: 100,
|
|
text: 'Reset Zoom',
|
|
name: 'resetzoom',
|
|
icon: 'app/resources/icons/delete.png',
|
|
scope: me,
|
|
handler: function(btn) {
|
|
var chart = btn.up().up().down('chart');
|
|
|
|
var existingwins = Ext.ComponentQuery.query('window[name=statisticswin]');
|
|
Ext.each(existingwins, function(existingwin) {
|
|
existingwin.destroy();
|
|
});
|
|
|
|
chart.restoreZoom();
|
|
|
|
chart.axes.get(0).minimum = chart.up().up().getLastYmin();
|
|
chart.axes.get(0).maximum = chart.up().up().getLastYmax();
|
|
chart.axes.get(1).minimum = chart.up().up().getLastY2min();
|
|
chart.axes.get(1).maximum = chart.up().up().getLastY2max();
|
|
chart.axes.get(2).minimum = chart.up().up().getLastXmin();
|
|
chart.axes.get(2).maximum = chart.up().up().getLastXmax();
|
|
|
|
chart.redraw();
|
|
//helper to reshow the hidden items after zooming back out
|
|
if (chart.up().up().artifactSeries && chart.up().up().artifactSeries.length > 0) {
|
|
Ext.each(chart.up().up().artifactSeries, function(serie) {
|
|
serie.showAll();
|
|
Ext.each(serie.group.items, function(item) {
|
|
if (item.type === "circle") {
|
|
item.show();
|
|
item.redraw();
|
|
}
|
|
});
|
|
});
|
|
chart.up().up().artifactSeries = [];
|
|
}
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
xtype: 'chart',
|
|
legend: {
|
|
position: 'right',
|
|
labelFont: '10px Helvetica, sans-serif',
|
|
padding: 4
|
|
},
|
|
axes: [
|
|
{
|
|
type : 'Numeric',
|
|
name : 'yaxe',
|
|
position : 'left',
|
|
fields : [],
|
|
title : '',
|
|
grid : true
|
|
},
|
|
{
|
|
type : 'Numeric',
|
|
name : 'yaxe2',
|
|
position : 'right',
|
|
fields : [],
|
|
title : ''
|
|
},
|
|
{
|
|
type : 'Time',
|
|
name : 'xaxe',
|
|
position : 'bottom',
|
|
fields : [ 'TIMESTAMP' ],
|
|
dateFormat : "Y-m-d H:i:s",
|
|
title : 'Time',
|
|
grid: true
|
|
}
|
|
],
|
|
animate: false,
|
|
shadow: false,
|
|
store: store,
|
|
enableMask: true,
|
|
mask: true,//'vertical',//true, //'horizontal',
|
|
gradients: [{
|
|
id: 'gradientId',
|
|
angle: 90,
|
|
stops: {
|
|
0: {
|
|
color: '#FF0000'
|
|
},
|
|
50: {
|
|
color: '#FFFF00'
|
|
},
|
|
100: {
|
|
color: '#079400'
|
|
}
|
|
}
|
|
}, {
|
|
id: 'gradientId2',
|
|
angle: 0,
|
|
stops: {
|
|
0: {
|
|
color: '#590'
|
|
},
|
|
20: {
|
|
color: '#599'
|
|
},
|
|
100: {
|
|
color: '#ddd'
|
|
}
|
|
}
|
|
}],
|
|
listeners: {
|
|
mousedown: function(evt) {
|
|
// fix for firefox, not dragging images
|
|
evt.preventDefault();
|
|
},
|
|
select: {
|
|
fn: function(chart, zoomConfig, evt) {
|
|
|
|
delete chart.axes.get(2).fromDate;
|
|
delete chart.axes.get(2).toDate;
|
|
chart.up().up().setLastYmax(chart.axes.get(0).maximum);
|
|
chart.up().up().setLastYmin(chart.axes.get(0).minimum);
|
|
chart.up().up().setLastY2max(chart.axes.get(1).maximum);
|
|
chart.up().up().setLastY2min(chart.axes.get(1).minimum);
|
|
chart.up().up().setLastXmax(chart.axes.get(2).maximum);
|
|
chart.up().up().setLastXmin(chart.axes.get(2).minimum);
|
|
|
|
chart.setZoom(zoomConfig);
|
|
chart.mask.hide();
|
|
|
|
//helper hiding series and items which are out of scope
|
|
Ext.each(chart.series.items, function(serie) {
|
|
if (serie.items.length === 0) {
|
|
chart.up().up().artifactSeries.push(serie);
|
|
Ext.each(serie.group.items, function(item) {
|
|
item.hide();
|
|
item.redraw();
|
|
});
|
|
serie.hideAll();
|
|
|
|
} else {
|
|
//creating statistic windows after zooming
|
|
var html,
|
|
count = 0,
|
|
sum = 0,
|
|
average = 0,
|
|
min = 99999999,
|
|
max = 0,
|
|
lastrec,
|
|
diffkwh = 0,
|
|
winwidth = 125,
|
|
winheight = 105;
|
|
Ext.each(serie.items, function(item) {
|
|
if (Ext.isNumeric(item.value[1])) {
|
|
count++;
|
|
sum = sum + item.value[1];
|
|
if (min > item.value[1]) {
|
|
min = item.value[1];
|
|
}
|
|
if (max < item.value[1]) {
|
|
max = item.value[1];
|
|
}
|
|
if (serie.title.indexOf('actual_kwh') >= 0) {
|
|
if (lastrec) {
|
|
var diffhrs = Ext.Date.getElapsed(lastrec.value[0], item.value[0]) / 1000 / 3600;
|
|
diffkwh = diffkwh + diffhrs * lastrec.value[1];
|
|
}
|
|
lastrec = item;
|
|
winwidth = 165,
|
|
winheight = 130;
|
|
}
|
|
}
|
|
});
|
|
average = sum / count;
|
|
|
|
html = '<b>Selected Items: </b>' + count + '<br>';
|
|
html += '<b>Sum: </b>' + Ext.util.Format.round(sum, 5) + '<br>';
|
|
html += '<b>Average: </b>' + Ext.util.Format.round(average, 5) + '<br>';
|
|
html += '<b>Min: </b>' + min + '<br>';
|
|
html += '<b>Max: </b>' + max + '<br>';
|
|
if (serie.title.indexOf('actual_kwh') >= 0) {
|
|
html += '<b>Used kW/h: </b>' + Ext.util.Format.round(diffkwh, 3) + '<br>';
|
|
html += '<b>Costs (at 25c/kWh): </b>' + Ext.util.Format.round(diffkwh * 0.25, 2) + '€<br>';
|
|
}
|
|
|
|
var existingwins = Ext.ComponentQuery.query('window[name=statisticswin]'),
|
|
matchfound = false,
|
|
lastwin,
|
|
win;
|
|
if (existingwins.length > 0) {
|
|
Ext.each(existingwins, function(existingwin) {
|
|
lastwin = existingwin;
|
|
if (existingwin.title === serie.title) {
|
|
existingwin.update(html);
|
|
existingwin.showAt(chart.getWidth() - 145, chart.getPosition()[1] + 8);
|
|
matchfound = true;
|
|
}
|
|
});
|
|
if (!matchfound) {
|
|
win = Ext.create('Ext.window.Window', {
|
|
width: winwidth,
|
|
height: winheight,
|
|
html: html,
|
|
title: serie.title,
|
|
name: 'statisticswin',
|
|
preventHeader: true,
|
|
border: false,
|
|
plain: true
|
|
});
|
|
win.showAt(chart.getWidth() - 145, lastwin.getPosition()[1] + lastwin.getHeight());
|
|
}
|
|
} else {
|
|
win = Ext.create('Ext.window.Window', {
|
|
width: winwidth,
|
|
height: winheight,
|
|
html: html,
|
|
title: serie.title,
|
|
name: 'statisticswin',
|
|
preventHeader: true,
|
|
border: false,
|
|
plain: true
|
|
});
|
|
win.showAt(chart.getWidth() - 145, chart.getPosition()[1] + 8);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
});
|
|
|
|
return chart;
|
|
},
|
|
|
|
/**
|
|
* creating baselines
|
|
*/
|
|
createBaseLine: function(index, basestart, baseend, basefill, basecolor) {
|
|
var me = this,
|
|
chart = me.getChart(),
|
|
store = chart.getStore(),
|
|
yfield = "VALUEBASE" + index;
|
|
|
|
if (!Ext.isEmpty(basestart) && basestart != "null") {
|
|
var baseline = {
|
|
type : 'line',
|
|
name: 'baseline',
|
|
axis : 'left',
|
|
xField : 'TIMESTAMP',
|
|
yField : yfield,
|
|
showInLegend: false,
|
|
highlight: true,
|
|
fill: basefill,
|
|
style: {
|
|
fill : basecolor,
|
|
'stroke-width': 3,
|
|
stroke: basecolor
|
|
},
|
|
tips : {
|
|
trackMouse : true,
|
|
mouseOffset: [1,1],
|
|
showDelay: 1000,
|
|
width : 280,
|
|
height : 50,
|
|
renderer : function(storeItem, item) {
|
|
this.setTitle(' Value: : ' + storeItem.get(yfield) +
|
|
'<br> Time: ' + storeItem.get('TIMESTAMP'));
|
|
}
|
|
}
|
|
};
|
|
chart.series.add(baseline);
|
|
|
|
store.first().set(yfield, basestart);
|
|
|
|
//getting the very last items time
|
|
var time = new Date("1970");
|
|
store.each(function(rec) {
|
|
current = rec.get("TIMESTAMP");
|
|
if (current > time) {
|
|
time = current;
|
|
}
|
|
});
|
|
var item = Ext.create('FHEM.model.ChartModel');
|
|
item.set(yfield, baseend);
|
|
item.set('TIMESTAMP', time);
|
|
store.add(item);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* fill the axes with data
|
|
*/
|
|
populateAxis: function(i, axeslength, device, yaxis, yaxiscolorcombo, yaxisfillcheck, yaxisstepcheck, axisside, yaxisstatistics, dbstarttime, dbendtime, logtype) {
|
|
|
|
var me = this,
|
|
chart = me.getChart(),
|
|
store = chart.getStore(),
|
|
proxy = store.getProxy(),
|
|
yseries,
|
|
generalization = Ext.ComponentQuery.query('radio[boxLabel=active]')[0],
|
|
generalizationfactor = Ext.ComponentQuery.query('combobox[name=genfactor]')[0].getValue();
|
|
|
|
if (i > 0) {
|
|
yseries = me.createSeries('VALUE' + (i + 1), device + " - " + yaxis, yaxisfillcheck, yaxiscolorcombo, axisside);
|
|
} else {
|
|
yseries = me.createSeries('VALUE', device + " - " + yaxis, yaxisfillcheck, yaxiscolorcombo, axisside);
|
|
}
|
|
|
|
var url;
|
|
if (logtype && logtype === "filelog") {
|
|
Ext.each(FHEM.filelogs, function(log) {
|
|
if (log.NAME === device) {
|
|
// found the filelog entry, getting the logfile to load values
|
|
currentlogfile = log.currentlogfile;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
// logfiles can have absolute or relative pathes...
|
|
if (Ext.String.startsWith(currentlogfile, "/") === true) {
|
|
// we need to get out of the relative fhem directory,
|
|
// as the get command wont support absolute pathes by default...
|
|
currentlogfile = "../../../../../../../../" + currentlogfile;
|
|
}
|
|
|
|
url += '../../../fhem?cmd=get%20Logfile%20' +
|
|
currentlogfile + '%20-%20' + dbstarttime +
|
|
'%20' + dbendtime + '%204:' + yaxis + '.*::&XHR=1';
|
|
} else if (!Ext.isDefined(yaxisstatistics) || yaxisstatistics === "none" || Ext.isEmpty(yaxisstatistics)) {
|
|
url += '../../../fhem?cmd=get+' + FHEM.dblogname + '+-+webchart+' + dbstarttime + '+' + dbendtime + '+';
|
|
url +=device + '+timerange+' + "TIMESTAMP" + '+' + yaxis;
|
|
url += '&XHR=1';
|
|
} else { //setup url to get statistics
|
|
url += '../../../fhem?cmd=get+' + FHEM.dblogname + '+-+webchart+' + dbstarttime + '+' + dbendtime + '+';
|
|
url +=device;
|
|
|
|
if (yaxisstatistics.indexOf("hour") === 0) {
|
|
url += '+hourstats+';
|
|
} else if (yaxisstatistics.indexOf("day") === 0) {
|
|
url += '+daystats+';
|
|
} else if (yaxisstatistics.indexOf("week") === 0) {
|
|
url += '+weekstats+';
|
|
} else if (yaxisstatistics.indexOf("month") === 0) {
|
|
url += '+monthstats+';
|
|
} else if (yaxisstatistics.indexOf("year") === 0) {
|
|
url += '+yearstats+';
|
|
}
|
|
|
|
url += 'TIMESTAMP' + '+' + yaxis;
|
|
url += '&XHR=1';
|
|
|
|
}
|
|
|
|
Ext.Ajax.request({
|
|
method: 'GET',
|
|
async: false,
|
|
disableCaching: false,
|
|
url: url,
|
|
success: function(response){
|
|
var json;
|
|
|
|
try {
|
|
// check if db response
|
|
json = Ext.decode(response.responseText);
|
|
} catch(error) {
|
|
// else we got filelog data
|
|
var resultObj = {},
|
|
dataArray = [];
|
|
if (response && response.responseText) {
|
|
var responseArr = response.responseText.split(/\n/);
|
|
|
|
Ext.each(responseArr, function(row) {
|
|
// the first column is always the timestamp, followed by device and key:value
|
|
var timestamp = row.split(" ")[0].replace("_", " "),
|
|
//keyindex = row.split(": ")[0].split(" ").length - 1,
|
|
//key = row.split(": ")[0].split(" ")[keyindex],
|
|
val = row.split(" ")[1];
|
|
|
|
if (timestamp && val) {
|
|
// filling dataarray for chart
|
|
var rowObj = {
|
|
"TIMESTAMP": timestamp,
|
|
"VALUE": val
|
|
};
|
|
dataArray.push(rowObj);
|
|
|
|
}
|
|
});
|
|
|
|
} else {
|
|
Ext.Msg.alert("Error", "No Data for the selected time found in this Logfile!");
|
|
}
|
|
|
|
resultObj.data = dataArray;
|
|
json = resultObj;
|
|
}
|
|
|
|
if (json.success && json.success === "false") {
|
|
Ext.Msg.alert("Error", "Error an adding Y-Axis number " + i + ", error was: <br>" + json.msg);
|
|
} else {
|
|
|
|
//get the current value descriptor
|
|
var valuetext;
|
|
if (yaxisstatistics.indexOf("none") >= 0) {
|
|
if (i === 0) {
|
|
valuetext = 'VALUE';
|
|
yseries.yField = 'VALUE';
|
|
yseries.tips.renderer = me.setSeriesRenderer('VALUE');
|
|
} else {
|
|
valuetext = 'VALUE' + (i + 1);
|
|
yseries.yField = 'VALUE' + (i + 1);
|
|
yseries.tips.renderer = me.setSeriesRenderer('VALUE' + (i + 1));
|
|
}
|
|
} else if (yaxisstatistics.indexOf("sum") > 0) {
|
|
if (i === 0) {
|
|
valuetext = 'SUM';
|
|
yseries.yField = 'SUM';
|
|
yseries.tips.renderer = me.setSeriesRenderer('SUM');
|
|
} else {
|
|
valuetext = 'SUM' + (i + 1);
|
|
yseries.yField = 'SUM' + (i + 1);
|
|
yseries.tips.renderer = me.setSeriesRenderer('SUM' + (i + 1));
|
|
}
|
|
} else if (yaxisstatistics.indexOf("average") > 0) {
|
|
if (i === 0) {
|
|
valuetext = 'AVG';
|
|
yseries.yField = 'AVG';
|
|
yseries.tips.renderer = me.setSeriesRenderer('AVG');
|
|
} else {
|
|
valuetext = 'AVG' + (i + 1);
|
|
yseries.yField = 'AVG' + (i + 1);
|
|
yseries.tips.renderer = me.setSeriesRenderer('AVG' + (i + 1));
|
|
}
|
|
} else if (yaxisstatistics.indexOf("min") > 0) {
|
|
if (i === 0) {
|
|
valuetext = 'MIN';
|
|
yseries.yField = 'MIN';
|
|
yseries.tips.renderer = me.setSeriesRenderer('MIN');
|
|
} else {
|
|
valuetext = 'MIN' + (i + 1);
|
|
yseries.yField = 'MIN' + (i + 1);
|
|
yseries.tips.renderer = me.setSeriesRenderer('MIN' + (i + 1));
|
|
}
|
|
} else if (yaxisstatistics.indexOf("max") > 0) {
|
|
if (i === 0) {
|
|
valuetext = 'MAX';
|
|
yseries.yField = 'MAX';
|
|
yseries.tips.renderer = me.setSeriesRenderer('MAX');
|
|
} else {
|
|
valuetext = 'MAX' + (i + 1);
|
|
yseries.yField = 'MAX' + (i + 1);
|
|
yseries.tips.renderer = me.setSeriesRenderer('MAX' + (i + 1));
|
|
}
|
|
} else if (yaxisstatistics.indexOf("count") > 0) {
|
|
if (i === 0) {
|
|
valuetext = 'COUNT';
|
|
yseries.yField = 'COUNT';
|
|
yseries.tips.renderer = me.setSeriesRenderer('COUNT');
|
|
} else {
|
|
valuetext = 'COUNT' + (i + 1);
|
|
yseries.yField = 'COUNT' + (i + 1);
|
|
yseries.tips.renderer = me.setSeriesRenderer('COUNT' + (i + 1));
|
|
}
|
|
}
|
|
|
|
//as we have the valuetext, we can fill the grid
|
|
//fill the grid with the data
|
|
me.fillChartGrid(json.data, valuetext);
|
|
|
|
var timestamptext;
|
|
if (i === 0) {
|
|
timestamptext = 'TIMESTAMP';
|
|
} else {
|
|
timestamptext = 'TIMESTAMP' + (i + 1);
|
|
}
|
|
|
|
//add records to store
|
|
for (var j = 0; j < json.data.length; j++) {
|
|
|
|
var item = Ext.create('FHEM.model.ChartModel');
|
|
Ext.iterate(item.data, function(key, value) {
|
|
if (key.indexOf("TIMESTAMP") >= 0) {
|
|
item.set(key, json.data[j].TIMESTAMP);
|
|
}
|
|
});
|
|
|
|
var valuestring = eval('json.data[j].' + valuetext.replace(/[0-9]/g, ''));
|
|
|
|
//parseFloat only when we got a numeric value, else textparsing in model will fail
|
|
if (Ext.isNumeric(valuestring)) {
|
|
valuestring = parseFloat(valuestring, 10);
|
|
}
|
|
item.set(valuetext, valuestring);
|
|
item.set(timestamptext, json.data[j].TIMESTAMP);
|
|
|
|
//check if we have to ues steps
|
|
//if yes, create a new record with the same value as the last one
|
|
//and a timestamp 1 millisecond less than the actual record to add.
|
|
//only do this, when last record is from same axis
|
|
if(yaxisstepcheck) {
|
|
if (store.last() && !Ext.isEmpty(store.last().get(valuetext)) && store.last().get(valuetext) !== "") {
|
|
var lastrec = store.last();
|
|
var datetomodify = Ext.Date.parse(json.data[j].TIMESTAMP, "Y-m-d H:i:s");
|
|
var modtimestamp = Ext.Date.add(datetomodify, Ext.Date.MILLI, -1);
|
|
var stepitem = lastrec.copy();
|
|
Ext.iterate(stepitem.data, function(key, value) {
|
|
if (key.indexOf("TIMESTAMP") >= 0) {
|
|
stepitem.set(key, modtimestamp);
|
|
}
|
|
});
|
|
store.add(stepitem);
|
|
}
|
|
}
|
|
store.add(item);
|
|
|
|
//rewrite of valuestring to get always numbers, even when text as value was passed to model
|
|
valuestring = item.get(valuetext);
|
|
|
|
// recheck if our min and max values are still valid
|
|
if (yseries.axis === "left") {
|
|
if (me.minYValue > valuestring) {
|
|
me.minYValue = valuestring;
|
|
}
|
|
if (me.maxYValue < valuestring) {
|
|
me.maxYValue = valuestring;
|
|
}
|
|
} else if (yseries.axis === "right") {
|
|
if (me.minY2Value > valuestring) {
|
|
me.minY2Value = valuestring;
|
|
}
|
|
if (me.maxY2Value < valuestring) {
|
|
me.maxY2Value = valuestring;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (generalization.checked) {
|
|
me.generalizeChartData(generalizationfactor, i);
|
|
}
|
|
|
|
chart.series.add(yseries);
|
|
|
|
}
|
|
},
|
|
failure: function() {
|
|
Ext.Msg.alert("Error", "Error an adding Y-Axis number " + i);
|
|
}
|
|
});
|
|
|
|
//check if we have added the last dataset
|
|
if ((i + 1) === axeslength) {
|
|
//add baselines
|
|
var j = 0,
|
|
basesstart = Ext.ComponentQuery.query('numberfield[name=basestart]'),
|
|
basesend = Ext.ComponentQuery.query('numberfield[name=baseend]'),
|
|
basescolors = Ext.ComponentQuery.query('combobox[name=baselinecolorcombo]'),
|
|
basesfills = Ext.ComponentQuery.query('checkboxfield[name=baselinefillcheck]');
|
|
|
|
Ext.each(basesstart, function(base) {
|
|
var basestart = basesstart[j].getValue(),
|
|
baseend = basesend[j].getValue(),
|
|
basecolor = basescolors[j].getValue(),
|
|
basefill = basesfills[j].checked;
|
|
|
|
me.createBaseLine(j + 1, basestart, baseend, basefill, basecolor);
|
|
|
|
//adjust min and max on y axis
|
|
if (me.maxYValue < basestart) {
|
|
me.maxYValue = basestart;
|
|
}
|
|
if (me.minYValue > basestart) {
|
|
me.minYValue = basestart;
|
|
}
|
|
if (me.maxYValue < baseend) {
|
|
me.maxYValue = baseend;
|
|
}
|
|
if (me.minYValue > baseend) {
|
|
me.minYValue = baseend;
|
|
}
|
|
j++;
|
|
});
|
|
me.doFinalChartLayout(chart);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* do the final layout of chart after all data is loaded
|
|
*/
|
|
doFinalChartLayout: function(chart) {
|
|
var me = this,
|
|
leftaxisconfiguration = Ext.ComponentQuery.query('radiogroup[name=leftaxisconfiguration]')[0].getChecked()[0].inputValue,
|
|
rightaxisconfiguration = Ext.ComponentQuery.query('radiogroup[name=rightaxisconfiguration]')[0].getChecked()[0].inputValue;
|
|
|
|
//remove the old max values of y axis to get a dynamic range
|
|
delete chart.axes.get(0).maximum;
|
|
delete chart.axes.get(0).minimum;
|
|
delete chart.axes.get(1).maximum;
|
|
delete chart.axes.get(1).minimum;
|
|
|
|
|
|
chart.axes.get(0).maximum = me.maxYValue;
|
|
chart.axes.get(1).maximum = me.maxY2Value;
|
|
|
|
// adopt the values from the other y-axis, if we have no values assigned at all
|
|
if (chart.axes.get(0).maximum === 0 && chart.axes.get(1).maximum > 0) {
|
|
chart.axes.get(0).maximum = chart.axes.get(1).maximum;
|
|
}
|
|
if (me.minYValue === 9999999 && me.minY2Value < 9999999) {
|
|
chart.axes.get(0).minimum = me.minY2Value;
|
|
} else {
|
|
chart.axes.get(0).minimum = me.minYValue;
|
|
}
|
|
|
|
if (chart.axes.get(1).maximum === 0 && chart.axes.get(0).maximum > 0) {
|
|
chart.axes.get(1).maximum = chart.axes.get(0).maximum;
|
|
}
|
|
if (me.minY2Value === 9999999 && me.minYValue < 9999999) {
|
|
chart.axes.get(1).minimum = me.minYValue;
|
|
} else {
|
|
chart.axes.get(1).minimum = me.minY2Value;
|
|
}
|
|
|
|
//if user has specified its own range, use it
|
|
if (leftaxisconfiguration === "manual") {
|
|
var leftaxismin = Ext.ComponentQuery.query('numberfield[name=leftaxisminimum]')[0].getValue(),
|
|
leftaxismax = Ext.ComponentQuery.query('numberfield[name=leftaxismaximum]')[0].getValue();
|
|
|
|
if (Ext.isNumeric(leftaxismin) && Ext.isNumeric(leftaxismax)) {
|
|
chart.axes.get(0).minimum = leftaxismin;
|
|
chart.axes.get(0).maximum = leftaxismax;
|
|
} else {
|
|
Ext.Msg.alert("Error", "Please select a valid minimum and maximum for the axis!");
|
|
}
|
|
}
|
|
if (rightaxisconfiguration === "manual") {
|
|
var rightaxismin = Ext.ComponentQuery.query('numberfield[name=rightaxisminimum]')[0].getValue(),
|
|
rightaxismax = Ext.ComponentQuery.query('numberfield[name=rightaxismaximum]')[0].getValue();
|
|
|
|
if (Ext.isNumeric(rightaxismin) && Ext.isNumeric(rightaxismax)) {
|
|
chart.axes.get(1).minimum = rightaxismin;
|
|
chart.axes.get(1).maximum = rightaxismax;
|
|
} else {
|
|
Ext.Msg.alert("Error", "Please select a valid minimum and maximum for the axis!");
|
|
}
|
|
}
|
|
|
|
// set the x axis range dependent on user given timerange
|
|
var starttime = new Date(me.getStarttimepicker().getValue()),
|
|
endtime = new Date(me.getEndtimepicker().getValue());
|
|
|
|
chart.axes.get(2).fromDate = starttime;
|
|
chart.axes.get(2).toDate = endtime;
|
|
chart.axes.get(2).setTitle(Ext.Date.format(starttime, 'Y-m-d H:i:s') + ' - ' + Ext.Date.format(endtime, 'Y-m-d H:i:s'));
|
|
chart.axes.get(2).displaySprite.attr.font = "14px Arial, Helvetica, sans-serif";
|
|
|
|
var timediffhrs = Ext.Date.getElapsed(chart.axes.get(2).fromDate, chart.axes.get(2).toDate) / 1000 / 3600;
|
|
|
|
if (timediffhrs <= 1) {
|
|
chart.axes.get(2).step = [Ext.Date.MINUTE, 10];
|
|
chart.axes.get(2).label.renderer = function(v) {
|
|
return Ext.Date.format(new Date(v), "H:i:s");
|
|
};
|
|
} else if (timediffhrs <= 24) {
|
|
chart.axes.get(2).step = [Ext.Date.HOUR, 1];
|
|
chart.axes.get(2).label.renderer = function(v) {
|
|
return Ext.Date.format(new Date(v), "H:i:s");
|
|
};
|
|
} else if (timediffhrs <= 168) {
|
|
chart.axes.get(2).step = [Ext.Date.DAY, 1];
|
|
chart.axes.get(2).label.renderer = function(v) {
|
|
return Ext.Date.format(new Date(v), "d-m-Y");
|
|
};
|
|
} else if (timediffhrs <= 720) {
|
|
chart.axes.get(2).step = [Ext.Date.DAY, 7];
|
|
chart.axes.get(2).label.renderer = function(v) {
|
|
return Ext.Date.format(new Date(v), "d-m-Y");
|
|
};
|
|
} else if (timediffhrs > 720) {
|
|
chart.axes.get(2).step = [Ext.Date.MONTH, 1];
|
|
chart.axes.get(2).label.renderer = function(v) {
|
|
return Ext.Date.format(new Date(v), "d-m-Y");
|
|
};
|
|
}
|
|
|
|
chart.axes.get(2).processView();
|
|
|
|
me.resizeChart();
|
|
|
|
chart.show();
|
|
|
|
me.getLinechartpanel().setLoading(false);
|
|
},
|
|
|
|
/**
|
|
* create a single series for the chart
|
|
*/
|
|
createSeries: function(yfield, title, fill, color, axisside) {
|
|
|
|
//setting axistitle and fontsize
|
|
var chart = this.getChart(),
|
|
axis;
|
|
|
|
if (axisside === "left") {
|
|
axis = chart.axes.get(0);
|
|
axistitle = this.getChartformpanel().down('textfield[name=leftaxistitle]').getValue();
|
|
} else if (axisside === "right") {
|
|
axis = chart.axes.get(1);
|
|
axistitle = this.getChartformpanel().down('textfield[name=rightaxistitle]').getValue();
|
|
}
|
|
|
|
if (axistitle && axistitle !== "") {
|
|
axis.setTitle(axistitle);
|
|
} else {
|
|
var currenttitle = axis.title;
|
|
|
|
if (currenttitle === "") {
|
|
axis.setTitle(title);
|
|
} else {
|
|
axis.setTitle(axis.title + " / " + title);
|
|
}
|
|
}
|
|
|
|
if (axis.title.length > 80) {
|
|
axis.displaySprite.attr.font = "10px Arial, Helvetica, sans-serif";
|
|
} else if (axis.title.length > 50) {
|
|
axis.displaySprite.attr.font = "12px Arial, Helvetica, sans-serif";
|
|
} else if (axis.title.length > 40) {
|
|
axis.displaySprite.attr.font = "13px Arial, Helvetica, sans-serif";
|
|
} else {
|
|
axis.displaySprite.attr.font = "14px Arial, Helvetica, sans-serif";
|
|
}
|
|
|
|
|
|
//adding linked yfield to axis fields
|
|
axis.fields.push(yfield);
|
|
|
|
var series = {
|
|
type : 'line',
|
|
axis : axisside,
|
|
xField : 'TIMESTAMP',
|
|
yField : yfield,
|
|
title: title,
|
|
showInLegend: true,
|
|
smooth: 0,
|
|
highlight: {
|
|
size: 5,
|
|
radius: 5
|
|
},
|
|
fill: fill,
|
|
style: {
|
|
fill: color,
|
|
// fill: 'url(#gradientId)',
|
|
opacity: 0.7,
|
|
stroke: '#808080',
|
|
'stroke-width': 2
|
|
},
|
|
markerConfig: {
|
|
type: 'circle',
|
|
radius: 2,
|
|
stroke: color,
|
|
fill: color
|
|
},
|
|
showMarkers: true,
|
|
selectionTolerance: 5,
|
|
tips : {
|
|
trackMouse : true,
|
|
mouseOffset: [1,1],
|
|
showDelay: 1000,
|
|
width : 280,
|
|
height : 50,
|
|
renderer : function(storeItem, item) {
|
|
this.setTitle(' Value: : ' + storeItem.get(yfield) +
|
|
'<br> Time: ' + storeItem.get('TIMESTAMP'));
|
|
}
|
|
}
|
|
};
|
|
return series;
|
|
},
|
|
|
|
/**
|
|
* Setup the renderer for displaying values in chart with mouse hover
|
|
*/
|
|
setSeriesRenderer: function(value) {
|
|
|
|
var renderer = function (storeItem, item) {
|
|
this.setTitle(' ' + value + ' : ' + storeItem.get(value) +
|
|
'<br> Time: ' + storeItem.get('TIMESTAMP'));
|
|
};
|
|
|
|
return renderer;
|
|
},
|
|
|
|
/**
|
|
*
|
|
*/
|
|
generalizeChartData: function(generalizationfactor, index) {
|
|
|
|
var store = this.getChart().getStore();
|
|
|
|
this.factorpositive = 1 + (generalizationfactor / 100),
|
|
this.factornegative = 1 - (generalizationfactor / 100),
|
|
this.lastValue = null,
|
|
this.lastItem = null,
|
|
this.recsToRemove = [];
|
|
|
|
Ext.each(store.data.items, function(item) {
|
|
|
|
var value;
|
|
if (index === 0) {
|
|
value = item.get('VALUE');
|
|
} else {
|
|
value = item.get('VALUE' + index);
|
|
}
|
|
var one = this.lastValue / 100;
|
|
var diff = value / one / 100;
|
|
if (diff > this.factorpositive || diff < this.factornegative) {
|
|
if (this.lastItem) {
|
|
if (index === 0) {
|
|
this.lastItem.set('VALUE', this.lastValue);
|
|
} else {
|
|
this.lastItem.set('VALUE' + index, this.lastValue);
|
|
}
|
|
}
|
|
this.lastValue = value;
|
|
this.lastItem = item;
|
|
} else {
|
|
//keep last record
|
|
if (store.last() !== item) {
|
|
if (index === 0) {
|
|
item.set('VALUE', '');
|
|
} else {
|
|
item.set('VALUE' + index, '');
|
|
}
|
|
}
|
|
this.lastValue = value;
|
|
this.lastItem = item;
|
|
}
|
|
}, this);
|
|
|
|
},
|
|
|
|
/**
|
|
* reset the form fields e.g. when loading a new chart
|
|
*/
|
|
resetFormFields: function() {
|
|
|
|
this.getLinechartpanel().axiscounter = 0;
|
|
var fieldset = this.getChartformpanel().down('fieldset[name=axesfieldset]');
|
|
fieldset.removeAll();
|
|
this.getLinechartpanel().createNewYAxis();
|
|
|
|
Ext.ComponentQuery.query('radiofield[name=rb]')[0].setValue(true);
|
|
Ext.ComponentQuery.query('datefield[name=starttimepicker]')[0].reset();
|
|
Ext.ComponentQuery.query('datefield[name=endtimepicker]')[0].reset();
|
|
Ext.ComponentQuery.query('radiofield[name=generalization]')[1].setValue(true);
|
|
|
|
Ext.ComponentQuery.query('numberfield[name=leftaxisminimum]')[0].reset();
|
|
Ext.ComponentQuery.query('numberfield[name=leftaxismaximum]')[0].reset();
|
|
Ext.ComponentQuery.query('numberfield[name=rightaxisminimum]')[0].reset();
|
|
Ext.ComponentQuery.query('numberfield[name=rightaxismaximum]')[0].reset();
|
|
|
|
Ext.ComponentQuery.query('radiogroup[name=leftaxisconfiguration]')[0].items.items[0].setValue(true);
|
|
Ext.ComponentQuery.query('radiogroup[name=rightaxisconfiguration]')[0].items.items[0].setValue(true);
|
|
this.getChartformpanel().down('textfield[name=rightaxistitle]').setValue("");
|
|
this.getChartformpanel().down('textfield[name=leftaxistitle]').setValue("");
|
|
|
|
},
|
|
|
|
/**
|
|
* jump one step back / forward in timerange
|
|
*/
|
|
stepchange: function(btn) {
|
|
var me = this;
|
|
|
|
//reset y-axis max
|
|
me.maxYValue = 0;
|
|
me.minYValue = 9999999;
|
|
me.maxY2Value = 0;
|
|
me.minY2Value = 9999999;
|
|
|
|
var starttime = me.getStarttimepicker().getValue(),
|
|
endtime = me.getEndtimepicker().getValue(),
|
|
dynamicradio = Ext.ComponentQuery.query('radiogroup[name=dynamictime]')[0];
|
|
|
|
if(!Ext.isEmpty(starttime) && !Ext.isEmpty(endtime)) {
|
|
var timediff = Ext.Date.getElapsed(starttime, endtime);
|
|
if(btn.name === "stepback") {
|
|
if (dynamicradio.getValue().rb === "month") {
|
|
me.getEndtimepicker().setValue(Ext.Date.getLastDateOfMonth(Ext.Date.add(endtime, Ext.Date.MONTH, -1)));
|
|
me.getStarttimepicker().setValue(Ext.Date.add(starttime, Ext.Date.MONTH, -1));
|
|
} else {
|
|
me.getEndtimepicker().setValue(starttime);
|
|
var newstarttime = Ext.Date.add(starttime, Ext.Date.MILLI, -timediff);
|
|
me.getStarttimepicker().setValue(newstarttime);
|
|
}
|
|
me.requestChartData(true);
|
|
} else if (btn.name === "stepforward") {
|
|
if (dynamicradio.getValue().rb === "month") {
|
|
me.getEndtimepicker().setValue(Ext.Date.getLastDateOfMonth(Ext.Date.add(endtime, Ext.Date.MONTH, +1)));
|
|
me.getStarttimepicker().setValue(Ext.Date.add(starttime, Ext.Date.MONTH, +1));
|
|
} else {
|
|
me.getStarttimepicker().setValue(endtime);
|
|
var newendtime = Ext.Date.add(endtime, Ext.Date.MILLI, timediff);
|
|
me.getEndtimepicker().setValue(newendtime);
|
|
}
|
|
me.requestChartData(true);
|
|
}
|
|
}
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
* save the current chart to database
|
|
*/
|
|
saveChartData: function() {
|
|
|
|
var me = this;
|
|
|
|
Ext.Msg.prompt("Select a name", "Enter a name to save the Chart", function(action, savename) {
|
|
if (action === "ok" && !Ext.isEmpty(savename)) {
|
|
//replacing spaces in name
|
|
savename = savename.replace(/ /g, "_");
|
|
//replacing + in name
|
|
savename = savename.replace(/\+/g, "_");
|
|
|
|
//getting the necessary values
|
|
var logtypes = Ext.ComponentQuery.query('radiogroup[name=datasourceradio]');
|
|
var devices = Ext.ComponentQuery.query('combobox[name=devicecombo]');
|
|
var yaxes = Ext.ComponentQuery.query('combobox[name=yaxiscombo]');
|
|
var yaxescolorcombos = Ext.ComponentQuery.query('combobox[name=yaxiscolorcombo]');
|
|
var yaxesfillchecks = Ext.ComponentQuery.query('checkbox[name=yaxisfillcheck]');
|
|
var yaxesstepchecks = Ext.ComponentQuery.query('checkbox[name=yaxisstepcheck]');
|
|
var axissideradio = Ext.ComponentQuery.query('radiogroup[name=axisside]');
|
|
var yaxesstatistics = Ext.ComponentQuery.query('combobox[name=yaxisstatisticscombo]');
|
|
|
|
var basesstart = Ext.ComponentQuery.query('numberfield[name=basestart]');
|
|
var basesend = Ext.ComponentQuery.query('numberfield[name=baseend]');
|
|
var basescolors = Ext.ComponentQuery.query('combobox[name=baselinecolorcombo]');
|
|
var basesfills = Ext.ComponentQuery.query('checkboxfield[name=baselinefillcheck]');
|
|
|
|
var starttime = me.getStarttimepicker().getValue(),
|
|
dbstarttime = Ext.Date.format(starttime, 'Y-m-d_H:i:s'),
|
|
endtime = me.getEndtimepicker().getValue(),
|
|
dbendtime = Ext.Date.format(endtime, 'Y-m-d_H:i:s'),
|
|
dynamicradio = Ext.ComponentQuery.query('radiogroup[name=dynamictime]')[0],
|
|
generalization = Ext.ComponentQuery.query('radio[boxLabel=active]')[0],
|
|
generalizationfactor = Ext.ComponentQuery.query('combobox[name=genfactor]')[0].getValue(),
|
|
leftaxisconfiguration = Ext.ComponentQuery.query('radiogroup[name=leftaxisconfiguration]')[0].getChecked()[0].inputValue,
|
|
rightaxisconfiguration = Ext.ComponentQuery.query('radiogroup[name=rightaxisconfiguration]')[0].getChecked()[0].inputValue,
|
|
chart = me.getChart(),
|
|
store = chart.getStore();
|
|
|
|
//setting the start / endtime parameter in the chartconfig to the string of the radiofield, gets parsed on load
|
|
if (this.getStarttimepicker().isDisabled()) {
|
|
dynamicradio.eachBox(function(box, idx) {
|
|
if (box.checked) {
|
|
dbstarttime = box.inputValue;
|
|
dbendtime = box.inputValue;
|
|
}
|
|
});
|
|
}
|
|
|
|
var jsonConfig = '{';
|
|
var i = 0;
|
|
Ext.each(devices, function(dev) {
|
|
|
|
var logtype = logtypes[i].getChecked()[0].inputValue,
|
|
device = dev.getValue(),
|
|
yaxis = yaxes[i].getValue(),
|
|
yaxiscolorcombo = yaxescolorcombos[i].getDisplayValue(),
|
|
yaxisfillcheck = yaxesfillchecks[i].checked,
|
|
yaxisstepcheck = yaxesstepchecks[i].checked,
|
|
yaxisstatistics = yaxesstatistics[i].getValue(),
|
|
axisside = axissideradio[i].getChecked()[0].getSubmitValue(),
|
|
rightaxistitle = me.getChartformpanel().down('textfield[name=rightaxistitle]').getValue(),
|
|
leftaxistitle = me.getChartformpanel().down('textfield[name=leftaxistitle]').getValue();
|
|
//replacing spaces in title
|
|
rightaxistitle = rightaxistitle.replace(/ /g, "_");
|
|
leftaxistitle = leftaxistitle.replace(/ /g, "_");
|
|
//replacing + in title
|
|
rightaxistitle = rightaxistitle.replace(/\+/g, "_");
|
|
leftaxistitle = leftaxistitle.replace(/\+/g, "_");
|
|
|
|
if (i === 0) {
|
|
jsonConfig += '"y":"' + yaxis + '","device":"' + device + '",';
|
|
jsonConfig += '"logtype":"' + logtype + '",';
|
|
jsonConfig += '"yaxiscolorcombo":"' + yaxiscolorcombo + '","yaxisfillcheck":"' + yaxisfillcheck + '",';
|
|
jsonConfig += '"yaxisstepcheck":"' + yaxisstepcheck + '",';
|
|
jsonConfig += '"yaxisside":"' + axisside + '",';
|
|
jsonConfig += '"leftaxistitle":"' + leftaxistitle + '",';
|
|
jsonConfig += '"rightaxistitle":"' + rightaxistitle + '",';
|
|
|
|
if (yaxisstatistics !== "none") {
|
|
jsonConfig += '"yaxisstatistics":"' + yaxisstatistics + '",';
|
|
}
|
|
} else {
|
|
var logtypename = "y" + (i + 1) + "logtype",
|
|
axisname = "y" + (i + 1) + "axis",
|
|
devicename = "y" + (i + 1) + "device",
|
|
colorname = "y" + (i + 1) + "axiscolorcombo",
|
|
fillname = "y" + (i + 1) + "axisfillcheck",
|
|
stepname = "y" + (i + 1) + "axisstepcheck",
|
|
sidename = "y" + (i + 1) + "axisside",
|
|
statsname = "y" + (i + 1) + "axisstatistics";
|
|
|
|
jsonConfig += '"' + axisname + '":"' + yaxis + '","' + devicename + '":"' + device + '",';
|
|
jsonConfig += '"' + logtypename + '":"' + logtype + '",';
|
|
jsonConfig += '"' + colorname + '":"' + yaxiscolorcombo + '","' + fillname + '":"' + yaxisfillcheck + '",';
|
|
jsonConfig += '"' + stepname + '":"' + yaxisstepcheck + '",';
|
|
jsonConfig += '"' + sidename + '":"' + axisside + '",';
|
|
if (yaxisstatistics !== "none") {
|
|
jsonConfig += '"' + statsname + '":"' + yaxisstatistics + '",';
|
|
}
|
|
}
|
|
i++;
|
|
});
|
|
|
|
if(generalization.checked) {
|
|
jsonConfig += '"generalization":"true",';
|
|
jsonConfig += '"generalizationfactor":"' + generalizationfactor + '",';
|
|
}
|
|
|
|
if (leftaxisconfiguration === 'manual') {
|
|
var leftaxismin = Ext.ComponentQuery.query('numberfield[name=leftaxisminimum]')[0].getValue(),
|
|
leftaxismax = Ext.ComponentQuery.query('numberfield[name=leftaxismaximum]')[0].getValue();
|
|
|
|
if (Ext.isNumeric(leftaxismin) && Ext.isNumeric(leftaxismax)) {
|
|
jsonConfig += '"leftaxismin":"' + leftaxismin + '",';
|
|
jsonConfig += '"leftaxismax":"' + leftaxismax + '",';
|
|
} else {
|
|
Ext.Msg.alert("Error", "Left axis configuration is invalid, values will not be saved!");
|
|
}
|
|
}
|
|
|
|
if (rightaxisconfiguration === "manual") {
|
|
var rightaxismin = Ext.ComponentQuery.query('numberfield[name=rightaxisminimum]')[0].getValue(),
|
|
rightaxismax = Ext.ComponentQuery.query('numberfield[name=rightaxismaximum]')[0].getValue();
|
|
|
|
if (Ext.isNumeric(rightaxismin) && Ext.isNumeric(rightaxismax)) {
|
|
jsonConfig += '"rightaxismin":"' + rightaxismin + '",';
|
|
jsonConfig += '"rightaxismax":"' + rightaxismax + '",';
|
|
} else {
|
|
Ext.Msg.alert("Error", "Right axis configuration is invalid, values will not be saved!");
|
|
}
|
|
}
|
|
|
|
var j = 0;
|
|
Ext.each(basesstart, function(base) {
|
|
var basestart = basesstart[j].getValue(),
|
|
baseend = basesend[j].getValue(),
|
|
basecolor = basescolors[j].getDisplayValue(),
|
|
basefill = basesfills[j].checked;
|
|
|
|
j++;
|
|
jsonConfig += '"base' + j + 'start":"' + basestart + '","base' + j + 'end":"' + baseend + '",';
|
|
jsonConfig += '"base' + j + 'color":"' + basecolor + '","base' + j + 'fill":"' + basefill + '",';
|
|
});
|
|
|
|
jsonConfig += '"starttime":"' + dbstarttime + '","endtime":"' + dbendtime + '"}';
|
|
|
|
chart.setLoading(true);
|
|
|
|
//decide if we save to db or to file
|
|
var filelogbool = false,
|
|
dblogbool = false;
|
|
|
|
Ext.each(logtypes, function(typeradio) {
|
|
if (typeradio.getChecked()[0].inputValue === "filelog") {
|
|
filelogbool = true;
|
|
} else {
|
|
dblogbool = true;
|
|
}
|
|
});
|
|
|
|
if (filelogbool === true && dblogbool === false) {
|
|
|
|
// create the current chart object
|
|
var chartobject = {},
|
|
hash = 0,
|
|
k,
|
|
char;
|
|
|
|
// generate hash from savename
|
|
for (k = 0, l = savename.length; k < l; k++) {
|
|
char = savename.charCodeAt(k);
|
|
hash = ((hash<<5)-hash)+char;
|
|
hash |= 0; // Convert to 32bit integer
|
|
}
|
|
|
|
chartobject.ID = hash;
|
|
chartobject.NAME = savename;
|
|
chartobject.TIMESTAMP = Ext.Date.format(new Date(), 'Y-m-d H:i:s');
|
|
chartobject.TYPE = "savedfilelogchart";
|
|
chartobject.VALUE = Ext.decode(jsonConfig);
|
|
|
|
// append the chartobject to the global FHEM.filelogcharts
|
|
FHEM.filelogcharts.push(chartobject);
|
|
|
|
me.updateFileLogCharts(true);
|
|
|
|
} else {
|
|
|
|
var url = '../../../fhem?cmd=get+' + FHEM.dblogname + '+-+webchart+' + dbstarttime + '+' + dbendtime + '+';
|
|
url +=devices[0].getValue() + '+savechart+""+""+' + savename + '+' + jsonConfig + '&XHR=1';
|
|
Ext.Ajax.request({
|
|
method: 'POST',
|
|
disableCaching: false,
|
|
url: url,
|
|
success: function(response){
|
|
chart.setLoading(false);
|
|
var json = Ext.decode(response.responseText);
|
|
if (json.success === "true" || json.data && json.data.length === 0) {
|
|
me.getMaintreepanel().fireEvent("treeupdated");
|
|
Ext.Msg.alert("Success", "Chart successfully saved!");
|
|
} else if (json.msg) {
|
|
Ext.Msg.alert("Error", "The Chart could not be saved, error Message is:<br><br>" + json.msg);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The Chart could not be saved!");
|
|
}
|
|
},
|
|
failure: function() {
|
|
chart.setLoading(false);
|
|
if (json && json.msg) {
|
|
Ext.Msg.alert("Error", "The Chart could not be saved, error Message is:<br><br>" + json.msg);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The Chart could not be saved!");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
}, this);
|
|
|
|
},
|
|
|
|
/**
|
|
* function used to update the filelogcharts by given cmd
|
|
*/
|
|
updateFileLogCharts: function(treeupdate) {
|
|
var me = this,
|
|
chart = me.getChart();
|
|
|
|
// preapre the string for the file
|
|
var finalstring = "FHEM.filelogcharts = " + Ext.encode(FHEM.filelogcharts) + ";;";
|
|
|
|
var cmd = "{ `echo '" + finalstring + "' > www/frontend/www/frontend/app/filelogcharts.js`}";
|
|
// var cmd = "{ `echo '" + finalstring + "' > www/frontenddev/app/filelogcharts.js`}";
|
|
|
|
Ext.Ajax.request({
|
|
method: 'POST',
|
|
disableCaching: false,
|
|
url: '../../../fhem?',
|
|
params: {
|
|
cmd: cmd,
|
|
XHR: 1
|
|
},
|
|
success: function(response){
|
|
if (chart) {
|
|
chart.setLoading(false);
|
|
}
|
|
if (treeupdate) {
|
|
me.getMaintreepanel().fireEvent("treeupdated");
|
|
}
|
|
|
|
if (response.status === 200) {
|
|
Ext.Msg.alert("Success", "Changes successfully saved!");
|
|
} else if (response.statusText) {
|
|
Ext.Msg.alert("Error", "The Changes could not be saved, error Message is:<br><br>" + response.statusText);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The Changes could not be saved!");
|
|
}
|
|
},
|
|
failure: function(response) {
|
|
if (chart) {
|
|
chart.setLoading(false);
|
|
}
|
|
me.getMaintreepanel().setLoading(false);
|
|
if (response.statusText) {
|
|
Ext.Msg.alert("Error", "The Changes could not be saved, error Message is:<br><br>" + response.statusText);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The Changes could not be saved!");
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* loading saved chart data and trigger the load of the chart
|
|
*/
|
|
loadsavedchart: function(treeview, record) {
|
|
if (!record.raw.data) {
|
|
record.raw.data = record.raw;
|
|
}
|
|
var me = this;
|
|
if (record.raw.data && record.raw.data.TYPE &&
|
|
(record.raw.data.TYPE === "savedchart" || record.raw.data.TYPE === "savedfilelogchart" )) {
|
|
var name = record.raw.data.NAME,
|
|
chartdata = record.raw.data.VALUE;
|
|
|
|
if (typeof chartdata !== "object") {
|
|
try {
|
|
chartdata = Ext.decode(chartdata);
|
|
} catch (e) {
|
|
Ext.Msg.alert("Error", "The Chart could not be loaded! RawChartdata was: <br>" + chartdata);
|
|
}
|
|
|
|
}
|
|
|
|
//cleanup the form before loading
|
|
this.resetFormFields();
|
|
|
|
this.getChartformpanel().collapse();
|
|
|
|
if (chartdata && !Ext.isEmpty(chartdata)) {
|
|
|
|
//reset y-axis max
|
|
me.maxYValue = 0;
|
|
me.minYValue = 9999999;
|
|
me.maxY2Value = 0;
|
|
me.minY2Value = 9999999;
|
|
|
|
//count axes
|
|
var axescount = 0;
|
|
Ext.iterate(chartdata, function(key, value) {
|
|
if (key.indexOf("device") >= 0 && value != "null") {
|
|
axescount++;
|
|
}
|
|
});
|
|
|
|
var yaxeslength = Ext.ComponentQuery.query('combobox[name=yaxiscombo]').length;
|
|
while (yaxeslength < axescount) {
|
|
Ext.ComponentQuery.query('linechartpanel')[0].createNewYAxis();
|
|
yaxeslength++;
|
|
}
|
|
|
|
var logtypes = Ext.ComponentQuery.query('radiogroup[name=datasourceradio]');
|
|
var devices = Ext.ComponentQuery.query('combobox[name=devicecombo]');
|
|
var yaxes = Ext.ComponentQuery.query('combobox[name=yaxiscombo]');
|
|
var yaxescolorcombos = Ext.ComponentQuery.query('combobox[name=yaxiscolorcombo]');
|
|
var yaxesfillchecks = Ext.ComponentQuery.query('checkbox[name=yaxisfillcheck]');
|
|
var yaxesstepchecks = Ext.ComponentQuery.query('checkbox[name=yaxisstepcheck]');
|
|
var axissideradio = Ext.ComponentQuery.query('radiogroup[name=axisside]');
|
|
var yaxesstatistics = Ext.ComponentQuery.query('combobox[name=yaxisstatisticscombo]');
|
|
var logtypename;
|
|
|
|
var i = 0;
|
|
Ext.each(yaxes, function(yaxis) {
|
|
|
|
if (i === 0) {
|
|
logtypename = logtypes[i].getChecked()[0].name;
|
|
eval('logtypes[i].setValue({' + logtypename + ': "' + chartdata.logtype + '"})');
|
|
devices[i].setValue(chartdata.device);
|
|
yaxes[i].getStore().getProxy().url = url = '../../../fhem?cmd=get+' + FHEM.dblogname + '+-+webchart+""+""+' + chartdata.device + '+getreadings&XHR=1';
|
|
yaxes[i].setDisabled(false);
|
|
yaxes[i].setValue(chartdata.y);
|
|
yaxescolorcombos[i].setValue(chartdata.yaxiscolorcombo);
|
|
yaxesfillchecks[i].setValue(chartdata.yaxisfillcheck);
|
|
yaxesstepchecks[i].setValue(chartdata.yaxisstepcheck);
|
|
axissideradio[i].items.items[0].setValue(chartdata.yaxisside);
|
|
|
|
if (chartdata.yaxisstatistics && chartdata.yaxisstatistics !== "") {
|
|
yaxesstatistics[i].setValue(chartdata.yaxisstatistics);
|
|
} else {
|
|
yaxesstatistics[i].setValue("none");
|
|
}
|
|
i++;
|
|
} else {
|
|
logtypename = logtypes[i].getChecked()[0].name,
|
|
logtype = "y" + (i + 1) + "logtype",
|
|
axisdevice = "y" + (i + 1) + "device",
|
|
axisname = "y" + (i + 1) + "axis",
|
|
axiscolorcombo = axisname + "colorcombo",
|
|
axisfillcheck = axisname + "fillcheck",
|
|
axisstepcheck = axisname + "stepcheck",
|
|
axisside = axisname + "side",
|
|
axisstatistics = axisname + "statistics";
|
|
eval('logtypes[i].setValue({' + logtypename + ' : chartdata.' + logtype + '})');
|
|
eval('devices[i].setValue(chartdata.' + axisdevice + ')');
|
|
yaxes[i].getStore().getProxy().url = '../../../fhem?cmd=get+' + FHEM.dblogname + '+-+webchart+""+""+' + eval('chartdata.' + axisdevice) + '+getreadings&XHR=1';
|
|
yaxes[i].setDisabled(false);
|
|
eval('yaxes[i].setValue(chartdata.' + axisname + ')');
|
|
eval('yaxescolorcombos[i].setValue(chartdata.' + axiscolorcombo + ')');
|
|
eval('yaxesfillchecks[i].setValue(chartdata.' + axisfillcheck + ')');
|
|
eval('yaxesstepchecks[i].setValue(chartdata.' + axisstepcheck + ')');
|
|
eval('axissideradio[i].items.items[0].setValue(chartdata.' + axisside + ')');
|
|
|
|
if (eval('chartdata.' + axisstatistics) && eval('chartdata.' + axisstatistics) !== "") {
|
|
eval('yaxesstatistics[i].setValue(chartdata.' + axisstatistics + ')');
|
|
} else {
|
|
yaxesstatistics[i].setValue("none");
|
|
}
|
|
i++;
|
|
}
|
|
});
|
|
|
|
//handling baselines
|
|
var basesstart = Ext.ComponentQuery.query('numberfield[name=basestart]'),
|
|
baselinecount = 0;
|
|
|
|
Ext.iterate(chartdata, function(key, value) {
|
|
if (key.indexOf("base") >= 0 && key.indexOf("start") >= 0 && value != "null") {
|
|
baselinecount++;
|
|
}
|
|
});
|
|
|
|
var renderedbaselines = basesstart.length;
|
|
while (renderedbaselines < baselinecount) {
|
|
Ext.ComponentQuery.query('linechartpanel')[0].createNewBaseLineFields();
|
|
renderedbaselines++;
|
|
}
|
|
|
|
i = 0;
|
|
var j = 1;
|
|
basesstart = Ext.ComponentQuery.query('numberfield[name=basestart]'),
|
|
basesend = Ext.ComponentQuery.query('numberfield[name=baseend]'),
|
|
basescolors = Ext.ComponentQuery.query('combobox[name=baselinecolorcombo]'),
|
|
basesfills = Ext.ComponentQuery.query('checkboxfield[name=baselinefillcheck]');
|
|
|
|
Ext.each(basesstart, function(base) {
|
|
var start = parseFloat(eval('chartdata.base' + j + 'start'), 10);
|
|
var end = parseFloat(eval('chartdata.base' + j + 'end'), 10);
|
|
|
|
basesstart[i].setValue(start);
|
|
basesend[i].setValue(end);
|
|
basescolors[i].setValue(eval('chartdata.base' + j + 'color'));
|
|
basesfills[i].setValue(eval('chartdata.base' + j + 'fill'));
|
|
i++;
|
|
j++;
|
|
});
|
|
|
|
//convert time
|
|
var dynamicradio = Ext.ComponentQuery.query('radiogroup[name=dynamictime]')[0],
|
|
st = chartdata.starttime;
|
|
if (st === "year" || st === "month" || st === "week" || st === "day" || st === "hour" ||
|
|
st === "lasthour" || st === "last24h" || st === "last7days" || st === "lastmonth") {
|
|
dynamicradio.eachBox(function(box, idx) {
|
|
if (box.inputValue === st) {
|
|
box.setValue(true);
|
|
}
|
|
});
|
|
} else {
|
|
var start = chartdata.starttime.replace("_", " "),
|
|
end = chartdata.endtime.replace("_", " ");
|
|
this.getStarttimepicker().setValue(start);
|
|
this.getEndtimepicker().setValue(end);
|
|
}
|
|
|
|
var genbox = Ext.ComponentQuery.query('radio[boxLabel=active]')[0],
|
|
genfaccombo = Ext.ComponentQuery.query('combobox[name=genfactor]')[0];
|
|
|
|
if (chartdata.generalization && chartdata.generalization === "true") {
|
|
genbox.setValue(true);
|
|
genfaccombo.setValue(chartdata.generalizationfactor);
|
|
} else {
|
|
genfaccombo.setValue('30');
|
|
genbox.setValue(false);
|
|
}
|
|
|
|
if (chartdata.leftaxismin && chartdata.leftaxismax) {
|
|
Ext.ComponentQuery.query('radiogroup[name=leftaxisconfiguration]')[0].items.items[1].setValue(true);
|
|
Ext.ComponentQuery.query('numberfield[name=leftaxisminimum]')[0].setValue(chartdata.leftaxismin);
|
|
Ext.ComponentQuery.query('numberfield[name=leftaxismaximum]')[0].setValue(chartdata.leftaxismax);
|
|
}
|
|
|
|
if (chartdata.rightaxismin && chartdata.rightaxismax) {
|
|
Ext.ComponentQuery.query('radiogroup[name=rightaxisconfiguration]')[0].items.items[1].setValue(true);
|
|
Ext.ComponentQuery.query('numberfield[name=rightaxisminimum]')[0].setValue(chartdata.rightaxismin);
|
|
Ext.ComponentQuery.query('numberfield[name=rightaxismaximum]')[0].setValue(chartdata.rightaxismax);
|
|
}
|
|
|
|
if (chartdata.rightaxistitle && chartdata.rightaxistitle !== "") {
|
|
//replacing spaces in title
|
|
var rightaxistitle = chartdata.rightaxistitle.replace(/_/g, " ");
|
|
me.getChartformpanel().down('textfield[name=rightaxistitle]').setValue(rightaxistitle);
|
|
}
|
|
|
|
if (chartdata.leftaxistitle && chartdata.leftaxistitle !== "") {
|
|
//replacing spaces in title
|
|
var leftaxistitle = chartdata.leftaxistitle.replace(/_/g, " ");
|
|
me.getChartformpanel().down('textfield[name=leftaxistitle]').setValue(leftaxistitle);
|
|
}
|
|
|
|
this.requestChartData();
|
|
this.getLinechartpanel().setTitle(name);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The Chart could not be loaded! RawChartdata was: <br>" + chartdata);
|
|
}
|
|
|
|
} else if (record.raw.data && record.raw.data.template) {
|
|
//seems we have clicked on a template chart, resetting the form...
|
|
me.resetFormFields();
|
|
if (me.getChart()) {
|
|
me.getChart().getStore().removeAll();
|
|
me.getChart().hide();
|
|
this.getChartformpanel().expand();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Rename a chart
|
|
*/
|
|
renamechart: function(menu, e) {
|
|
var me = this,
|
|
chartid = menu.record.raw.data.ID,
|
|
oldchartname = menu.record.raw.data.NAME;
|
|
|
|
Ext.Msg.prompt("Renaming Chart", "Enter a new name for this Chart", function(action, savename) {
|
|
if (action === "ok" && !Ext.isEmpty(savename)) {
|
|
//replacing spaces in name
|
|
savename = savename.replace(/ /g, "_");
|
|
//replacing + in name
|
|
savename = savename.replace(/\+/g, "_");
|
|
|
|
var url = '../../../fhem?cmd=get+' + FHEM.dblogname + '+-+webchart+""+""+""+renamechart+""+""+' + savename + '+' + chartid + '&XHR=1';
|
|
|
|
if (menu.record.raw.data.TYPE === "savedfilelogchart") {
|
|
if (Ext.Array.contains(FHEM.filelogcharts, menu.record.raw.data) === true) {
|
|
Ext.Array.remove(FHEM.filelogcharts, menu.record.raw.data);
|
|
|
|
var newRec = menu.record.raw.data;
|
|
newRec.NAME = savename;
|
|
FHEM.filelogcharts.push(newRec);
|
|
|
|
me.updateFileLogCharts(true);
|
|
}
|
|
} else {
|
|
Ext.Ajax.request({
|
|
method: 'GET',
|
|
disableCaching: false,
|
|
url: url,
|
|
success: function(response){
|
|
var json = Ext.decode(response.responseText);
|
|
if (json && json.success === "true" || json.data && json.data.length === 0) {
|
|
me.getMaintreepanel().fireEvent("treeupdated");
|
|
Ext.Msg.alert("Success", "Chart successfully renamed!");
|
|
} else if (json && json.msg) {
|
|
Ext.Msg.alert("Error", "The Chart could not be renamed, error Message is:<br><br>" + json.msg);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The Chart could not be renamed!");
|
|
}
|
|
},
|
|
failure: function() {
|
|
if (json && json.msg) {
|
|
Ext.Msg.alert("Error", "The Chart could not be renamed, error Message is:<br><br>" + json.msg);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The Chart could not be renamed!");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delete a chart by its id from the database
|
|
*/
|
|
deletechart: function(menu, e) {
|
|
var me = this,
|
|
chartid = menu.record.raw.data.ID;
|
|
|
|
if (Ext.isDefined(chartid) && chartid !== "") {
|
|
|
|
Ext.create('Ext.window.Window', {
|
|
width: 250,
|
|
layout: 'fit',
|
|
title:'Delete?',
|
|
modal: true,
|
|
items: [
|
|
{
|
|
xtype: 'displayfield',
|
|
value: 'Do you really want to delete this chart?'
|
|
}
|
|
],
|
|
buttons: [{
|
|
text: "Ok",
|
|
handler: function(btn){
|
|
|
|
var url = '../../../fhem?cmd=get+' + FHEM.dblogname + '+-+webchart+""+""+""+deletechart+""+""+' + chartid + '&XHR=1';
|
|
|
|
if (menu.record.raw.data.TYPE === "savedfilelogchart") {
|
|
if (Ext.Array.contains(FHEM.filelogcharts, menu.record.raw.data) === true) {
|
|
Ext.Array.remove(FHEM.filelogcharts, menu.record.raw.data);
|
|
|
|
me.updateFileLogCharts(true);
|
|
btn.up().up().destroy();
|
|
}
|
|
} else {
|
|
Ext.Ajax.request({
|
|
method: 'GET',
|
|
disableCaching: false,
|
|
url: url,
|
|
success: function(response){
|
|
var json = Ext.decode(response.responseText);
|
|
if (json && json.success === "true" || json.data && json.data.length === 0) {
|
|
var rootNode = me.getMaintreepanel().getRootNode();
|
|
var deletedNode = rootNode.findChildBy(function(rec) {
|
|
if (rec.raw.data && rec.raw.data.ID === chartid) {
|
|
return true;
|
|
}
|
|
}, this, true);
|
|
if (deletedNode) {
|
|
deletedNode.destroy();
|
|
}
|
|
Ext.Msg.alert("Success", "Chart successfully deleted!");
|
|
|
|
} else if (json && json.msg) {
|
|
Ext.Msg.alert("Error", "The Chart could not be deleted, error Message is:<br><br>" + json.msg);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The Chart could not be deleted!");
|
|
}
|
|
btn.up().up().destroy();
|
|
},
|
|
failure: function() {
|
|
if (json && json.msg) {
|
|
Ext.Msg.alert("Error", "The Chart could not be deleted, error Message is:<br><br>" + json.msg);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The Chart could not be deleted!");
|
|
}
|
|
btn.up().up().destroy();
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
},
|
|
{
|
|
text: "Cancel",
|
|
handler: function(btn){
|
|
btn.up().up().destroy();
|
|
}
|
|
}]
|
|
}).show();
|
|
} else {
|
|
Ext.Msg.alert("Error", "The Chart could not be deleted, no record id has been found");
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* fill the charts grid with data
|
|
*/
|
|
fillChartGrid: function(jsondata, valuetext) {
|
|
if (jsondata && jsondata[0]) {
|
|
//this.getChartformpanel().collapse();
|
|
|
|
var store = this.getChartdatagrid().getStore(),
|
|
columnwidth = 0,
|
|
storefields = [],
|
|
gridcolumns = [];
|
|
|
|
if (store.model.fields && store.model.fields.length > 0) {
|
|
Ext.each(store.model.getFields(), function(field) {
|
|
storefields.push(field.name);
|
|
});
|
|
}
|
|
var i = 0;
|
|
Ext.each(jsondata, function(dataset) {
|
|
Ext.iterate(dataset, function(key, value) {
|
|
|
|
if (!Ext.Array.contains(storefields, key)) {
|
|
storefields.push(key);
|
|
}
|
|
});
|
|
// we add each dataset a new key for the valuetext
|
|
jsondata[i].valuetext = valuetext;
|
|
i++;
|
|
});
|
|
store.model.setFields(storefields);
|
|
|
|
columnwidth = 99 / storefields.length + "%";
|
|
|
|
Ext.each(storefields, function(key) {
|
|
var column;
|
|
if (key != "TIMESTAMP") {
|
|
column = {
|
|
header: key,
|
|
dataIndex: key,
|
|
// xtype: 'numbercolumn',
|
|
// format:'0.000',
|
|
// renderer: function(value){
|
|
// return parseFloat(value, 10);
|
|
// },
|
|
width: columnwidth
|
|
};
|
|
} else {
|
|
column = {
|
|
header: key,
|
|
dataIndex: key,
|
|
width: columnwidth
|
|
};
|
|
}
|
|
|
|
gridcolumns.push(column);
|
|
});
|
|
|
|
this.getChartdatagrid().reconfigure(store, gridcolumns);
|
|
store.add(jsondata);
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* highlight hoverered record from grid in chart
|
|
*/
|
|
highlightRecordInChart: function(gridview, record) {
|
|
|
|
var recdate = new Date(Ext.Date.parse(record.get("TIMESTAMP"), 'Y-m-d H:i:s')),
|
|
chartstore = this.getChart().getStore(),
|
|
chartrecord,
|
|
found = false,
|
|
highlightSprite;
|
|
chartstore.each(function(rec) {
|
|
if (Ext.Date.isEqual(new Date(rec.get("TIMESTAMP")), recdate)) {
|
|
var valuematcher = record.raw.valuetext,
|
|
gridvaluematcher = valuematcher.replace(/[0-9]/g, '');
|
|
var chartrec = rec.get(valuematcher);
|
|
var gridrec = record.get(gridvaluematcher);
|
|
if (parseInt(chartrec, 10) === parseInt(gridrec, 10)) {
|
|
chartrecord = rec;
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (chartrecord && !Ext.isEmpty(chartrecord)) {
|
|
Ext.each(this.getChart().series.items, function(serie) {
|
|
Ext.each(serie.items, function(sprite) {
|
|
if (sprite.storeItem === chartrecord) {
|
|
highlightSprite = sprite;
|
|
found = true;
|
|
}
|
|
if (found) {
|
|
return;
|
|
}
|
|
});
|
|
if (found) {
|
|
return;
|
|
}
|
|
});
|
|
if (highlightSprite && !Ext.isEmpty(highlightSprite)) {
|
|
Ext.create('Ext.fx.Animator', {
|
|
target: highlightSprite.sprite.el.dom,
|
|
duration: 700, // 10 seconds
|
|
keyframes: {0: {
|
|
strokeWidth: 2
|
|
},
|
|
50: {
|
|
strokeWidth: 70
|
|
},
|
|
|
|
100: {
|
|
strokeWidth: 2
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* handling the moving of nodes in tree, saving new position of saved charts in db
|
|
*/
|
|
movenodeintree: function(treeview, action, collidatingrecord) {
|
|
var me = this,
|
|
unsorted = Ext.ComponentQuery.query('treepanel button[name=unsortedtree]')[0].pressed;
|
|
|
|
//only save orders when in sorted mode
|
|
if (!unsorted) {
|
|
Ext.ComponentQuery.query('treepanel')[0].setLoading(true);
|
|
var rec = action.records[0],
|
|
id = rec.raw.data.ID;
|
|
|
|
if (rec.raw.data && rec.raw.data.ID &&
|
|
(rec.raw.data.TYPE === "savedchart" || rec.raw.data.TYPE === "savedfilelogchart") &&
|
|
!rec.raw.data.template) {
|
|
|
|
var rootNode = this.getMaintreepanel().getRootNode();
|
|
rootNode.cascadeBy(function(node) {
|
|
if (node.raw && node.raw.data && node.raw.data.ID && node.raw.data.ID === id) {
|
|
//updating whole folder to get indexes right
|
|
Ext.each(node.parentNode.childNodes, function(node) {
|
|
var ownerfolder = node.parentNode.data.text,
|
|
index = node.parentNode.indexOf(node);
|
|
|
|
|
|
if (node.raw.data && node.raw.data.ID && node.raw.data.VALUE) {
|
|
var chartid = node.raw.data.ID,
|
|
chartconfig = node.raw.data.VALUE;
|
|
chartconfig.parentFolder = ownerfolder;
|
|
chartconfig.treeIndex = index;
|
|
var encodedchartconfig = Ext.encode(chartconfig),
|
|
url = '../../../fhem?cmd=get+' + FHEM.dblogname + '+-+webchart+""+""+""+updatechart+""+""+' + chartid + '+' + encodedchartconfig + '&XHR=1';
|
|
|
|
// check for filelog or dblog
|
|
if (node.raw.data.TYPE === "savedfilelogchart") {
|
|
|
|
if (Ext.Array.contains(FHEM.filelogcharts, rec.raw.data) === true) {
|
|
Ext.Array.remove(FHEM.filelogcharts, rec.raw.data);
|
|
var newRec = rec.raw.data;
|
|
newRec.parentFolder = ownerfolder;
|
|
newRec.treeIndex = index;
|
|
FHEM.filelogcharts.push(newRec);
|
|
//
|
|
me.updateFileLogCharts();
|
|
}
|
|
|
|
} else {
|
|
Ext.Ajax.request({
|
|
method: 'GET',
|
|
disableCaching: false,
|
|
url: url,
|
|
success: function(response){
|
|
Ext.ComponentQuery.query('treepanel')[0].setLoading(false);
|
|
var json = Ext.decode(response.responseText);
|
|
if (json && json.success === "true" || json.data && json.data.length === 0) {
|
|
//be quiet
|
|
} else if (json && json.msg) {
|
|
Ext.Msg.alert("Error", "The new position could not be saved, error Message is:<br><br>" + json.msg);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The new position could not be saved!");
|
|
}
|
|
},
|
|
failure: function() {
|
|
Ext.ComponentQuery.query('treepanel')[0].setLoading(false);
|
|
if (json && json.msg) {
|
|
Ext.Msg.alert("Error", "The new position could not be saved, error Message is:<br><br>" + json.msg);
|
|
} else {
|
|
Ext.Msg.alert("Error", "The new position could not be saved!");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
Ext.ComponentQuery.query('treepanel')[0].setLoading(false);
|
|
}
|
|
}); |