]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Tooltips now using new lineArray format
authorTanner Linsley <tannerlinsley@gmail.com>
Wed, 7 Oct 2015 02:40:25 +0000 (20:40 -0600)
committerTanner Linsley <tannerlinsley@gmail.com>
Wed, 7 Oct 2015 02:40:25 +0000 (20:40 -0600)
samples/line.html
src/core/core.controller.js
src/core/core.helpers.js
src/core/core.js
src/core/core.scale.js
src/core/core.tooltip.js
src/scales/scale.radialLinear.js

index 9e0cf17d78d4a95d87b3d47cb0979931ad3b811d..546f69acd6968597b1003a00a891ef8d3f6a4ef2 100644 (file)
@@ -13,7 +13,7 @@
 </head>
 
 <body>
-    <div style="width:50%;">
+    <div style="width:100%;">
         <canvas id="canvas" style="width:100%;height:100%"></canvas>
     </div>
     <br>
             },
             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,
index 6d8d5e25e2b922ba2b022aef5c13bd5a3f057f9f..ccb3a7a0608ce7adac0b91f958d0e484692cb974 100644 (file)
                },
 
                generateLegend: function generateLegend() {
-                       return helpers.template(this.options.legendTemplate, this);
+                       return this.options.legendCallback(this);
                },
 
                destroy: function destroy() {
                eventHandler: function eventHandler(e) {
                        this.lastActive = this.lastActive || [];
 
-                       // Find Active Elements
+                       // Find Active Elements for hover and tooltips
                        if (e.type == 'mouseout') {
-                               this.active = [];
+                               this.active = this.tooltipActive = [];
                        } else {
                                this.active = function() {
                                        switch (this.options.hover.mode) {
                                                        return e;
                                        }
                                }.call(this);
+                               this.tooltipActive = function() {
+                                       switch (this.options.tooltips.mode) {
+                                               case 'single':
+                                                       return this.getElementAtEvent(e);
+                                               case 'label':
+                                                       return this.getElementsAtEvent(e);
+                                               case 'dataset':
+                                                       return this.getDatasetAtEvent(e);
+                                               default:
+                                                       return e;
+                                       }
+                               }.call(this);
                        }
 
                        // On Hover hook
 
                        var dataset;
                        var index;
+
                        // Remove styling for last active (even if it may still be active)
                        if (this.lastActive.length) {
                                switch (this.options.hover.mode) {
                                this.tooltip.initialize();
 
                                // Active
-                               if (this.active.length) {
+                               if (this.tooltipActive.length) {
                                        this.tooltip._model.opacity = 1;
 
                                        helpers.extend(this.tooltip, {
-                                               _active: this.active,
+                                               _active: this.tooltipActive,
                                        });
 
                                        this.tooltip.update();
index 8c848deba9f830b751bb85de5a973fd5da5f0139..d2f6c4d253e9e59f6378ee9df381197b09d6c487 100644 (file)
                                                                        base[key].push(helpers.configMerge(valueObj.type ? Chart.scaleService.getScaleDefaults(valueObj.type) : {}, valueObj));
                                                                } else if (valueObj.type !== base[key][index].type) {
                                                                        // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults
-                                                                       base[key][index] = helpers.configMerge(base[key][index], valueObj.type ? Chart.scaleService.getScaleDefaults(valueObj.type) : {}, valueObj)
+                                                                       base[key][index] = helpers.configMerge(base[key][index], valueObj.type ? Chart.scaleService.getScaleDefaults(valueObj.type) : {}, valueObj);
                                                                } else {
                                                                        // Type is the same
                                                                        base[key][index] = helpers.configMerge(base[key][index], valueObj);
                },
                log10 = helpers.log10 = function(x) {
                        if (Math.log10) {
-                               return Math.log10(x)
+                               return Math.log10(x);
                        } else {
                                return Math.log(x) / Math.LN10;
                        }
 
                        return niceFraction * Math.pow(10, exponent);
                },
-               /* jshint ignore:start */
-               // Blows up jshint errors based on the new Function constructor
-               //Templating methods
-               //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
-               templateStringCache = {},
-               template = helpers.template = function(templateString, valuesObject) {
-
-                       // If templateString is function rather than string-template - call the function for valuesObject
-
-                       if (templateString instanceof Function) {
-                               return templateString(valuesObject);
-                       }
-
-                       function tmpl(str, data) {
-                               // Figure out if we're getting a template, or if we need to
-                               // load the template - and be sure to cache the result.
-                               var fn;
-
-                               if (templateStringCache.hasOwnProperty(str)) {
-                                       fn = templateStringCache[str];
-                               } else {
-                                       // Generate a reusable function that will serve as a template
-                                       // generator (and which will be cached).
-                                       var functionCode = "var p=[],print=function(){p.push.apply(p,arguments);};" +
-
-                                               // Introduce the data as local variables using with(){}
-                                               "with(obj){p.push('" +
-
-                                               // Convert the template into pure JavaScript
-                                               str
-                                               .replace(/[\r\t\n]/g, " ")
-                                               .split("<%").join("\t")
-                                               .replace(/((^|%>)[^\t]*)'/g, "$1\r")
-                                               .replace(/\t=(.*?)%>/g, "',$1,'")
-                                               .split("\t").join("');")
-                                               .split("%>").join("p.push('")
-                                               .split("\r").join("\\'") +
-                                               "');}return p.join('');";
-                                       fn = new Function("obj", functionCode);
-
-                                       // Cache the result
-                                       templateStringCache[str] = fn;
-                               }
-
-                               // Provide some basic currying to the user
-                               return data ? fn(data) : fn;
-                       }
-                       return tmpl(templateString, valuesObject);
-               },
-               /* jshint ignore:end */
-               //--Animation methods
                //Easing functions adapted from Robert Penner's easing equations
                //http://www.robertpenner.com/easing/
                easingEffects = helpers.easingEffects = {
                                // can use classlist
                                hiddenIframe.classlist.add(hiddenIframeClass);
                        } else {
-                               hiddenIframe.setAttribute('class', hiddenIframeClass)
+                               hiddenIframe.setAttribute('class', hiddenIframeClass);
                        }
 
                        // Set the style
                                if (callback) {
                                        callback();
                                }
-                       }
+                       };
                },
                removeResizeListener = helpers.removeResizeListener = function(node) {
                        var hiddenIframe = node.querySelector('.chartjs-hidden-iframe');
index d930c024fd0f4017991a8a75be38a2701e11395a..a4eb71294d720ee0ae221aa13177cc239efb085c 100755 (executable)
                        // Element defaults defined in element extensions
                        elements: {},
 
-                       // Legend template string
-                       legendTemplate: "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i = 0; i < data.datasets.length; i++){%><li><span style=\"background-color:<%=data.datasets[i].backgroundColor%>\"><%if(data.datasets[i].label){%><%=data.datasets[i].label%><%}%></span></li><%}%></ul>",
+                       // Legend callback string
+                       legendCallback: function(chart) {
+                               var text = [];
+                               text.push('<ul class="' + chart.id + '-legend">');
+                               for (var i = 0; i < chart.data.datasets.length; i++) {
+                                       text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '">');
+                                       if (chart.data.datasets[i].label) {
+                                               text.push(chart.data.datasets[i].label);
+                                       }
+                                       text.push('</span></li>');
+                               }
+                               text.push('</ul>');
+
+                               return text.join("");
+                       }
                },
        };
 
index 40c73b4f5514c26b9d6550d88530f31ba7c7a103..295346a68e1b859aafa9719ba74a6c97ce85d3d1 100644 (file)
@@ -47,7 +47,9 @@
                        padding: 10,
                        reverse: false,
                        show: true,
-                       template: "<%=value%>",
+                       callback: function(value) {
+                               return value;
+                       },
                },
        };
 
                convertTicksToLabels: function() {
                        // Convert ticks to strings
                        this.ticks = this.ticks.map(function(numericalTick, index, ticks) {
-                               if (this.options.ticks.userCallback) {
-                                       return this.options.ticks.userCallback(numericalTick, index, ticks);
-                               } else {
-                                       return helpers.template(this.options.ticks.template, {
-                                               value: numericalTick
-                                       });
-                               }
-                       }, this);
+                                       if (this.options.ticks.userCallback) {
+                                               return this.options.ticks.userCallback(numericalTick, index, ticks);
+                                       }
+                                       return this.options.ticks.callback(numericalTick);
+                               },
+                               this);
                },
                afterTickToLabelConversion: helpers.noop,
 
                        }
 
                        // Are we showing a title for the scale?
