]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Tooltip Hooks
authorTanner Linsley <tannerlinsley@gmail.com>
Mon, 12 Oct 2015 20:51:00 +0000 (14:51 -0600)
committerTanner Linsley <tannerlinsley@gmail.com>
Mon, 12 Oct 2015 20:51:00 +0000 (14:51 -0600)
samples/tooltip-hooks.html [new file with mode: 0644]
src/core/core.scale.js
src/core/core.tooltip.js

diff --git a/samples/tooltip-hooks.html b/samples/tooltip-hooks.html
new file mode 100644 (file)
index 0000000..1fbe8d2
--- /dev/null
@@ -0,0 +1,193 @@
+<!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>
index 295346a68e1b859aafa9719ba74a6c97ce85d3d1..854c48892f2b9ce09d105e4720a41826d718854a 100644 (file)
 
                                return this.left + Math.round(valueOffset);
                        } else {
-                               return this.top + (decimal * (this.height / this.ticks.length));
+                               return this.top + (decimal * this.height);
                        }
                },
 
index 38714968b7a95d6c11fe9a13d15109248d0813bc..2dc4dfd451124dc8ad9c80edfb801ae296dabf0e 100644 (file)
                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,
@@ -35,7 +40,7 @@
                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,
 
@@ -70,6 +75,7 @@
                                        _bodyFontFamily: options.tooltips.bodyFontFamily,
                                        _bodyFontStyle: options.tooltips.bodyFontStyle,
                                        bodyFontSize: options.tooltips.bodyFontSize,
+                                       bodySpacing: options.tooltips.bodySpacing,
                                        _bodposition: options.tooltips.bodposition,
 
                                        // Title
@@ -78,6 +84,8 @@
                                        _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,
@@ -85,6 +93,8 @@
                                        _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;
+                               }
+
                        }
                },
        });