Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
97512e3
server side and first shot at client side fix
DanCunnington Sep 21, 2016
b53a01a
frontend
DanCunnington Sep 22, 2016
6a5514c
data sent on connection
DanCunnington Sep 22, 2016
388cb7b
improved design
DanCunnington Sep 23, 2016
79b32a5
resolved conflicts
DanCunnington Oct 11, 2016
aca6b24
bug fix for sending one data point
DanCunnington Oct 11, 2016
5d29a48
started new chart.js node
DanCunnington Oct 11, 2016
112e771
new chart working, needs css tidy
DanCunnington Oct 24, 2016
6ef5321
working v1
DanCunnington Oct 25, 2016
090311e
bug fix for found not returning
DanCunnington Oct 25, 2016
3583785
conflicts part 1
DanCunnington Oct 26, 2016
d24f841
tidy up
DanCunnington Oct 26, 2016
295e96b
tidy up
DanCunnington Oct 26, 2016
fbdfb9b
tidy up
DanCunnington Oct 26, 2016
627cf55
clean up
DanCunnington Oct 26, 2016
bb5d819
dynamic bar chart type
DanCunnington Oct 26, 2016
ac8bd77
tidy up
DanCunnington Oct 26, 2016
fd2d94e
bar charts fill
DanCunnington Oct 27, 2016
a0a3f3d
multiple series on line chart
DanCunnington Oct 27, 2016
5b3362f
multiple series working on line chart
DanCunnington Oct 27, 2016
51be301
bar chart working
DanCunnington Oct 27, 2016
fedd866
working
DanCunnington Oct 28, 2016
3c9163b
tidy up, no fill under lines
DanCunnington Oct 28, 2016
3184899
fixes and colour change
DanCunnington Oct 28, 2016
6461b53
configuration reset properly
DanCunnington Oct 31, 2016
f201f2d
colours
DanCunnington Oct 31, 2016
24050f2
time xformat can be configured
DanCunnington Oct 31, 2016
4ec5b2f
bar chart yAxes begins at 0
DanCunnington Oct 31, 2016
93f3829
take out bar chart limit
DanCunnington Nov 1, 2016
aa12dd8
resolved conflicts
DanCunnington Nov 1, 2016
883ed12
tidy up
DanCunnington Nov 1, 2016
2a80a7c
Fix for chart node memory leak
DanCunnington Nov 1, 2016
1ee1d95
removed second output, tooltips, tidy up
DanCunnington Nov 8, 2016
a4ea13d
limit by time and number of points working
DanCunnington Nov 8, 2016
bd3da41
Resolved conflicts
DanCunnington Nov 8, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions dist/css/app.min.css

This file was deleted.

18 changes: 0 additions & 18 deletions dist/dashboard.appcache

This file was deleted.

Binary file removed dist/fonts/FontAwesome.otf
Binary file not shown.
Binary file removed dist/fonts/fontawesome-webfont.eot
Binary file not shown.
685 changes: 0 additions & 685 deletions dist/fonts/fontawesome-webfont.svg

This file was deleted.

Binary file removed dist/fonts/fontawesome-webfont.ttf
Binary file not shown.
Binary file removed dist/fonts/fontawesome-webfont.woff
Binary file not shown.
Binary file removed dist/fonts/fontawesome-webfont.woff2
Binary file not shown.
Binary file removed dist/icon.png
Binary file not shown.
15 changes: 0 additions & 15 deletions dist/index.html

This file was deleted.

510 changes: 0 additions & 510 deletions dist/js/app.min.js

This file was deleted.

56 changes: 28 additions & 28 deletions nodes/ui_chart.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@
label: {value: 'chart'},
chartType: {value: 'line'},
legend: {value: 'false'},
xformat: {value: '%H:%M:%S'},
xformat: {value: 'HH:mm:ss'},
interpolate: {value: 'linear', required:true},
nodata: {value: ''},
ymin: {value: '', validate:function(value) { return value === '' || RED.validators.number(); }},
ymax: {value: '', validate:function(value) { return value === '' || RED.validators.number(); }},
removeOlder: {value: 1, validate:RED.validators.number(), required:true},
removeOlderPoints: {value: '', validate:function(value) { return value === '' || RED.validators.number(); }},
removeOlderUnit: {value: '3600', required:true}
},
inputs:1,
outputs:2,
outputs:1,
align: "right",
icon: "ui_chart.png",
paletteLabel: 'chart',
Expand All @@ -50,28 +51,24 @@
if (!$("#node-input-chartType").val()) {
$("#node-input-chartType").val("line");
}

var oval = $("#node-input-xformat").val();
if (!oval) { $("#node-input-xformat").val("%H:%M:%S"); }
if (!oval) { $("#node-input-xformat").val("HH:mm:ss"); }
var odef = 'custom';
if (oval === "%H:%M:%S") { odef = oval; }
if (oval === "%H:%M") { odef = oval; }
if (oval === "%Y-%m-%d") { odef = oval; }
if (oval === "%d/%m") { odef = oval; }
if (oval === "%a %H:%M") { odef = oval; }