-            if (this.options.scaleLabel.show) {
-                if (this.isHorizontal()) {
-                    this.minSize.height += (this.options.scaleLabel.fontSize * 1.5);
-                } else {
-                    this.minSize.width += (this.options.scaleLabel.fontSize * 1.5);
-                }
-            }
+                       if (this.options.scaleLabel.show) {
+                               if (this.isHorizontal()) {
+                                       this.minSize.height += (this.options.scaleLabel.fontSize * 1.5);
+                               } else {
+                                       this.minSize.width += (this.options.scaleLabel.fontSize * 1.5);
+                               }
+                       }
 
                        if (this.options.ticks.show && this.options.display) {
                                // Don't bother fitting the ticks if we are not showing them
                },
                afterFit: helpers.noop,
 
+
+
+
+
                // Shared Methods
                isHorizontal: function() {
                        return this.options.position == "top" || this.options.position == "bottom";
                },
 
+               getLabelForIndex: function(index, datasetIndex) {
+                       if (this.isHorizontal()) {
+                               return this.data.datasets[datasetIndex].label || this.data.labels[index];
+                       }
+                       return this.data.datasets[datasetIndex].data[index];
+               },
+
                // Used to get data value locations.  Value can either be an index or a numerical value
                getPixelForValue: helpers.noop,
 
                                                                }
                                                        }
 
