Moorings.Chart = {
initChart: function(idPlatform, idInstrument, idVariable, idVariableSpeed, idVariableDirection){
window.chart = null;
var data;
var dataChart;
var qcDataChart;
var titleChart;
var yAxisTitle;
var tooltipUnits;
var seriesOptions = [];
$('#variable_chart_container').hide(0);
$('#chart_container').fadeIn(1000);
var heatmap = Moorings.getParam('heatmap');
var source = {
url: Moorings.PATH + 'requests/get_plotting_data.php',
params: {
id_platform: idPlatform,
id_instrument: idInstrument,
id_variable: idVariable
}
};
this.extraData = {};
if (idVariableSpeed > 0 && idVariableDirection > 0){
var idExtraVariable = (idVariableSpeed == idVariable) ? idVariableDirection : idVariableSpeed;
$.get(source.url, {id_platform: idPlatform, id_instrument: idInstrument, id_variable: idExtraVariable}, (function(response){
data = response;
var parsedJSON = $.parseJSON(data);
this.extraData['data'] = parsedJSON.dataList;
var displayName = parsedJSON.displayName.replace(/CTD\d\-/, " at ") + ' (' + parsedJSON.inputUnits + ')';
displayName = displayName.replace(/(.*) ([\w])(-1)/, "$1/$2");
displayName = displayName.replace(/(.*) \(C\)$/, "$1 (°C)");
this.extraData['displayName'] = displayName;
this.extraData['name'] = parsedJSON.name;
var units = parsedJSON.inputUnits.replace(/(.*) ([\w])(-1)/, "$1/$2");
units = units.replace(/^C$/, "°C");
this.extraData['units'] = units;
}).bind(this));
}
$.get(source.url, source.params, function(response) {
data = response;
var parsedJSON = $.parseJSON(data);
dataChart = parsedJSON.dataList;
titleChart = parsedJSON.displayName.replace(/CTD\d\-/, " at ") + ' (' + parsedJSON.inputUnits + ')';
titleChart = titleChart.replace(/(.*) ([\w])(-1)/, "$1/$2");
titleChart = titleChart.replace(/(.*) \(C\)$/, "$1 (°C)");
tooltipUnits = parsedJSON.inputUnits;
tooltipUnits = tooltipUnits.replace(/(.*) ([\w])(-1)/, "$1/$2");
tooltipUnits = tooltipUnits.replace(/^C$/, "°C");
yAxisTitle = parsedJSON.name;
$('#chart').css('height', '500px');
if (dataChart) {
if ( /*qcDataChart*/ false)
Moorings.Chart.createChart(dataChart, qcDataChart, titleChart, yAxisTitle, tooltipUnits);
else {
if (undefined !== dataChart.depthDimensionData) {
var variable = {
name: parsedJSON.name,
standardName: parsedJSON.standardName,
inputUnits: tooltipUnits
};
if (titleChart.indexOf(' at ') > 0)
titleChart = titleChart.substring(0, titleChart.indexOf(' at '));
Moorings.Chart.createProfileChart(dataChart, titleChart, variable);
} else {
if (heatmap === "") {
seriesOptions[0] = {
name: yAxisTitle,
data: dataChart.timeDimensionData
};
Moorings.Chart.createChartWithoutQC(seriesOptions, titleChart, yAxisTitle, tooltipUnits, null, undefined, source, true, true);
} else {
var heatmapVariable = {
name: yAxisTitle,
standardName: titleChart,
inputUnits: tooltipUnits
};
Moorings.Chart.createHeatmapChart(dataChart.timeDimensionData, titleChart, heatmapVariable);
}
}
}
} else {
$('#chart').html('
No data avaible for this variable
');
}
});
},
addClickHandlers: function() {
$(document).on('click', '.back_button', function(e) {
e.preventDefault();
Moorings.delParam("id_variable");
});
$(document).on('click', '.variable_plotting_link', function(e) {
e.preventDefault();
var clickedVar = $(this);
var idVariable = clickedVar.attr('id');
var idPlatform = $('.platform_container').attr('id');
var idInstrument = clickedVar.parent().attr('id');
window.location.href = "?seccion=observingFacilities&facility=mooring&tab=chart&id=" + idPlatform + "&id_instrument=" + idInstrument + "&id_variable=" + idVariable;
});
},
createChart: function(dataChart, qcDataChart, titleChart, yAxisTitle, tooltipUnits) {
window.chart = new Highcharts.StockChart({
chart: {
renderTo: 'chart'
},
plotOptions: {
line: {
dataGrouping: {
approximation: 'open'
}
},
column: {
dataGrouping: {
approximation: 'open'
}
}
},
rangeSelector: {
selected: 1,
buttons: [{
type: 'day',
count: 1,
text: '1 d'
}, {
type: 'day',
count: 3,
text: '3 d'
}, {
type: 'week',
count: 1,
text: '1 w'
}, {
type: 'month',
count: 1,
text: '1 m'
}, {
type: 'all',
text: 'All'
}]
},
tooltip: {
formatter: function() {
var s = '' + Highcharts.dateFormat('%A %b %e, %Y %H:%M:%S', this.x) + '';
$.each(this.points, function(i, point) {
if (i % 2 === 0 && point.y != 9)
s += '
' + yAxisTitle + ' = ' + point.y + ' ' + tooltipUnits;
else
s += '
' + 'QC_' + yAxisTitle + ' = ' + point.y;
});
return s;
},
shared: true
},
legend: {
enabled: true,
align: 'right',
borderColor: 'black',
borderWidth: 2,
layout: 'vertical',
verticalAlign: 'top',
y: 75,
shadow: true
},
title: {
text: titleChart
},
xAxis: {
// type:'datetime',
maxZoom: 60000,
tickPixelInterval: 100
},
yAxis: [{
// type: 'column',
title: {
text: yAxisTitle + ' (' + tooltipUnits + ')'
},
height: 150
}, {
min: 0,
max: 9.0,
title: {
text: 'QC_' + yAxisTitle
},
type: 'column',
top: 250,
height: 150,
offset: 0
}],
series: [{
name: yAxisTitle + ' (' + tooltipUnits + ')',
data: dataChart
}, {
name: 'QC_' + yAxisTitle,
yAxis: 1,
data: qcDataChart,
step: true
}]
});
},
createChartWithoutQC: function(dataChart, titleChart, yAxisTitle, tooltipUnits, displayNamesDict, container, source, show_summary, show_heatmap) {
/*if (tooltipUnits == 'degree'){
// unwrap values to remove spikes
for (var i=0, l= dataChart.length; i < l; i++){
dataChart[i].data = unwrapbidimensional(dataChart[i].data, 360);
}
}*/
show_summary = typeof show_summary !== 'undefined' ? show_summary : false;
show_heatmap = typeof show_heatmap !== 'undefined' ? show_heatmap : false;
container = typeof container !== 'undefined' ? container : $('#chart');
container.highcharts('StockChart', {
socibSource: source,
center: 180,
chart: {
renderTo: container
/*,
zoomType: 'y',
events: {
selection: function(event) {
event.preventDefault();
if (event.yAxis && event.yAxis[0] && event.yAxis[0].min) {
var center = (event.yAxis[0].min + event.yAxis[0].max)/2;
console.log(center);
this.options.center = center;
for (var i=0, l= dataChart.length; i < l; i++){
dataChart[i].data = wrapbidimensional(dataChart[i].data, 360, center);
this.series[i].setData(dataChart[i].data,true);
}
}
}
}
*/
// alignTicks: false
},
plotOptions: {
line: {
dataGrouping: {
enabled: false,
approximation: 'open'
}
}
},
rangeSelector: {
selected: 1,
buttons: [{
type: 'day',
count: 1,
text: '1 d'
}, {
type: 'day',
count: 3,
text: '3 d'
}, {
type: 'week',
count: 1,
text: '1 w'
}, {
type: 'month',
count: 1,
text: '1 m'
}, {
type: 'all',
text: 'All'
}]
},
legend: {
enabled: true,
align: 'right',
borderColor: 'black',
borderWidth: 2,
layout: 'vertical',
verticalAlign: 'top',
y: 75,
shadow: true
},
title: {
text: titleChart
},
xAxis: {
// type:'datetime',
maxZoom: 60000,
tickPixelInterval: 100
},
yAxis: [{
title: {
text: yAxisTitle + ' (' + tooltipUnits + ')'
}
}],
series: dataChart,
tooltip: {
formatter: function() {
var s = '' + Highcharts.dateFormat('%A %b %e, %Y %H:%M:%S', this.x) + '';
$.each(this.points, function(i, point) {
var displayName = '';
if (null !== displayNamesDict) {
if (null !== displayNamesDict[point.series.name + '+' + point.series.index]) {
var serieName = point.series.name.match(/.*\s+at\s+.*/) ? point.series.name.split(/\s+at\s+/)[0] : point.series.name;
displayName = displayNamesDict[point.series.name + '+' + point.series.index].split('+') + ' on ' + serieName;
} else {
displayName = displayNamesDict[point.series.name] + ' on ' + point.series.name;
}
} else {
displayName = point.series.name;
}
s += '
' + displayName + ' = ' + point.y + ' ' + tooltipUnits;
s += Moorings.Chart.getExtraTooltipData(point);
});
return s;
},
shared: true
}
});
// add summary
if (show_summary) {
var dataValues;
var summary = [];
var variables;
$.each(dataChart, function(i, serie) {
dataValues = _.map(serie.data, function(value) {
return value[1];
});
// console.log('Serie ' + serie.name);
// console.log(' - Length of values: ' + dataValues.length);
var nullSamples = _.reduce(dataValues, function(memo, value) {
return memo + (value === null);
}, 0);
// console.log(' - Null values: ' + nullSamples);
var nullSamplesPercentage = nullSamples * 100 / dataValues.length;
// console.log(' - Null values %:' + nullSamplesPercentage);
dataValues = _.without(dataValues, null);
dataValues.sort(function(a, b) {
return a - b;
});
var half = Math.floor(dataValues.length / 2);
var median = dataValues.length % 2 ? dataValues[half] : (dataValues[half - 1] + dataValues[half]) / 2.0;
var average = _.reduce(dataValues, function(memo, value) {
return memo + value;
}, 0) / dataValues.length;
var min = _.min(dataValues);
var max = _.max(dataValues);
// console.log(' - Min: ' + min);
// console.log(' - Max: ' + max);
// console.log(' - Median: ' + median);
// console.log(' - Average: ' + average);
variables = [];
if (dataChart.length > 1)
variables.push(serie.name);
variables.push('Min: ' + min.toFixed(2) + ' ' + tooltipUnits + '');
variables.push('Max: ' + max.toFixed(2) + ' ' + tooltipUnits + '');
variables.push('Median: ' + median.toFixed(2) + ' ' + tooltipUnits + '');
variables.push('Average: ' + average.toFixed(2) + ' ' + tooltipUnits + '');
variables.push('No data samples: ' + nullSamplesPercentage.toFixed(2) + '%' + '');
summary.push(variables.join(' | '));
});
$(container).after('' + summary.join('
') + '
');
}
if (show_heatmap) {
var button_heatmapchart = $("");
button_heatmapchart.find('button').click(function(event) {
container.html('');
var heatmapVariable = {
name: yAxisTitle,
standardName: titleChart,
inputUnits: tooltipUnits
};
Moorings.Chart.createHeatmapChart(dataChart[0].data, titleChart, heatmapVariable);
$(this).hide();
});
$(container).after(button_heatmapchart);
}
},
getExtraTooltipData: function(point){
if (Moorings.Chart.extraData && Moorings.Chart.extraData.hasOwnProperty('data')){
var data = Moorings.Chart.extraData['data'];
var i, value, dataChart, searchValue;
if (undefined === data.depthDimensionData){
dataChart = data.timeDimensionData;
searchValue = point.x;
if (point.hasOwnProperty('value'))
searchValue += point.y * 60 * 60 * 1000;
for (i=0, l=dataChart.length; i= searchValue){
value = dataChart[i][1] + " " + Moorings.Chart.extraData['units'];
if (!dataChart[i][1]){
value = 'No value';
}else if (Moorings.Chart.extraData['units'] == 'degree'){
value = Moorings.Chart.degreeToDirection(dataChart[i][1]) + " (" + value + ")";
}
return "
" + Moorings.Chart.extraData['name'] + " = " + value;
}
}
}else{
var searchDepth = point.series.options.depth || data.depthDimensionData[0];
for (var d=0, ld=data.depthDimensionData.length; d= searchValue){
value = dataChart[i][1] + " " + Moorings.Chart.extraData['units'];
if (!dataChart[i][1]){
value = 'No value';
}else if (Moorings.Chart.extraData['units'] == 'degree'){
value = Moorings.Chart.degreeToDirection(dataChart[i][1]) + " (" + value + ")";
}
return "
" + Moorings.Chart.extraData['name'] + " = " + value;
}
}
break;
}
}
}
}
return '';
},
degreeToDirection: function(degree){
var directions = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
var degreeAdjusted = (degree + 11.25) % 360;
for (var i = 0; i < 16; i++) {
if (degreeAdjusted < 22.50 * (i + 1)) {
return directions[i];
}
}
},
createProfileChart: function(data, title, variable, container) {
container = typeof container !== 'undefined' ? container : $('#chart');
var depthDimension = data.depthDimensionData.length;
var maxDepth = data.depthDimensionData[depthDimension - 1];
var profiles = {};
var time;
var profileData;
var colors = ["#2f7ed8", "#0d233a", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"];
var colorindex = 0;
var timesDepth = [];
var max_profile = -1;
// Convert data to profiles
for (var i = 0, l = data.timeDimensionData[0].length; i < l; i++) {
time = data.timeDimensionData[0][i][0] / 1000;
max_profile = -1;
profileData = {};
profileData.DEPTH = data.depthDimensionData;
profileData[variable.name] = new Array(depthDimension);
for (var j = 0; j < depthDimension; j++) {
profileData[variable.name][j] = data.timeDimensionData[j][i][1];
if (data.timeDimensionData[j][i][1] && max_profile < data.timeDimensionData[j][i][1])
max_profile = data.timeDimensionData[j][i][1];
}
if (variable.inputUnits == 'degree')
profileData[variable.name] = unwrap(profileData[variable.name], 360, profileData[variable.name][0]);
if (max_profile > -1) {
profiles[time] = {
depth: maxDepth,
selected: false,
data: profileData
};
timesDepth.push([time, max_profile]);
}
}
// create div containers
container.css('height', '650px');
container.append("Profiler: " + title + "
");
var container_nav = $("");
container.append(container_nav);
var container_chart = $("");
container.append(container_chart);
// New profiles navigator
ProfilesViewerNavigation.createNavigation(container_nav, {
active: true
}, timesDepth, profiles, 2, variable);
// Create chart
ProfilesViewerChart.createChart(container_chart, variable, profiles, 0);
// Subscribe to navigator events to select and unselect profiles
PubSub.subscribe("selectNavigationProfile", function(msg, time) {
if (profiles[time].selected) {
profiles[time].selected = false;
setTimeout(function() {
PubSub.publish("unselectProfile", time);
}, 0);
} else {
profiles[time].selected = true;
profiles[time].color = colors[colorindex++ % 10];
PubSub.publish("selectProfile", time);
}
});
PubSub.subscribe("selectNavigationProfiles", function(msg, data) {
var color = colors[colorindex++ % 10];
_.map(profiles, function(profile, key) {
var profile_time = parseFloat(key);
var select = !profile.selected;
// data could be min+max object or an array of times
if (data.min) {
select = select && profile_time >= data.min && profile_time <= data.max;
} else {
select = _.contains(data, profile_time);
}
if (select) {
profile.selected = true;
profile.color = color;
}
return null;
}, this);
PubSub.publish("selectProfiles", data);
});
var buton_reset = $("");
buton_reset.find('button').click(function(event) {
for (var key in profiles) {
var profile = profiles[key];
if (profile.selected) {
profile.selected = false;
}
}
PubSub.publish("unselectProfiles", {});
});
container.append(buton_reset);
var buton_oldchart = $("");
buton_oldchart.find('button').click(function(event) {
container_nav.remove();
container_chart.remove();
container.html('');
container.css('height', '500px');
var seriesOptions = [];
$.each(data.timeDimensionData, function(i, dataList) {
seriesOptions[i] = {
name: variable.name + ' at ' + data.depthDimensionData[i] + ' m',
data: dataList,
depth: data.depthDimensionData[i],
};
});
Moorings.Chart.createChartWithoutQC(seriesOptions, title, variable.standardName, variable.inputUnits, null, undefined, null, true, true);
});
container.append(buton_oldchart);
// Select last profile (initial profile)
profiles[time].selected = true;
profiles[time].color = colors[colorindex++];
PubSub.publish("selectProfile", time);
},
getSeriesSummary: function(serie, indexValues) {
indexValues = typeof indexValues !== 'undefined' ? indexValues : 1;
var dataValues = _.map(serie.data, function(value) {
return value[indexValues];
});
var nullSamples = _.reduce(dataValues, function(memo, value) {
return memo + (value === null);
}, 0);
var nullSamplesPercentage = nullSamples * 100 / dataValues.length;
dataValues = _.without(dataValues, null);
dataValues.sort(function(a, b) {
return a - b;
});
var half = Math.floor(dataValues.length / 2);
var median = dataValues.length % 2 ? dataValues[half] : (dataValues[half - 1] + dataValues[half]) / 2.0;
var average = _.reduce(dataValues, function(memo, value) {
return memo + value;
}, 0) / dataValues.length;
var min = _.min(dataValues);
var max = _.max(dataValues);
return {
nullSamplesPercentage: nullSamplesPercentage,
median: median,
average: average,
min: min,
max: max
};
},
createHeatmapChart: function(data, title, variable, container) {
(function(H) {
var wrap = H.wrap,
seriesTypes = H.seriesTypes;
/**
* Recursively builds a K-D-tree
*/
function KDTree(points, depth) {
var axis, median, length = points && points.length;
if (length) {
// alternate between the axis
axis = ['plotX', 'plotY'][depth % 2];
// sort point array
points.sort(function(a, b) {
return a[axis] - b[axis];
});
median = Math.floor(length / 2);
// build and return node
return {
point: points[median],
left: KDTree(points.slice(0, median), depth + 1),
right: KDTree(points.slice(median + 1), depth + 1)
};
}
}
/**
* Recursively searches for the nearest neighbour using the given K-D-tree
*/
function nearest(search, tree, depth) {
var point = tree.point,
axis = ['plotX', 'plotY'][depth % 2],
tdist,
sideA,
sideB,
ret = point,
nPoint1,
nPoint2;
// Get distance
point.dist = Math.pow(search.plotX - point.plotX, 2) +
Math.pow(search.plotY - point.plotY, 2);
// Pick side based on distance to splitting point
tdist = search[axis] - point[axis];
sideA = tdist < 0 ? 'left' : 'right';
// End of tree
if (tree[sideA]) {
nPoint1 = nearest(search, tree[sideA], depth + 1);
ret = (nPoint1.dist < ret.dist ? nPoint1 : point);
sideB = tdist < 0 ? 'right' : 'left';
if (tree[sideB]) {
// compare distance to current best to splitting point to decide wether to check side B or not
if (Math.abs(tdist) < ret.dist) {
nPoint2 = nearest(search, tree[sideB], depth + 1);
ret = (nPoint2.dist < ret.dist ? nPoint2 : ret);
}
}
}
return ret;
}
// Extend the heatmap to use the K-D-tree to search for nearest points
H.seriesTypes.heatmap.prototype.setTooltipPoints = function() {
var series = this;
this.tree = null;
setTimeout(function() {
series.tree = KDTree(series.points, 0);
});
};
H.seriesTypes.heatmap.prototype.getNearest = function(search) {
if (this.tree) {
return nearest(search, this.tree, 0);
}
};
H.wrap(H.Pointer.prototype, 'runPointActions', function(proceed, e) {
var chart = this.chart;
proceed.call(this, e);
// Draw independent tooltips
H.each(chart.series, function(series) {
var point;
if (series.getNearest) {
point = series.getNearest({
plotX: e.chartX - chart.plotLeft,
plotY: e.chartY - chart.plotTop
});
if (point) {
point.onMouseOver(e);
}
}
})
});
/**
* Get the canvas context for a series
*/
H.Series.prototype.getContext = function() {
var canvas;
if (!this.ctx) {
canvas = document.createElement('canvas');
canvas.setAttribute('width', this.chart.plotWidth);
canvas.setAttribute('height', this.chart.plotHeight);
canvas.style.position = 'absolute';
canvas.style.left = this.group.translateX + 'px';
canvas.style.top = this.group.translateY + 'px';
canvas.style.zIndex = 0;
canvas.style.cursor = 'crosshair';
this.chart.container.appendChild(canvas);
if (canvas.getContext) {
this.ctx = canvas.getContext('2d');
}
}
return this.ctx;
}
/**
* Wrap the drawPoints method to draw the points in canvas instead of the slower SVG,
* that requires one shape each point.
*/
H.wrap(H.seriesTypes.heatmap.prototype, 'drawPoints', function(proceed) {
var ctx;
if (this.chart.renderer.forExport) {
// Run SVG shapes
proceed.call(this);
} else {
if (ctx = this.getContext()) {
// draw the columns
H.each(this.points, function(point) {
var plotY = point.plotY,
shapeArgs;
if (plotY !== undefined && !isNaN(plotY) && point.y !== null) {
shapeArgs = point.shapeArgs;
ctx.fillStyle = point.pointAttr[''].fill;
ctx.fillRect(shapeArgs.x, shapeArgs.y, shapeArgs.width, shapeArgs.height);
}
});
} else {
this.chart.showLoading("Your browser doesn't support HTML5 canvas,
please use a modern browser");
// Uncomment this to provide low-level (slow) support in oldIE. It will cause script errors on
// charts with more than a few thousand points.
//proceed.call(this);
}
}
});
}(Highcharts));
container = typeof container !== 'undefined' ? container : $('#chart');
var serie = {
colsize: 86400000, // one day
name: title + ' (' + variable.tooltipUnits + ')',
data: (function() {
var result = [];
var item;
var date;
var hour;
for (var i = 0, l = data.length; i < l; i++) {
item = data[i];
date = new Date(item[0]);
hour = date.getUTCHours();
hour += date.getUTCMinutes() / 60.0;
date.setUTCHours(0, 0, 0);
result.push([date.getTime(), hour, item[1]]);
}
return result;
})()
};
var serie_summary = this.getSeriesSummary(serie, 2);
container.highcharts({
series: [serie],
chart: {
type: 'heatmap',
margin: [60, 10, 80, 50]
},
title: {
text: title
},
xAxis: {
min: data[0][0],
max: data[data.length - 1][0],
labels: {
align: 'left',
x: 5,
format: '{value:%d/%m}'
},
showLastLabel: false,
tickLength: 16
},
yAxis: {
title: {
text: null
},
labels: {
format: '{value}:00'
},
minPadding: 0,
maxPadding: 0,
startOnTick: false,
endOnTick: false,
tickPostiions: [0, 6, 12, 18, 24],
tickWidth: 1,
min: 0,
max: 23,
reversed: true
},
colorAxis: {
stops: [
[0, '#3060cf'],
[0.5, '#fffbbc'],
[0.9, '#c4463a'],
[1, '#c4463a']
],
min: serie_summary.min,
max: serie_summary.max,
startOnTick: false,
endOnTick: false,
labels: {
format: '{value} ' + variable.inputUnits
}
},
tooltip: {
formatter: function() {
var s = '' + Highcharts.dateFormat('%A %b %e, %Y %H:%M:%S', this.point.x + (this.point.y * 60 * 60 * 1000)) + '';
s += '
' + variable.name + ' = ';
if (this.point.value) {
s += this.point.value + ' ' + variable.inputUnits;
} else {
s += 'No data';
}
s += Moorings.Chart.getExtraTooltipData(this.point);
return s;
},
shared: true,
backgroundColor: null,
borderWidth: 0,
distance: 10,
shadow: false,
useHTML: true,
style: {
padding: 0,
color: 'black'
}
}
});
}
};