var ohms = {value:"%H:%M:%S", label:"H:M:S", hasValue:false};
var ohm = {value:"%H:%M", label:"H:M", hasValue:false};
var oymd = {value:"%Y-%m-%d", label:"Year-Month-Date", hasValue:false};
var odm = {value:"%d/%m", label:"Date/Month", hasValue:false};
var oahm = {value:"%a %H:%M", label:"Day H:M", hasValue:false};
if (oval === "HH:mm:ss") { odef = oval; }
if (oval === "HH:mm") { odef = oval; }
if (oval === "Y-M-D") { odef = oval; }
if (oval === "D/M") { odef = oval; }
if (oval === "dd HH:mm") { odef = oval; }
var ohms = {value:"HH:mm:ss", label:"HH:mm:ss", hasValue:false};
var ohm = {value:"HH:mm", label:"HH:mm", hasValue:false};
var oymd = {value:"Y-M-D", label:"Year-Month-Date", hasValue:false};
var odm = {value:"D/M", label:"Date/Month", hasValue:false};
var oahm = {value:"dd HH:mm", label:"Day HH:mm", hasValue:false};
var ocus = {value:"custom", label:"custom", icon:"red/images/typedInput/az.png"};

$("#node-input-xformat").typedInput({
default: odef,
types:[ ohms, ohm, oahm, odm, oymd, ocus ]
});

},
oneditsave: function() {
if ($("#node-input-xformat").typedInput('type') !== 'custom') {
Expand Down Expand Up @@ -107,13 +104,20 @@
<label for="node-input-removeOlder">X-axis</label>
<label for="node-input-removeOlder" style="width: auto">last</label>
<input type="text" id="node-input-removeOlder" style="width:50px;">
<select id="node-input-removeOlderUnit" style="width:90px;">
<select id="node-input-removeOlderUnit" style="width:80px;">
<option value="1">seconds</option>
<option value="60">minutes</option>
<option value="3600">hours</option>
<option value="86400">days</option>
<option value="604800">weeks</option>
</select>
<label for="node-input-removeOlderPoints" style="width: auto; margin-left: 10px; margin-right: 10px;">OR</label>
<input type="text" id="node-input-removeOlderPoints" style="width:60px;" placeholder="1000">
<span style="margin-left: 5px;">points</span>

</div>
<div class="form-row">
<label for="node-input-xformat">X-axis Label</label>
<input type="text" id="node-input-xformat" style="width:150px;">
</div>
<div class="form-row">
Expand All @@ -127,10 +131,8 @@
<label for="node-input-interpolate">Interpolate</label>
<select id="node-input-interpolate" style="width:120px;">
<option value="linear">linear</option>
<option value="step-after">step</option>
<option value="basis">b-spline</option>
<option value="cardinal">cardinal</option>
<option value="monotone">cubic</option>
<option value="step">step</option>
<option value="bezier">bezier</option>
</select>
&nbsp;&nbsp;&nbsp;&nbsp;Legend <select id="node-input-legend" style="width:100px;">
<option value="false">None</option>
Expand All @@ -154,11 +156,9 @@
<p>Minimum and Maximum <b>Y</b> axis values are optional. The graph will auto-scale to any values received.</p>
<p>Multiple lines can be shown on the same chart by using a different <code>msg.topic</code>
value on each input message.</p>
<p>The <b>X</b> axis defines a time window. Older data will be automatically removed from the graph.
The axis labels can be formatted using a <a href="https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md" target="_blank">
d3 time formatted</a> string.</p>
<p>The <b>X</b> axis defines a time window or a maximum number of points to display. Older data will be automatically removed from the graph.
The axis labels can be formatted using a <a href="http://momentjs.com/docs/#/displaying/format/" target="_blank">
Moment.js time formatted</a> string.</p>
<p>The <b>Blank label</b> field can be used to display some text before any valid data is received.</p>
<p>The first output contains an array of the chart state that can be persisted if needed. The second
output sends a <code>msg.payload</code> of <b>"restore"</b> that can be used to trigger a restore of
the state of the chart.</p>
<p>The node output contains an array of the chart state that can be persisted if needed. This can be passed into the chart node to display the persisted data.</p>
</script>
123 changes: 93 additions & 30 deletions nodes/ui_chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ module.exports = function(RED) {
RED.nodes.createNode(this, config);
this.chartType = config.chartType || "line";
var node = this;

var group = RED.nodes.getNode(config.group);
if (!group) { return; }
var tab = RED.nodes.getNode(group.config.tab);
if (!tab) { return; }

if (config.width === "0") { delete config.width; }
if (config.height === "0") { delete config.height; }
// number of pixels wide the chart will be... 43 = sizes.sx - sizes.px
var pixelsWide = ((config.width || group.config.width || 6) - 1) * 43 - 15;
//console.log("pixelsWide",pixelsWide);

if (!tab || !group) { return; }
var options = {
Expand All @@ -34,9 +31,9 @@ module.exports = function(RED) {
nodata: config.nodata,
width: config.width || group.config.width || 6,
height: config.height || parseInt(group.config.width/2+1) || 4,
ymin: config.ymin,
ymin: config.ymin || 0,
ymax: config.ymax,
xformat : config.xformat || "%H:%M:%S"
xformat : config.xformat || "HH:mm:SS"
},
convert: function(value, oldValue, msg) {
if (ChartIdList.hasOwnProperty(node.id) && ChartIdList[node.id] !== node.chartType) {
Expand All @@ -48,66 +45,132 @@ module.exports = function(RED) {
oldValue = value;
} else {
value = parseFloat(value);
var point;
if (isNaN(value)) { return oldValue; }
var topic = msg.topic || 'Data';
var series = msg.topic || 'Series 1';
var storageKey = node.id;
var found;
if (!oldValue) { oldValue = []; }
if (!oldValue) { oldValue = [];}
var converted = {};
if (node.chartType === "bar") { // handle bar type data
if (oldValue.length === 0) { oldValue = [{ key:node.id, values:[] }] }
for (var j = 0; j < oldValue[0].values.length; j++) {
if (oldValue[0].values[j][0] === topic) {
oldValue[0].values[j][1] = value;
if (oldValue.length == 0) {
oldValue = [{
key: storageKey,
values: {
data: [],
series: []
}
}]
}
for (var i=0; i<oldValue[0].values.series.length; i++) {
if (oldValue[0].values.series[i] === series) {
oldValue[0].values.data[i] = value;
found = true;
break;
}
}
if (!found) {
oldValue[0].values.push([topic,value]);
oldValue[0].values.series.push(series);
oldValue[0].values.data.push(value);
}
converted.update = false;
converted.updatedValues = oldValue;
}
else { // handle line and area data
else { // Line chart

// Find the chart data
for (var i = 0; i < oldValue.length; i++) {
if (oldValue[i].key === topic) {
if (oldValue[i].key === storageKey) {
found = oldValue[i];
break;
}
}

// Setup the data structure if this is the first time
if (!found) {
found = { key:topic, values:[] };
found = {
key: storageKey,
values: {
series: [],
data: []
}
}
oldValue.push(found);
}

// Create the new point and add to the dataset
// Create series if it doesn't exist
var seriesIndex = found.values.series.indexOf(series);
if (seriesIndex === -1) {
found.values.series.push(series);
found.values.data.push([]);
seriesIndex = found.values.series.indexOf(series);
}

// Add a new point
var time = new Date().getTime();
var point = [time, value];
found.values.push(point);

// Add the data to the correct series
var point = {"x": time, "y": value};
found.values.data[seriesIndex].push(point);


// Remove datapoints older than a certain time
var limitOffsetSec = parseInt(config.removeOlder) * parseInt(config.removeOlderUnit);
var limitTime = new Date().getTime() - limitOffsetSec * 1000;
var pointsLimit = config.removeOlderPoints;
var removed = [];
var removeSeries = [];

oldValue[0].values.data.forEach(function(series, seriesIndex) {
var i = 0;
while (i < series.length && series[i]['x'] < limitTime) { i++; }
if (i > 0) {
series.splice(0, i);
removed.push({seriesIndex: seriesIndex, noPoints: i});
}

var remove = [];
oldValue.forEach(function (series, index) {
var i=0;
while (i<series.values.length && series.values[i][0]<limitTime) { i++; }
if (i) { series.values.splice(0, i); }
if (series.values.length === 0) { remove.push(index); }
// Remove oldest datapoints if length is greater than points limit
if (pointsLimit > 0 && series.length > pointsLimit) {
var noToRemove = series.length - pointsLimit;
series.splice(0, noToRemove);
removed.push({seriesIndex: seriesIndex, noPoints: noToRemove});
}

if (series.length === 0) { removeSeries.push(seriesIndex); }
});

remove.forEach(function (index) {
oldValue.splice(index, 1);
// Ensure series match up
removeSeries.forEach(function(index) {
oldValue[0].values.series.splice(index, 1);
oldValue[0].values.data.splice(index, 1);
});

// if more datapoints than number of pixels wide...
// TODO - warning is not the answer but hey... it's a hint.
// if (found.values.length % pixelsWide === 0) {
// If more datapoints than number of pixels wide...
// if (found.values.data[seriesIndex].length % pixelsWide === 0) {
// node.warn("More than "+found.values.length+" datapoints");
// }

// Return an object including the new point and all the values
converted.update = true;
converted.newPoint = [{
key: series,
update: true,
removedData: removed,
removedSeries: removeSeries,
values: {
data: point
}
}];
converted.updatedValues = oldValue;
}
}
return oldValue;
return converted;
}
};

var done = ui.add(options);
setTimeout(function() {
node.send([null, {payload:"restore", for:node.id}]);
node.emit("input",{payload:"start"}); // trigger a redraw at start to flush out old data.
}, 100);
node.on("close", done);
Expand Down
Loading