--- /dev/null
+<!doctype html>
+<html>
+
+<head>
+ <title>Line Chart</title>
+ <script src="../Chart.js"></script>
+ <script src="../node_modules/jquery/dist/jquery.min.js"></script>
+ <style>
+ canvas {
+ -webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, .5);
+ }
+ </style>
+</head>
+
+<body>
+ <div style="width:100%;">
+ <canvas id="canvas" style="width:100%;height:100%"></canvas>
+ </div>
+ <br>
+ <br>
+ <button id="randomizeData">Randomize Data</button>
+ <button id="addDataset">Add Dataset</button>
+ <button id="removeDataset">Remove Dataset</button>
+ <button id="addData">Add Data</button>
+ <button id="removeData">Remove Data</button>
+ <div>
+ <h3>Legend</h3>
+ <div id="legendContainer">
+ </div>
+ </div>
+ <script>
+ var randomScalingFactor = function() {
+ return Math.round(Math.random() * 100 * (Math.random() > 0.5 ? -1 : 1));
+ };
+ var randomColorFactor = function() {
+ return Math.round(Math.random() * 255);
+ };
+ var randomColor = function(opacity) {
+ return 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',' + (opacity || '.3') + ')';
+ };
+
+ var config = {
+ type: 'line',
+ data: {
+ labels: ["January", "February", "March", "April", "May", "June", "July"],
+ datasets: [{
+ label: "My First dataset",
+ data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
+ fill: false,
+ borderDash: [5, 5],
+ }, {
+ label: "My Second dataset",
+ data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
+ }]
+ },
+ options: {
+ responsive: true,
+ tooltips: {
+ mode: 'label',
+ callbacks: {
+ beforeTitle: function() {
+ return '...beforeTitle';
+ },
+ afterTitle: function() {
+ return '...afterTitle';
+ },
+ beforeBody: function() {
+ return '...beforeBody';
+ },
+ afterBody: function() {
+ return '...afterBody';
+ },
+ beforeFooter: function() {
+ return '...beforeFooter';
+ },
+ footer: function() {
+ return 'Footer';
+ },
+ afterFooter: function() {
+ return '...afterFooter';
+ },
+ }
+ },
+ hover: {
+ mode: 'label'
+ },
+ scales: {
+ xAxes: [{
+ display: true,
+ scaleLabel: {
+ show: true,
+ labelString: 'Month'
+ }
+ }],
+ yAxes: [{
+ display: true,
+ scaleLabel: {
+ show: true,
+ labelString: 'Value'
+ }
+ }]
+ }
+ }
+ };
+
+ $.each(config.data.datasets, function(i, dataset) {
+ dataset.borderColor = randomColor(0.4);
+ dataset.backgroundColor = randomColor(0.5);
+ dataset.pointBorderColor = randomColor(0.7);
+ dataset.pointBackgroundColor = randomColor(0.5);
+ dataset.pointBorderWidth = 1;
+ });
+
+ console.log(config.data);
+
+ window.onload = function() {
+ var ctx = document.getElementById("canvas").getContext("2d");
+ window.myLine = new Chart(ctx, config);
+
+ updateLegend();
+ };
+
+ function updateLegend() {
+ $legendContainer = $('#legendContainer');
+ $legendContainer.empty();
+ $legendContainer.append(window.myLine.generateLegend());
+ }
+
+ $('#randomizeData').click(function() {
+ $.each(config.data.datasets, function(i, dataset) {
+ dataset.data = dataset.data.map(function() {
+ return randomScalingFactor();
+ });
+
+ });
+
+ window.myLine.update();
+ updateLegend();
+ });
+
+ $('#addDataset').click(function() {
+ var newDataset = {
+ label: 'Dataset ' + config.data.datasets.length,
+ borderColor: randomColor(0.4),
+ backgroundColor: randomColor(0.5),
+ pointBorderColor: randomColor(0.7),
+ pointBackgroundColor: randomColor(0.5),
+ pointBorderWidth: 1,
+ data: [],
+ };
+
+ for (var index = 0; index < config.data.labels.length; ++index) {
+ newDataset.data.push(randomScalingFactor());
+ }
+
+ config.data.datasets.push(newDataset);
+ window.myLine.update();
+ updateLegend();
+ });
+
+ $('#addData').click(function() {
+ if (config.data.datasets.length > 0) {
+ config.data.labels.push('dataset #' + config.data.labels.length);
+
+ $.each(config.data.datasets, function(i, dataset) {
+ dataset.data.push(randomScalingFactor());
+ });
+
+ window.myLine.update();
+ updateLegend();
+ }
+ });
+
+ $('#removeDataset').click(function() {
+ config.data.datasets.splice(0, 1);
+ window.myLine.update();
+ updateLegend();
+ });
+
+ $('#removeData').click(function() {
+ config.data.labels.splice(-1, 1); // remove the label first
+
+ config.data.datasets.forEach(function(dataset, datasetIndex) {
+ dataset.data.pop();
+ });
+
+ window.myLine.update();
+ updateLegend();
+ });
+ </script>
+</body>
+
+</html>
titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
titleFontSize: 12,
titleFontStyle: "bold",
+ titleSpacing: 2,
+ titleMarginBottom: 6,
titleColor: "#fff",
titleAlign: "left",
bodyFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
bodyFontSize: 12,
bodyFontStyle: "normal",
+ bodySpacing: 2,
bodyColor: "#fff",
bodyAlign: "left",
footerFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
footerFontSize: 12,
footerFontStyle: "bold",
+ footerSpacing: 2,
+ footerMarginTop: 6,
footerColor: "#fff",
footerAlign: "left",
yPadding: 6,
callbacks: {
beforeTitle: helpers.noop,
title: function(xLabel, yLabel, index, datasetIndex, data) {
- return data.datasets[datasetIndex].label;
+ return this._options.tooltips.mode == 'single' ? data.datasets[datasetIndex].label : data.labels[index];
},
afterTitle: helpers.noop,
_bodyFontFamily: options.tooltips.bodyFontFamily,
_bodyFontStyle: options.tooltips.bodyFontStyle,
bodyFontSize: options.tooltips.bodyFontSize,
+ bodySpacing: options.tooltips.bodySpacing,
_bodposition: options.tooltips.bodposition,
// Title
_titleFontStyle: options.tooltips.titleFontStyle,
titleFontSize: options.tooltips.titleFontSize,
_titleAlign: options.tooltips.titleAlign,
+ titleSpacing: options.tooltips.titleSpacing,
+ titleMarginBottom: options.tooltips.titleMarginBottom,
// Footer
footerColor: options.tooltips.footerColor,
_footerFontStyle: options.tooltips.footerFontStyle,
footerFontSize: options.tooltips.footerFontSize,
_footerAlign: options.tooltips.footerAlign,
+ footerSpacing: options.tooltips.footerSpacing,
+ footerMarginTop: options.tooltips.footerMarginTop,
// Appearance
caretSize: options.tooltips.caretSize,
var lines = [];
- var beforeBody = this._options.tooltips.callbacks.beforeBody.apply(this, arguments);
- if (beforeBody) {
- lines.push(beforeBody);
- }
-
var beforeLabel,
afterLabel,
label;
// Run EACH label pair through the label callback this time.
for (var i = 0; i < xLabel.length; i++) {
- beforeLabel = this._options.tooltips.callbacks.beforeLabel(xLabel[i], yLabel[i], index, datasetIndex);
- afterLabel = this._options.tooltips.callbacks.afterLabel(xLabel[i], yLabel[i], index, datasetIndex);
+ beforeLabel = this._options.tooltips.callbacks.beforeLabel.call(this, xLabel[i], yLabel[i], index, datasetIndex);
+ afterLabel = this._options.tooltips.callbacks.afterLabel.call(this, xLabel[i], yLabel[i], index, datasetIndex);
- labels.push((beforeLabel ? beforeLabel : '') + this._options.tooltips.callbacks.label(xLabel[i], yLabel[i], index, datasetIndex) + (afterLabel ? afterLabel : ''));
+ labels.push((beforeLabel ? beforeLabel : '') + this._options.tooltips.callbacks.label.call(this, xLabel[i], yLabel[i], index, datasetIndex) + (afterLabel ? afterLabel : ''));
}
}
}
- var afterBody = this._options.tooltips.callbacks.afterBody.apply(this, arguments);
- if (afterBody) {
- lines.push(afterBody);
- }
-
return lines;
},
var element = this._active[0],
xLabel,
yLabel,
+ labelColors = [],
tooltipPosition;
if (this._options.tooltips.mode == 'single') {
xLabel = [];
yLabel = [];
+
+ console.log(this._active);
+
helpers.each(this._data.datasets, function(dataset, datasetIndex) {
+
xLabel.push(element._xScale.getLabelForIndex(element._index, datasetIndex));
yLabel.push(element._yScale.getLabelForIndex(element._index, datasetIndex));
});
+
+ helpers.each(this._active, function(active, i) {
+ labelColors.push({
+ borderColor: active._view.borderColor,
+ backgroundColor: active._view.backgroundColor
+ });
+ }, this);
+
tooltipPosition = this._active[0].tooltipPosition();
+ tooltipPosition.y = this._active[0]._yScale.getPixelForDecimal(0.5);
- // for (var i = 0; i < this._data.datasets.length; i++) {
- // this._data.datasets[i].data[index];
- // };
-
- // // Tooltip Content
-
- // var dataArray,
- // dataIndex;
-
- // var labels = [],
- // colors = [];
-
- // for (var i = this._data.datasets.length - 1; i >= 0; i--) {
- // dataArray = this._data.datasets[i].metaData;
- // dataIndex = helpers.indexOf(dataArray, this._active[0]);
- // if (dataIndex !== -1) {
- // break;
- // }
- // }
-
- // var medianPosition = (function(index) {
- // // Get all the points at that particular index
- // var elements = [],
- // dataCollection,
- // xPositions = [],
- // yPositions = [],
- // xMax,
- // yMax,
- // xMin,
- // yMin;
- // helpers.each(this._data.datasets, function(dataset) {
- // dataCollection = dataset.metaData;
- // if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) {
- // elements.push(dataCollection[dataIndex]);
- // }
- // }, this);
-
- // // Reverse labels if stacked
- // helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) {
- // xPositions.push(element._view.x);
- // yPositions.push(element._view.y);
-
- // //Include any colour information about the element
- // labels.push(
- // this._options.tooltips.multiTemplate(
- // element,
- // this._data.datasets[element._datasetIndex].label,
- // this._data.datasets[element._datasetIndex].data[element._index]
- // )
- // );
-
- // colors.push({
- // fill: element._view.backgroundColor,
- // stroke: element._view.borderColor
- // });
-
- // }, this);
-
- // yMin = helpers.min(yPositions);
- // yMax = helpers.max(yPositions);
-
- // xMin = helpers.min(xPositions);
- // xMax = helpers.max(xPositions);
-
- // return {
- // x: (xMin > this._chart.width / 2) ? xMin : xMax,
- // y: (yMin + yMax) / 2,
- // };
- // }).call(this, dataIndex);
-
- // // Apply for now
- // helpers.extend(this._model, {
- // x: medianPosition.x,
- // y: medianPosition.y,
- // labels: labels,
- // title: (function() {
- // return this._data.timeLabels ? this._data.timeLabels[this._active[0]._index] :
- // (this._data.labels && this._data.labels.length) ? this._data.labels[this._active[0]._index] :
- // '';
- // }).call(this),
- // legendColors: colors,
- // legendBackgroundColor: this._options.tooltips.multiKeyBackground,
- // });
-
-
- // // Calculate Appearance Tweaks
-
- // this._model.height = (labels.length * this._model.bodyFontSize) + ((labels.length - 1) * (this._model.bodyFontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5;
-
- // var titleWidth = ctx.measureText(this._model.title).width,
- // //Label has a legend square as well so account for this.
- // labelWidth = helpers.longestText(ctx, this.font, labels) + this._model.bodyFontSize + 3,
- // longestTextWidth = helpers.max([labelWidth, titleWidth]);
-
- // this._model.width = longestTextWidth + (this._model.xPadding * 2);
-
-
- // var halfHeight = this._model.height / 2;
-
- // //Check to ensure the height will fit on the canvas
- // if (this._model.y - halfHeight < 0) {
- // this._model.y = halfHeight;
- // } else if (this._model.y + halfHeight > this._chart.height) {
- // this._model.y = this._chart.height - halfHeight;
- // }
-
- // //Decide whether to align left or right based on position on canvas
- // if (this._model.x > this._chart.width / 2) {
- // this._model.x -= this._model.xOffset + this._model.width;
- // } else {
- // this._model.x += this._model.xOffset;
- // }
- // break;
}
+
// Build the Text Lines
helpers.extend(this._model, {
title: this.getTitle(xLabel, yLabel, element._index, element._datasetIndex, this._data),
+ beforeBody: this._options.tooltips.callbacks.beforeBody.call(this, xLabel, yLabel, element._index, element._datasetIndex, this._data),
body: this.getBody(xLabel, yLabel, element._index, element._datasetIndex, this._data),
+ afterBody: this._options.tooltips.callbacks.afterBody.call(this, xLabel, yLabel, element._index, element._datasetIndex, this._data),
footer: this.getFooter(xLabel, yLabel, element._index, element._datasetIndex, this._data),
});
helpers.extend(this._model, {
x: Math.round(tooltipPosition.x),
y: Math.round(tooltipPosition.y),
- caretPadding: tooltipPosition.padding
+ caretPadding: tooltipPosition.padding,
+ labelColors: labelColors,
});
return this;
vm.position = "top";
var caretPadding = vm.caretPadding || 2;
+ var combinedBodyLength = vm.body.length + (vm.beforeBody ? 1 : 0) + (vm.afterBody ? 1 : 0);
// Height
- var tooltipHeight = vm.yPadding * 2;
+ var tooltipHeight = vm.yPadding * 2; // Tooltip Padding
+
+ tooltipHeight += vm.title.length * vm.titleFontSize; // Title Lines
+ tooltipHeight += (vm.title.length - 1) * vm.titleSpacing; // Title Line Spacing
+ tooltipHeight += vm.title.length ? vm.titleMarginBottom : 0; // Title's bottom Margin
+
+ tooltipHeight += combinedBodyLength * vm.bodyFontSize; // Body Lines
+ tooltipHeight += (combinedBodyLength - 1) * vm.bodySpacing; // Body Line Spacing
- tooltipHeight += vm.title.length * vm.titleFontSize; // Line Height
- tooltipHeight += vm.title.length ? vm.yPadding : 0;
- tooltipHeight += vm.body.length * (vm.bodyFontSize); // Line Height
- tooltipHeight += vm.footer.length ? vm.yPadding : 0;
- tooltipHeight += vm.footer.length * (vm.footerFontSize); // Line Height
+ tooltipHeight += vm.footer.length ? vm.footerMarginTop : 0; // Footer Margin
+ tooltipHeight += vm.footer.length * (vm.footerFontSize); // Footer Lines
+ tooltipHeight += (vm.footer.length - 1) * vm.footerSpacing; // Footer Line Spacing
// Width
var tooltipWidth = 0;
});
helpers.each(vm.body, function(line, i) {
ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
- tooltipWidth = Math.max(tooltipWidth, ctx.measureText(line).width);
- });
+ tooltipWidth = Math.max(tooltipWidth, ctx.measureText(line).width + (this._options.tooltips.mode != 'single' ? (vm.bodyFontSize + 2) : 0));
+ }, this);
helpers.each(vm.footer, function(line, i) {
ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
tooltipWidth = Math.max(tooltipWidth, ctx.measureText(line).width);
var tooltipTotalWidth = tooltipWidth + vm.caretSize + caretPadding;
- // Smart Tooltip placement to stay on the canvas
+ // Smart Tooltip placement to stay on the canvas
// Top, center, or bottom
vm.yAlign = "center";
if (vm.y - (tooltipHeight / 2) < 0) {
if (this._options.tooltips.enabled) {
- var bodyStart,
- footerStart;
+ var yBase = tooltipY + vm.yPadding;
+ var xBase = tooltipX + vm.xPadding;
// Titles
- ctx.textAlign = vm._titleAlign;
- ctx.textBaseline = "top";
- ctx.fillStyle = helpers.color(vm.titleColor).alpha(vm.opacity).rgbString();
- ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
- helpers.each(vm.title, function(title, i) {
- var yPos = tooltipY + vm.yPadding + (vm.titleFontSize * i);
- ctx.fillText(title, tooltipX + vm.xPadding, yPos);
- if (i + 1 == vm.title.length) {
- bodyStart = yPos + vm.yPadding + vm.titleFontSize;
- }
- }, this);
+ if (vm.title.length) {
+ ctx.textAlign = vm._titleAlign;
+ ctx.textBaseline = "top";
+ ctx.fillStyle = helpers.color(vm.titleColor).alpha(vm.opacity).rgbString();
+ ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
+
+ helpers.each(vm.title, function(title, i) {
+ ctx.fillText(title, xBase, yBase);
+ yBase += vm.titleFontSize + vm.titleSpacing; // Line Height and spacing
+ if (i + 1 == vm.title.length) {
+ yBase += vm.titleMarginBottom - vm.titleSpacing; // If Last, add margin, remove spacing
+ }
+ }, this);
+ }
// Body
ctx.fillStyle = helpers.color(vm.bodyColor).alpha(vm.opacity).rgbString();
ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
- console.log(bodyStart);
+ // Before Body
+ if (vm.beforeBody) {
+ ctx.fillText(vm.beforeBody, xBase, yBase);
+ yBase += vm.bodyFontSize + vm.bodySpacing;
+ }
helpers.each(vm.body, function(body, i) {
- var yPos = bodyStart + (vm.bodyFontSize * i);
- ctx.fillText(body, tooltipX + vm.xPadding, yPos);
- if (i + 1 == vm.body.length) {
- footerStart = yPos + vm.bodyFontSize;
- }
- }, this);
- // Footer
- ctx.textAlign = vm._footerAlign;
- ctx.textBaseline = "top";
- ctx.fillStyle = helpers.color(vm.footerColor).alpha(vm.opacity).rgbString();
- ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
+ // Draw Legend-like boxes if needed
+ if (this._options.tooltips.mode != 'single') {
+ ctx.fillStyle = helpers.color(vm.labelColors[i].borderColor).alpha(vm.opacity).rgbString();
+ ctx.fillRect(xBase, yBase, vm.bodyFontSize, vm.bodyFontSize);
- helpers.each(vm.footer, function(footer, i) {
- var yPos = footerStart + vm.yPadding + (vm.footerFontSize * i);
- ctx.fillText(footer, tooltipX + vm.xPadding, yPos);
- }, this);
-
- }
-
- return;
-
- // Draw Body
- ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
-
- // Draw Footer
-
- // Custom Tooltips
-
- if (this._options.tooltips.custom) {
- this._options.tooltips.custom(this);
- }
+ ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(vm.opacity).rgbString();
+ ctx.fillRect(xBase + 1, yBase + 1, vm.bodyFontSize - 2, vm.bodyFontSize - 2);
- switch (this._options.tooltips.mode) {
- case 'single':
-
-
-
- ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
- ctx.textAlign = "center";
- ctx.textBaseline = "middle";
- ctx.fillText(vm.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipHeight / 2);
- break;
- case 'label':
+ ctx.fillStyle = helpers.color(vm.bodyColor).alpha(vm.opacity).rgbString(); // Return fill style for text
+ }
+ // Body Line
+ ctx.fillText(body, xBase + (this._options.tooltips.mode != 'single' ? (vm.bodyFontSize + 2) : 0), yBase);
- //helpers.drawRoundedRectangle(ctx, vm.x, vm.y - vm.height / 2, vm.width, vm.height, vm.cornerRadius);
- // ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
- // ctx.fill();
- // ctx.closePath();
+ yBase += vm.bodyFontSize + vm.bodySpacing;
- // Title
- ctx.textAlign = "left";
- ctx.textBaseline = "middle";
- ctx.fillStyle = helpers.color(vm.titleColor).alpha(vm.opacity).rgbString();
- ctx.font = helpers.fontString(vm.bodyFontSize, vm._titleFontStyle, vm._titleFontFamily);
- ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0));
+ }, this);
- ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
- helpers.each(vm.labels, function(label, index) {
- ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
- ctx.fillText(label, vm.x + vm.xPadding + vm.bodyFontSize + 3, this.getLineHeight(index + 1));
+ // After Body
+ if (vm.afterBody) {
+ ctx.fillText(vm.afterBody, xBase, yBase);
+ yBase += vm.bodyFontSize;
+ } else {
+ yBase -= vm.bodySpacing; // Remove last body spacing
+ }
- //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
- //ctx.clearRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.bodyFontSize/2, vm.bodyFontSize, vm.bodyFontSize);
- //Instead we'll make a white filled block to put the legendColour palette over.
- ctx.fillStyle = helpers.color(vm.legendColors[index].stroke).alpha(vm.opacity).rgbString();
- ctx.fillRect(vm.x + vm.xPadding - 1, this.getLineHeight(index + 1) - vm.bodyFontSize / 2 - 1, vm.bodyFontSize + 2, vm.bodyFontSize + 2);
+ // Footer
+ if (vm.footer.length) {
- ctx.fillStyle = helpers.color(vm.legendColors[index].fill).alpha(vm.opacity).rgbString();
- ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.bodyFontSize / 2, vm.bodyFontSize, vm.bodyFontSize);
+ yBase += vm.footerMarginTop;
+ ctx.textAlign = vm._footerAlign;
+ ctx.textBaseline = "top";
+ ctx.fillStyle = helpers.color(vm.footerColor).alpha(vm.opacity).rgbString();
+ ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
+ helpers.each(vm.footer, function(footer, i) {
+ ctx.fillText(footer, xBase, yBase);
+ yBase += vm.footerFontSize + vm.footerSpacing;
}, this);
- break;
+ }
+
}
},
});