518 lines
19 KiB
JavaScript
518 lines
19 KiB
JavaScript
;
|
|
|
|
(function($) {
|
|
$.fn.highchartTable = function() {
|
|
|
|
var allowedGraphTypes = ['column', 'line', 'area', 'spline', 'pie'];
|
|
|
|
var getCallable = function (table, attribute) {
|
|
var callback = $(table).data(attribute);
|
|
if (typeof callback != 'undefined') {
|
|
var infosCallback = callback.split('.');
|
|
var callable = window[infosCallback[0]];
|
|
for(var i = 1, infosCallbackLength = infosCallback.length; i < infosCallbackLength; i++) {
|
|
callable = callable[infosCallback[i]];
|
|
}
|
|
return callable;
|
|
}
|
|
};
|
|
|
|
this.each(function() {
|
|
var table = $(this);
|
|
var $table = $(table);
|
|
var nbYaxis = 1;
|
|
|
|
// Retrieve graph title from the table caption
|
|
var captions = $('caption', table);
|
|
var graphTitle = captions.length ? $(captions[0]).text() : '';
|
|
|
|
var graphContainer;
|
|
if ($table.data('graph-container-before') != 1) {
|
|
// Retrieve where the graph must be displayed from the graph-container attribute
|
|
var graphContainerSelector = $table.data('graph-container');
|
|
if (!graphContainerSelector) {
|
|
throw "graph-container data attribute is mandatory";
|
|
}
|
|
|
|
if (graphContainerSelector[0] === '#' || graphContainerSelector.indexOf('..')===-1) {
|
|
// Absolute selector path
|
|
graphContainer = $(graphContainerSelector);
|
|
} else {
|
|
var referenceNode = table;
|
|
var currentGraphContainerSelector = graphContainerSelector;
|
|
|
|
while (currentGraphContainerSelector.indexOf('..')!==-1) {
|
|
currentGraphContainerSelector = currentGraphContainerSelector.replace(/^.. /, '');
|
|
referenceNode = referenceNode.parent();
|
|
}
|
|
|
|
graphContainer = $(currentGraphContainerSelector, referenceNode);
|
|
}
|
|
if (graphContainer.length !== 1) {
|
|
throw "graph-container is not available in this DOM or available multiple times";
|
|
}
|
|
graphContainer = graphContainer[0];
|
|
} else {
|
|
$table.before('<div></div>');
|
|
graphContainer = $table.prev();
|
|
graphContainer = graphContainer[0];
|
|
}
|
|
|
|
// Retrieve graph type from graph-type attribute
|
|
var globalGraphType = $table.data('graph-type');
|
|
if (!globalGraphType) {
|
|
throw "graph-type data attribute is mandatory";
|
|
}
|
|
if ($.inArray(globalGraphType, allowedGraphTypes) == -1) {
|
|
throw "graph-container data attribute must be one of " + allowedGraphTypes.join(', ');
|
|
}
|
|
|
|
var stackingType = $table.data('graph-stacking');
|
|
if (!stackingType) {
|
|
stackingType = 'normal';
|
|
}
|
|
|
|
var dataLabelsEnabled = $table.data('graph-datalabels-enabled');
|
|
var isGraphInverted = $table.data('graph-inverted') == 1;
|
|
|
|
// Retrieve series titles
|
|
var ths = $('thead th', table);
|
|
var columns = [];
|
|
var vlines = [];
|
|
var skippedColumns = 0;
|
|
var graphIsStacked = false;
|
|
ths.each(function(indexTh, th) {
|
|
var $th = $(th);
|
|
var columnScale = $th.data('graph-value-scale');
|
|
|
|
var serieGraphType = $th.data('graph-type');
|
|
if($.inArray(serieGraphType, allowedGraphTypes) == -1) {
|
|
serieGraphType = globalGraphType;
|
|
}
|
|
|
|
var serieStackGroup = $th.data('graph-stack-group');
|
|
if(serieStackGroup) {
|
|
graphIsStacked = true;
|
|
}
|
|
|
|
var serieDataLabelsEnabled = $th.data('graph-datalabels-enabled');
|
|
if (typeof serieDataLabelsEnabled == 'undefined') {
|
|
serieDataLabelsEnabled = dataLabelsEnabled;
|
|
}
|
|
|
|
var yaxis = $th.data('graph-yaxis');
|
|
|
|
if (typeof yaxis != 'undefined' && yaxis == '1') {
|
|
nbYaxis = 2;
|
|
}
|
|
|
|
var isColumnSkipped = $th.data('graph-skip') == 1;
|
|
if (isColumnSkipped)
|
|
{
|
|
skippedColumns = skippedColumns + 1;
|
|
}
|
|
|
|
var thGraphConfig = {
|
|
libelle: $th.text(),
|
|
skip: isColumnSkipped,
|
|
indexTd: indexTh - skippedColumns - 1,
|
|
color: $th.data('graph-color'),
|
|
visible: !$th.data('graph-hidden'),
|
|
yAxis: typeof yaxis != 'undefined' ? yaxis : 0,
|
|
dashStyle: $th.data('graph-dash-style') || 'solid',
|
|
dataLabelsEnabled: serieDataLabelsEnabled == 1,
|
|
dataLabelsColor: $th.data('graph-datalabels-color') || $table.data('graph-datalabels-color')
|
|
|
|
};
|
|
|
|
var vlinex = $th.data('graph-vline-x');
|
|
if (typeof vlinex == 'undefined') {
|
|
thGraphConfig.scale = typeof columnScale != 'undefined' ? parseFloat(columnScale) : 1;
|
|
thGraphConfig.graphType = serieGraphType == 'column' && isGraphInverted ? 'bar' : serieGraphType;
|
|
thGraphConfig.stack = serieStackGroup;
|
|
thGraphConfig.unit = $th.data('graph-unit');
|
|
columns[indexTh] = thGraphConfig;
|
|
} else {
|
|
thGraphConfig.x = vlinex;
|
|
thGraphConfig.height = $th.data('graph-vline-height');
|
|
thGraphConfig.name = $th.data('graph-vline-name');
|
|
vlines[indexTh] = thGraphConfig;
|
|
}
|
|
});
|
|
|
|
var series = [];
|
|
$(columns).each(function(indexColumn, column) {
|
|
if(indexColumn!=0 && !column.skip) {
|
|
|
|
var serieConfig = {
|
|
name: column.libelle + (column.unit ? ' (' + column.unit + ')' : ''),
|
|
data: [],
|
|
type: column.graphType,
|
|
stack: column.stack,
|
|
color: column.color,
|
|
visible: column.visible,
|
|
yAxis: column.yAxis,
|
|
dashStyle: column.dashStyle,
|
|
marker: {
|
|
enabled: false
|
|
},
|
|
dataLabels: {
|
|
enabled: column.dataLabelsEnabled,
|
|
color: column.dataLabelsColor,
|
|
align: $table.data('graph-datalabels-align') || (globalGraphType == 'column' && isGraphInverted == 1 ? undefined : 'center')
|
|
}
|
|
};
|
|
|
|
if(column.dataLabelsEnabled) {
|
|
var callableSerieDataLabelsFormatter = getCallable(table, 'graph-datalabels-formatter');
|
|
if (callableSerieDataLabelsFormatter) {
|
|
serieConfig.dataLabels.formatter = function () {
|
|
return callableSerieDataLabelsFormatter(this.y);
|
|
};
|
|
}
|
|
}
|
|
series.push(serieConfig);
|
|
}
|
|
});
|
|
|
|
$(vlines).each(function(indexColumn, vline) {
|
|
if (typeof vline != 'undefined' && !vline.skip) {
|
|
series.push({
|
|
name: vline.libelle,
|
|
data: [{x: vline.x, y:0, name: vline.name}, {x:vline.x, y:vline.height, name: vline.name}],
|
|
type: 'spline',
|
|
color: vline.color,
|
|
visible: vline.visible,
|
|
marker: {
|
|
enabled: false
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
var xValues = [];
|
|
var callablePoint = getCallable(table, 'graph-point-callback');
|
|
var isGraphDatetime = $table.data('graph-xaxis-type') == 'datetime';
|
|
|
|
var rows = $('tbody:first tr', table);
|
|
rows.each(function(indexRow, row) {
|
|
if (!!$(row).data('graph-skip')) {
|
|
return;
|
|
}
|
|
|
|
var tds = $('td', row);
|
|
tds.each(function(indexTd, td) {
|
|
var cellValue;
|
|
var column = columns[indexTd];
|
|
|
|
if (column.skip) {
|
|
return;
|
|
}
|
|
var $td = $(td);
|
|
if (indexTd==0) {
|
|
cellValue = $td.text();
|
|
xValues.push(cellValue);
|
|
} else {
|
|
var rawCellValue = $td.text();
|
|
var serie = series[column.indexTd];
|
|
|
|
if (rawCellValue.length==0) {
|
|
if (!isGraphDatetime) {
|
|
serie.data.push(null);
|
|
}
|
|
} else {
|
|
var cleanedCellValue = rawCellValue.replace(/\s/g, '').replace(/,/, '.');
|
|
var eventOptions = {
|
|
value: cleanedCellValue,
|
|
rawValue: rawCellValue,
|
|
td: $td,
|
|
tr: $(row),
|
|
indexTd: indexTd,
|
|
indexTr: indexRow
|
|
}
|
|
$table.trigger('highchartTable.cleanValue', eventOptions);
|
|
cellValue = Math.round(parseFloat(eventOptions.value) * column.scale * 100) / 100;
|
|
|
|
var dataGraphX = $td.data('graph-x');
|
|
|
|
if (isGraphDatetime) {
|
|
dataGraphX = $('td', $(row)).first().text();
|
|
var date = parseDate(dataGraphX);
|
|
dataGraphX = date.getTime() - date.getTimezoneOffset()*60*1000;
|
|
}
|
|
|
|
var tdGraphName = $td.data('graph-name');
|
|
var serieDataItem = {
|
|
name: typeof tdGraphName != 'undefined' ? tdGraphName : rawCellValue,
|
|
y: cellValue,
|
|
x: dataGraphX //undefined if no x defined in table
|
|
};
|
|
|
|
if (callablePoint) {
|
|
serieDataItem.events = {
|
|
click: function () {
|
|
return callablePoint(this);
|
|
}
|
|
};
|
|
}
|
|
|
|
if (column.graphType === 'pie') {
|
|
if ($td.data('graph-item-highlight')) {
|
|
serieDataItem.sliced = 1;
|
|
}
|
|
}
|
|
|
|
var tdGraphItemColor = $td.data('graph-item-color');
|
|
if (typeof tdGraphItemColor != 'undefined') {
|
|
serieDataItem.color = tdGraphItemColor;
|
|
}
|
|
|
|
serie.data.push(serieDataItem);
|
|
}
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
var getYaxisAttr = function($table, yAxisNum, name) {
|
|
var oldConvention = $table.data('graph-yaxis-' + yAxisNum + '-' + name);
|
|
if (typeof oldConvention != 'undefined') {
|
|
return oldConvention;
|
|
}
|
|
|
|
return $table.data('graph-yaxis' + yAxisNum + '-' + name);
|
|
};
|
|
|
|
var yAxisConfig = [];
|
|
var yAxisNum;
|
|
for (yAxisNum=1 ; yAxisNum <= nbYaxis ; yAxisNum++) {
|
|
var yAxisConfigCurrentAxis = {
|
|
title: {
|
|
text: typeof getYaxisAttr($table, yAxisNum, 'title-text') != 'undefined' ? getYaxisAttr($table, yAxisNum, 'title-text') : null
|
|
},
|
|
max: typeof getYaxisAttr($table, yAxisNum, 'max') != 'undefined' ? getYaxisAttr($table, yAxisNum, 'max') : null,
|
|
min: typeof getYaxisAttr($table, yAxisNum, 'min') != 'undefined' ? getYaxisAttr($table, yAxisNum, 'min') : null,
|
|
reversed: getYaxisAttr($table, yAxisNum, 'reversed') == '1',
|
|
opposite: getYaxisAttr($table, yAxisNum, 'opposite') == '1',
|
|
tickInterval: getYaxisAttr($table, yAxisNum, 'tick-interval') || null,
|
|
labels: {
|
|
rotation: getYaxisAttr($table, yAxisNum, 'rotation') || 0
|
|
},
|
|
startOnTick: getYaxisAttr($table, yAxisNum, 'start-on-tick') != "0",
|
|
endOnTick: getYaxisAttr($table, yAxisNum, 'end-on-tick') != "0",
|
|
stackLabels : {
|
|
enabled: getYaxisAttr($table, yAxisNum, 'stacklabels-enabled') == '1'
|
|
},
|
|
gridLineInterpolation: getYaxisAttr($table, yAxisNum, 'grid-line-interpolation') || null
|
|
};
|
|
|
|
var callableYAxisFormatter = getCallable(table, 'graph-yaxis-'+yAxisNum+'-formatter-callback');
|
|
|
|
if (!callableYAxisFormatter) {
|
|
callableYAxisFormatter = getCallable(table, 'graph-yaxis'+yAxisNum+'-formatter-callback');
|
|
}
|
|
|
|
if (callableYAxisFormatter) {
|
|
yAxisConfigCurrentAxis.labels.formatter = function () {
|
|
return callableYAxisFormatter(this.value);
|
|
};
|
|
}
|
|
|
|
yAxisConfig.push(yAxisConfigCurrentAxis);
|
|
}
|
|
|
|
var defaultColors = [
|
|
'#4572A7',
|
|
'#AA4643',
|
|
'#89A54E',
|
|
'#80699B',
|
|
'#3D96AE',
|
|
'#DB843D',
|
|
'#92A8CD',
|
|
'#A47D7C',
|
|
'#B5CA92'
|
|
];
|
|
var colors = [];
|
|
|
|
var themeColors = typeof Highcharts.theme != 'undefined' && typeof Highcharts.theme.colors != 'undefined' ? Highcharts.theme.colors : [];
|
|
var lineShadow = $table.data('graph-line-shadow');
|
|
var lineWidth = $table.data('graph-line-width') || 2;
|
|
|
|
var nbOfColors = Math.max(defaultColors.length, themeColors.length);
|
|
for(var i=0; i < nbOfColors; i++) {
|
|
var dataname = 'graph-color-' + (i+1);
|
|
colors.push(typeof $table.data(dataname) != 'undefined' ? $table.data(dataname) : typeof themeColors[i] != 'undefined' ? themeColors[i] : defaultColors[i]);
|
|
}
|
|
|
|
var marginTop = $table.data('graph-margin-top');
|
|
var marginRight = $table.data('graph-margin-right');
|
|
var marginBottom = $table.data('graph-margin-bottom');
|
|
var marginLeft = $table.data('graph-margin-left');
|
|
|
|
var xAxisLabelsEnabled = $table.data('graph-xaxis-labels-enabled');
|
|
|
|
var xAxisLabelStyle = {};
|
|
var xAxisLabelFontSize = $table.data('graph-xaxis-labels-font-size');
|
|
|
|
if (typeof xAxisLabelFontSize != 'undefined')
|
|
{
|
|
xAxisLabelStyle.fontSize = xAxisLabelFontSize;
|
|
}
|
|
|
|
var highChartConfig = {
|
|
colors: colors,
|
|
chart: {
|
|
renderTo: graphContainer,
|
|
inverted: isGraphInverted,
|
|
marginTop: typeof marginTop != 'undefined' ? marginTop : null,
|
|
marginRight: typeof marginRight != 'undefined' ? marginRight : null,
|
|
marginBottom: typeof marginBottom != 'undefined' ? marginBottom : null,
|
|
marginLeft: typeof marginLeft != 'undefined' ? marginLeft : null,
|
|
spacingTop: $table.data('graph-spacing-top') || 10,
|
|
height: $table.data('graph-height') || null,
|
|
zoomType: $table.data('graph-zoom-type') || null,
|
|
polar: $table.data('graph-polar') || null
|
|
},
|
|
title: {
|
|
text: graphTitle
|
|
},
|
|
subtitle: {
|
|
text: $table.data('graph-subtitle-text') || ''
|
|
},
|
|
legend: {
|
|
enabled: $table.data('graph-legend-disabled') != '1',
|
|
layout: $table.data('graph-legend-layout') || 'horizontal',
|
|
symbolWidth: $table.data('graph-legend-width') || 30,
|
|
x: $table.data('graph-legend-x') || 15,
|
|
y: $table.data('graph-legend-y') || 0
|
|
},
|
|
xAxis: {
|
|
categories: ($table.data('graph-xaxis-type') != 'datetime') ? xValues : undefined,
|
|
type: ($table.data('graph-xaxis-type') == 'datetime') ? 'datetime' : undefined,
|
|
reversed: $table.data('graph-xaxis-reversed') == '1',
|
|
opposite: $table.data('graph-xaxis-opposite') == '1',
|
|
showLastLabel: typeof $table.data('graph-xaxis-show-last-label') != 'undefined' ? $table.data('graph-xaxis-show-last-label') : true,
|
|
tickInterval: $table.data('graph-xaxis-tick-interval') || null,
|
|
dateTimeLabelFormats: { //by default, we display the day and month on the datetime graphs
|
|
second: '%e. %b',
|
|
minute: '%e. %b',
|
|
hour: '%e. %b',
|
|
day: '%e. %b',
|
|
week: '%e. %b',
|
|
month: '%e. %b',
|
|
year: '%e. %b'
|
|
},
|
|
labels:
|
|
{
|
|
rotation: $table.data('graph-xaxis-rotation') || undefined,
|
|
align: $table.data('graph-xaxis-align') || undefined,
|
|
enabled: typeof xAxisLabelsEnabled != 'undefined' ? xAxisLabelsEnabled : true,
|
|
style: xAxisLabelStyle
|
|
},
|
|
startOnTick: $table.data('graph-xaxis-start-on-tick'),
|
|
endOnTick: $table.data('graph-xaxis-end-on-tick'),
|
|
min: getXAxisMinMax(table, 'min'),
|
|
max: getXAxisMinMax(table, 'max'),
|
|
alternateGridColor: $table.data('graph-xaxis-alternateGridColor') || null,
|
|
title: {
|
|
text: $table.data('graph-xaxis-title-text') || null
|
|
},
|
|
gridLineWidth: $table.data('graph-xaxis-gridLine-width') || 0,
|
|
gridLineDashStyle: $table.data('graph-xaxis-gridLine-style') || 'ShortDot',
|
|
tickmarkPlacement: $table.data('graph-xaxis-tickmark-placement') || 'between',
|
|
lineWidth: $table.data('graph-xaxis-line-width') || 0
|
|
},
|
|
yAxis: yAxisConfig,
|
|
tooltip: {
|
|
formatter: function() {
|
|
if ($table.data('graph-xaxis-type') == 'datetime') {
|
|
return '<b>'+ this.series.name +'</b><br/>'+ Highcharts.dateFormat('%e. %b', this.x) +' : '+ this.y;
|
|
} else {
|
|
var xValue = typeof xValues[this.point.x] != 'undefined' ? xValues[this.point.x] : this.point.x;
|
|
if (globalGraphType === 'pie') {
|
|
return '<strong>' + this.series.name + '</strong><br />' + xValue + ' : ' + this.point.y;
|
|
}
|
|
return '<strong>' + this.series.name + '</strong><br />' + xValue + ' : ' + this.point.name;
|
|
}
|
|
}
|
|
},
|
|
credits: {
|
|
enabled: false
|
|
},
|
|
plotOptions: {
|
|
line: {
|
|
dataLabels: {
|
|
enabled: true
|
|
},
|
|
lineWidth: lineWidth
|
|
},
|
|
area: {
|
|
lineWidth: lineWidth,
|
|
shadow: typeof lineShadow != 'undefined' ? lineShadow : true,
|
|
fillOpacity: $table.data('graph-area-fillOpacity') || 0.75
|
|
},
|
|
pie: {
|
|
allowPointSelect: true,
|
|
dataLabels: {
|
|
enabled: true
|
|
},
|
|
showInLegend: $table.data('graph-pie-show-in-legend') == '1',
|
|
size: '80%'
|
|
},
|
|
series: {
|
|
animation: false,
|
|
stickyTracking : false,
|
|
stacking: graphIsStacked ? stackingType : null,
|
|
groupPadding: $table.data('graph-group-padding') || 0
|
|
}
|
|
},
|
|
series: series,
|
|
exporting: {
|
|
filename: graphTitle.replace(/ /g, '_'),
|
|
buttons: {
|
|
exportButton: {
|
|
menuItems: null,
|
|
onclick: function() {
|
|
this.exportChart();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
$table.trigger('highchartTable.beforeRender', highChartConfig);
|
|
new Highcharts.Chart(highChartConfig);
|
|
|
|
});
|
|
//for fluent api
|
|
return this;
|
|
};
|
|
|
|
var getXAxisMinMax = function(table, minOrMax) {
|
|
var value = $(table).data('graph-xaxis-'+minOrMax);
|
|
if (typeof value != 'undefined') {
|
|
if ($(table).data('graph-xaxis-type') == 'datetime') {
|
|
var date = parseDate(value);
|
|
return date.getTime() - date.getTimezoneOffset()*60*1000;
|
|
}
|
|
return value;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
var parseDate = function(datetime) {
|
|
var calculatedateInfos = datetime.split(' ');
|
|
var dateDayInfos = calculatedateInfos[0].split('-');
|
|
var min = null;
|
|
var hour = null;
|
|
// If hour and minute are available in the datetime string
|
|
if(calculatedateInfos[1]) {
|
|
var dateHourInfos = calculatedateInfos[1].split(':');
|
|
min = parseInt(dateHourInfos[0], 10);
|
|
hour = parseInt(dateHourInfos[1], 10);
|
|
}
|
|
return new Date(parseInt(dateDayInfos[0], 10), parseInt(dateDayInfos[1], 10)-1, parseInt(dateDayInfos[2], 10), min, hour);
|
|
};
|
|
|
|
})(jQuery);
|