-                                                       
+
                                                        this.ctx.translate(xLabelValue, yLabelValue);
                                                        this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1);
                                                        this.ctx.font = this.font;
index 92e2b136463f7c5c9a916cf9bfb7714a1553e6a5..38714968b7a95d6c11fe9a13d15109248d0813bc 100644 (file)
@@ -9,33 +9,50 @@
        Chart.defaults.global.tooltips = {
                enabled: true,
                custom: null,
+               mode: 'single',
                backgroundColor: "rgba(0,0,0,0.8)",
-               fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
-               fontSize: 10,
-               fontStyle: "normal",
-               fontColor: "#fff",
                titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
                titleFontSize: 12,
                titleFontStyle: "bold",
-               titleFontColor: "#fff",
+               titleColor: "#fff",
+               titleAlign: "left",
+               bodyFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+               bodyFontSize: 12,
+               bodyFontStyle: "normal",
+               bodyColor: "#fff",
+               bodyAlign: "left",
+               footerFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+               footerFontSize: 12,
+               footerFontStyle: "bold",
+               footerColor: "#fff",
+               footerAlign: "left",
                yPadding: 6,
                xPadding: 6,
-               caretSize: 8,
+               caretSize: 5,
                cornerRadius: 6,
                xOffset: 10,
-               template: [
-                       '<% if(label){ %>',
-                       '<%=label %>: ',
-                       '<% } %>',
-                       '<%=value %>',
-               ].join(''),
-               multiTemplate: [
-                       '<%if (datasetLabel){ %>',
-                       '<%=datasetLabel %>: ',
-                       '<% } %>',
-                       '<%=value %>'
-               ].join(''),
                multiKeyBackground: '#fff',
+               callbacks: {
+                       beforeTitle: helpers.noop,
+                       title: function(xLabel, yLabel, index, datasetIndex, data) {
+                               return data.datasets[datasetIndex].label;
+                       },
+                       afterTitle: helpers.noop,
+
+                       beforeBody: helpers.noop,
+
+                       beforeLabel: helpers.noop,
+                       label: function(xLabel, yLabel, index, datasetIndex, data) {
+                               return xLabel + ': ' + yLabel;
+                       },
+                       afterLabel: helpers.noop,
+
+                       afterBody: helpers.noop,
+
+                       beforeFooter: helpers.noop,
+                       footer: helpers.noop,
+                       afterFooter: helpers.noop,
+               },
        };
 
        Chart.Tooltip = Chart.Element.extend({
                                        yPadding: options.tooltips.yPadding,
                                        xOffset: options.tooltips.xOffset,
 
-                                       // Labels
-                                       textColor: options.tooltips.fontColor,
-                                       _fontFamily: options.tooltips.fontFamily,
-                                       _fontStyle: options.tooltips.fontStyle,
-                                       fontSize: options.tooltips.fontSize,
+                                       // Body
+                                       bodyColor: options.tooltips.bodyColor,
+                                       _bodyFontFamily: options.tooltips.bodyFontFamily,
+                                       _bodyFontStyle: options.tooltips.bodyFontStyle,
+                                       bodyFontSize: options.tooltips.bodyFontSize,
+                                       _bodposition: options.tooltips.bodposition,
 
                                        // Title
-                                       titleTextColor: options.tooltips.titleFontColor,
+                                       titleColor: options.tooltips.titleColor,
                                        _titleFontFamily: options.tooltips.titleFontFamily,
                                        _titleFontStyle: options.tooltips.titleFontStyle,
                                        titleFontSize: options.tooltips.titleFontSize,
+                                       _titleAlign: options.tooltips.titleAlign,
+
+                                       // Footer
+                                       footerColor: options.tooltips.footerColor,
+                                       _footerFontFamily: options.tooltips.footerFontFamily,
+                                       _footerFontStyle: options.tooltips.footerFontStyle,
+                                       footerFontSize: options.tooltips.footerFontSize,
+                                       _footerAlign: options.tooltips.footerAlign,
 
                                        // Appearance
-                                       caretHeight: options.tooltips.caretSize,
+                                       caretSize: options.tooltips.caretSize,
                                        cornerRadius: options.tooltips.cornerRadius,
                                        backgroundColor: options.tooltips.backgroundColor,
                                        opacity: 0,
                                },
                        });
                },
-               update: function() {
 
-                       var ctx = this._chart.ctx;
+               getTitle: function() {
+                       var beforeTitle = this._options.tooltips.callbacks.beforeTitle.apply(this, arguments),
+                               title = this._options.tooltips.callbacks.title.apply(this, arguments),
+                               afterTitle = this._options.tooltips.callbacks.afterTitle.apply(this, arguments);
 
-                       switch (this._options.hover.mode) {
-                               case 'single':
-                                       helpers.extend(this._model, {
-                                               text: helpers.template(this._options.tooltips.template, {
-                                                       // These variables are available in the template function. Add others here
-                                                       element: this._active[0],
-                                                       value: this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index],
-                                                       label: this._active[0]._model.label !== undefined ? this._active[0]._model.label : this._data.labels ? this._data.labels[this._active[0]._index] : '',
-                                               }),
-                                       });
-
-                                       var tooltipPosition = this._active[0].tooltipPosition();
-                                       helpers.extend(this._model, {
-                                               x: Math.round(tooltipPosition.x),
-                                               y: Math.round(tooltipPosition.y),
-                                               caretPadding: tooltipPosition.padding
-                                       });
+                       var lines = [];
 
-                                       break;
+                       if (beforeTitle) {
+                               lines.push(beforeTitle);
+                       }
+                       if (title) {
+                               lines.push(title);
+                       }
+                       if (afterTitle) {
+                               lines.push(afterTitle);
+                       }
+                       return lines;
+               },
 
-                               case 'label':
+               getBody: function(xLabel, yLabel, index, datasetIndex) {
 
-                                       // Tooltip Content
+                       var lines = [];
 
-                                       var dataArray,
-                                               dataIndex;
+                       var beforeBody = this._options.tooltips.callbacks.beforeBody.apply(this, arguments);
+                       if (beforeBody) {
+                               lines.push(beforeBody);
+                       }
 
-                                       var labels = [],
-                                               colors = [];
+                       var beforeLabel,
+                               afterLabel,
+                               label;
 
-                                       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;
-                                               }
-                                       }
+                       if (helpers.isArray(xLabel)) {
 
-                                       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(helpers.template(this._options.tooltips.multiTemplate, {
-                                                               // These variables are available in the template function. Add others here
-                                                               element: element,
-                                                               datasetLabel: this._data.datasets[element._datasetIndex].label,
-                                                               value: 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.fontSize) + ((labels.length - 1) * (this._model.fontSize / 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.fontSize + 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;
-                                       }
+                               var labels = [];
 
-                                       //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;
+                               // 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);
+
+                                       labels.push((beforeLabel ? beforeLabel : '') + this._options.tooltips.callbacks.label(xLabel[i], yLabel[i], index, datasetIndex) + (afterLabel ? afterLabel : ''));
+
+                               }
+
+                               if (labels.length) {
+                                       lines = lines.concat(labels);
+                               }
+
+                       } else {
+
+                               // Run the single label through the callback
+
+                               beforeLabel = this._options.tooltips.callbacks.beforeLabel.apply(this, arguments);
+                               label = this._options.tooltips.callbacks.label.apply(this, arguments);
+                               afterLabel = this._options.tooltips.callbacks.afterLabel.apply(this, arguments);
+
+                               if (beforeLabel || label || afterLabel) {
+                                       lines.push((beforeLabel ? afterLabel : '') + label + (afterLabel ? afterLabel : ''));
+                               }
                        }
 
+                       var afterBody = this._options.tooltips.callbacks.afterBody.apply(this, arguments);
+                       if (afterBody) {
+                               lines.push(afterBody);
+                       }
+
+                       return lines;
+               },
+
+               getFooter: function() {
+                       var beforeFooter = this._options.tooltips.callbacks.beforeFooter.apply(this, arguments),
+                               footer = this._options.tooltips.callbacks.footer.apply(this, arguments),
+                               afterFooter = this._options.tooltips.callbacks.afterFooter.apply(this, arguments);
+
+                       var lines = [];
+
+                       if (beforeFooter) {
+                               lines.push(beforeFooter);
+                       }
+                       if (footer) {
+                               lines.push(footer);
+                       }
+                       if (afterFooter) {
+                               lines.push(afterFooter);
+                       }
+
+                       return lines;
+               },
+
+               update: function() {
+
+                       var ctx = this._chart.ctx;
+
+                       var element = this._active[0],
+                               xLabel,
+                               yLabel,
+                               tooltipPosition;
+
+                       if (this._options.tooltips.mode == 'single') {
+
+                               xLabel = element._xScale.getLabelForIndex(element._index, element._datasetIndex);
+                               yLabel = element._yScale.getLabelForIndex(element._index, element._datasetIndex);
+                               tooltipPosition = this._active[0].tooltipPosition();
+
+                       } else {
+
+                               xLabel = [];
+                               yLabel = [];
+                               helpers.each(this._data.datasets, function(dataset, datasetIndex) {
+                                       xLabel.push(element._xScale.getLabelForIndex(element._index, datasetIndex));
+                                       yLabel.push(element._yScale.getLabelForIndex(element._index, datasetIndex));
+                               });
+                               tooltipPosition = this._active[0].tooltipPosition();
+
+                               // 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),
+                               body: this.getBody(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
+                       });
+
                        return this;
                },
                draw: function() {
                        var ctx = this._chart.ctx;
                        var vm = this._view;
 
-                       switch (this._options.hover.mode) {
-                               case 'single':
+                       // Get Dimensions
 
-                                       ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily);
+                       vm.position = "top";
 
-                                       vm.xAlign = "center";
-                                       vm.yAlign = "above";
+                       var caretPadding = vm.caretPadding || 2;
 
-                                       //Distance between the actual element.y position and the start of the tooltip caret
-                                       var caretPadding = vm.caretPadding || 2;
+                       // Height
+                       var tooltipHeight = vm.yPadding * 2;
 
-                                       var tooltipWidth = ctx.measureText(vm.text).width + 2 * vm.xPadding,
-                                               tooltipRectHeight = vm.fontSize + 2 * vm.yPadding,
-                                               tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding;
+                       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
 
-                                       if (vm.x + tooltipWidth / 2 > this._chart.width) {
-                                               vm.xAlign = "left";
-                                       } else if (vm.x - tooltipWidth / 2 < 0) {
-                                               vm.xAlign = "right";
-                                       }
+                       // Width
+                       var tooltipWidth = 0;
+                       helpers.each(vm.title, function(line, i) {
+                               ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
+                               tooltipWidth = Math.max(tooltipWidth, ctx.measureText(line).width);
+                       });
+                       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);
+                       });
+                       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);
+                       });
+                       tooltipWidth += 2 * vm.xPadding;
+                       var tooltipTotalWidth = tooltipWidth + vm.caretSize + caretPadding;
 
-                                       if (vm.y - tooltipHeight < 0) {
-                                               vm.yAlign = "below";
-                                       }
 
-                                       var tooltipX = vm.x - tooltipWidth / 2,
-                                               tooltipY = vm.y - tooltipHeight;
+                       // Smart Tooltip placement to stay on the canvas
 
-                                       ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
+                       // Top, center, or bottom
+                       vm.yAlign = "center";
+                       if (vm.y - (tooltipHeight / 2) < 0) {
+                               vm.yAlign = "top";
+                       } else if (vm.y + (tooltipHeight / 2) > this._chart.height) {
+                               vm.yAlign = "bottom";
+                       }
 
-                                       // Custom Tooltips
-                                       if (this._options.tooltips.custom) {
-                                               this._options.tooltips.custom(this);
-                                       }
-                                       if (!this._options.tooltips.enabled) {
-                                               return;
-                                       }
 
-                                       switch (vm.yAlign) {
-                                               case "above":
-                                                       //Draw a caret above the x/y
-                                                       ctx.beginPath();
-                                                       ctx.moveTo(vm.x, vm.y - caretPadding);
-                                                       ctx.lineTo(vm.x + vm.caretHeight, vm.y - (caretPadding + vm.caretHeight));
-                                                       ctx.lineTo(vm.x - vm.caretHeight, vm.y - (caretPadding + vm.caretHeight));
-                                                       ctx.closePath();
-                                                       ctx.fill();
-                                                       break;
-                                               case "below":
-                                                       tooltipY = vm.y + caretPadding + vm.caretHeight;
-                                                       //Draw a caret below the x/y
-                                                       ctx.beginPath();
-                                                       ctx.moveTo(vm.x, vm.y + caretPadding);
-                                                       ctx.lineTo(vm.x + vm.caretHeight, vm.y + caretPadding + vm.caretHeight);
-                                                       ctx.lineTo(vm.x - vm.caretHeight, vm.y + caretPadding + vm.caretHeight);
-                                                       ctx.closePath();
-                                                       ctx.fill();
-                                                       break;
+                       // Left or Right
+                       vm.xAlign = "right";
+                       if (vm.x + tooltipTotalWidth > this._chart.width) {
+                               vm.xAlign = "left";
+                       }
+
+
+                       // Background Position
+                       var tooltipX = vm.x,
+                               tooltipY = vm.y;
+
+                       if (vm.yAlign == 'top') {
+                               tooltipY = vm.y - vm.caretSize - vm.cornerRadius;
+                       } else if (vm.yAlign == 'bottom') {
+                               tooltipY = vm.y - tooltipHeight + vm.caretSize + vm.cornerRadius;
+                       } else {
+                               tooltipY = vm.y - (tooltipHeight / 2);
+                       }
+
+                       if (vm.xAlign == 'left') {
+                               tooltipX = vm.x - tooltipTotalWidth;
+                       } else if (vm.xAlign == 'right') {
+                               tooltipX = vm.x + caretPadding + vm.caretSize;
+                       } else {
+                               tooltipX = vm.x + (tooltipTotalWidth / 2);
+                       }
+
+                       // Draw Background
+
+                       if (this._options.tooltips.enabled) {
+                               ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
+                               helpers.drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipHeight, vm.cornerRadius);
+                               ctx.fill();
+                       }
+
+
+                       // Draw Caret
+                       if (this._options.tooltips.enabled) {
+                               ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
+
+                               if (vm.xAlign == 'left') {
+
+                                       ctx.beginPath();
+                                       ctx.moveTo(vm.x - caretPadding, vm.y);
+                                       ctx.lineTo(vm.x - caretPadding - vm.caretSize, vm.y - vm.caretSize);
+                                       ctx.lineTo(vm.x - caretPadding - vm.caretSize, vm.y + vm.caretSize);
+                                       ctx.closePath();
+                                       ctx.fill();
+                               } else {
+                                       ctx.beginPath();
+                                       ctx.moveTo(vm.x + caretPadding, vm.y);
+                                       ctx.lineTo(vm.x + caretPadding + vm.caretSize, vm.y - vm.caretSize);
+                                       ctx.lineTo(vm.x + caretPadding + vm.caretSize, vm.y + vm.caretSize);
+                                       ctx.closePath();
+                                       ctx.fill();
+                               }
+                       }
+
+                       // Draw Title, Body, and Footer
+
+                       if (this._options.tooltips.enabled) {
+
+                               var bodyStart,
+                                       footerStart;
+
+                               // 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);
+
+
+                               // Body
+                               ctx.textAlign = vm._bodyAlign;
+                               ctx.textBaseline = "top";
+                               ctx.fillStyle = helpers.color(vm.bodyColor).alpha(vm.opacity).rgbString();
+                               ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
 
-                                       switch (vm.xAlign) {
-                                               case "left":
-                                                       tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight);
-                                                       break;
-                                               case "right":
-                                                       tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight);
-                                                       break;
+                               console.log(bodyStart);
+
+                               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);
+
+                               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);
+                       }
+
+                       switch (this._options.tooltips.mode) {
+                               case 'single':
 
-                                       helpers.drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, vm.cornerRadius);
 
-                                       ctx.fill();
 
                                        ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
                                        ctx.textAlign = "center";
                                        ctx.textBaseline = "middle";
-                                       ctx.fillText(vm.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2);
+                                       ctx.fillText(vm.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipHeight / 2);
                                        break;
                                case 'label':
 
-                                       // Custom Tooltips
-                                       if (this._options.tooltips.custom) {
-                                               this._options.tooltips.custom(this);
-                                       }
-                                       if (!this._options.tooltips.enabled) {
-                                               return;
-                                       }
 
-                                       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();
+                                       //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();
 
+                                       // Title
                                        ctx.textAlign = "left";
                                        ctx.textBaseline = "middle";
-                                       ctx.fillStyle = helpers.color(vm.titleTextColor).alpha(vm.opacity).rgbString();
-                                       ctx.font = helpers.fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily);
+                                       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));
 
-                                       ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily);
+                                       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.fontSize + 3, this.getLineHeight(index + 1));
+                                               ctx.fillText(label, vm.x + vm.xPadding + vm.bodyFontSize + 3, this.getLineHeight(index + 1));
 
                                                //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.fontSize/2, vm.fontSize, vm.fontSize);
+                                               //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.fontSize / 2 - 1, vm.fontSize + 2, vm.fontSize + 2);
+                                               ctx.fillRect(vm.x + vm.xPadding - 1, this.getLineHeight(index + 1) - vm.bodyFontSize / 2 - 1, vm.bodyFontSize + 2, vm.bodyFontSize + 2);
 
                                                ctx.fillStyle = helpers.color(vm.legendColors[index].fill).alpha(vm.opacity).rgbString();
-                                               ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize);
+                                               ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.bodyFontSize / 2, vm.bodyFontSize, vm.bodyFontSize);
 
 
                                        }, this);
                                        break;
                        }
                },
-               getLineHeight: function(index) {
-                       var baseLineHeight = this._view.y - (this._view.height / 2) + this._view.yPadding,
-                               afterTitleIndex = index - 1;
-
-                       //If the index is zero, we're getting the title
-                       if (index === 0) {
-                               return baseLineHeight + this._view.titleFontSize / 2;
-                       } else {
-                               return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5;
-                       }
-
-               },
        });
 
 }).call(this);
index b9fae20a7b65fe54882920529701a1c58344ea96..269d788de7fd7a796bf8030f40f5c590851e74e2 100644 (file)
@@ -59,7 +59,7 @@
                        this.height = this.maxHeight;
                        this.xCenter = Math.round(this.width / 2);
                        this.yCenter = Math.round(this.height / 2);
-                       
+
                        var minSize = helpers.min([this.height, this.width]);
                        this.drawingArea = (this.options.display) ? (minSize / 2) - (this.options.ticks.fontSize / 2 + this.options.ticks.backdropPaddingY) : (minSize / 2);
                },
                        for (i = 0; i < this.getValueCount(); i++) {
                                // 5px to space the text slightly out - similar to what we do in the draw function.
                                pointPosition = this.getPointPosition(i, largestPossibleRadius);
-                               textWidth = this.ctx.measureText(helpers.template(this.options.ticks.template, {
-                                       value: this.data.labels[i]
-                               })).width + 5;
+                               textWidth = this.ctx.measureText(this.options.ticks.callback(this.data.labels[i])).width + 5;
                                if (i === 0 || i === this.getValueCount() / 2) {
                                        // If we're at index zero, or exactly the middle, we're at exactly the top/bottom
                                        // of the radar chart, so text will be aligned centrally, so we'll half it and compare