Expose tooltip items from tooltip model and added `x` and `y` properties to `TooltipItemInterface`
beforeFooter | `Array[tooltipItem], data` | Text to render before the footer section
footer | `Array[tooltipItem], data` | Text to render as the footer
afterFooter | `Array[tooltipItem], data` | Text to render after the footer section
+dataPoints | `Array[tooltipItem]` | List of matching point informations.
#### Tooltip Item Interface
datasetIndex: Number,
// Index of this data item in the dataset
- index: Number
+ index: Number,
+
+ // X position of matching point
+ x: Number,
+
+ // Y position of matching point
+ y: Number,
}
```
{
left: Number, // left edge of the scale bounding box
right: Number, // right edge of the bounding box'
- top: Number,
+ top: Number,
bottom: Number,
width: Number, // the same as right - left
height: Number, // the same as bottom - top
- // Margin on each side. Like css, this is outside the bounding box.
+ // Margin on each side. Like css, this is outside the bounding box.
margins: {
left: Number,
right: Number,
```
#### Scale Interface
-To work with Chart.js, custom scale types must implement the following interface.
+To work with Chart.js, custom scale types must implement the following interface.
```javascript
{
Optionally, the following methods may also be overwritten, but an implementation is already provided by the `Chart.Scale` base class.
```javascript
- // Transform the ticks array of the scale instance into strings. The default implementation simply calls this.options.ticks.callback(numericalTick, index, ticks);
+ // Transform the ticks array of the scale instance into strings. The default implementation simply calls this.options.ticks.callback(numericalTick, index, ticks);
convertTicksToLabels: function() {},
- // Determine how much the labels will rotate by. The default implementation will only rotate labels if the scale is horizontal.
+ // Determine how much the labels will rotate by. The default implementation will only rotate labels if the scale is horizontal.
calculateTickRotation: function() {},
// Fits the scale into the canvas.
The Core.Scale base class also has some utility functions that you may find useful.
```javascript
-{
+{
// Returns true if the scale instance is horizontal
isHorizontal: function() {},
// chart types using a single scale
linkScales: function() {},
- // Called by the main chart controller when an update is triggered. The default implementation handles the number of data points changing and creating elements appropriately.
+ // Called by the main chart controller when an update is triggered. The default implementation handles the number of data points changing and creating elements appropriately.
buildOrUpdateElements: function() {}
}
```
### Building Chart.js
-Chart.js uses <a href="http://gulpjs.com/" target="_blank">gulp</a> to build the library into a single JavaScript file.
+Chart.js uses <a href="http://gulpjs.com/" target="_blank">gulp</a> to build the library into a single JavaScript file.
Firstly, we need to ensure development dependencies are installed. With node and npm installed, after cloning the Chart.js repo to a local directory, and navigating to that directory in the command line, we can run the following:
--- /dev/null
+<!doctype html>
+<html>
+
+<head>
+ <title>Custom Tooltips using Data Points</title>
+ <script src="../dist/Chart.bundle.js"></script>
+ <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
+ <style>
+ canvas{
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ }
+ .chartjs-tooltip {
+ opacity: 1;
+ position: absolute;
+ background: rgba(0, 0, 0, .7);
+ color: white;
+ border-radius: 3px;
+ -webkit-transition: all .1s ease;
+ transition: all .1s ease;
+ pointer-events: none;
+ -webkit-transform: translate(-50%, 0);
+ transform: translate(-50%, 0);
+ padding: 4px;
+ }
+
+ .chartjs-tooltip-key {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ }
+ </style>
+</head>
+
+<body>
+ <div id="canvas-holder1" style="width:75%;">
+ <canvas id="chart1"></canvas>
+ </div>
+ <div class="chartjs-tooltip" id="tooltip-0"></div>
+ <div class="chartjs-tooltip" id="tooltip-1"></div>
+ <script>
+ var customTooltips = function (tooltip) {
+ $(this._chart.canvas).css("cursor", "pointer");
+
+ $(".chartjs-tooltip").css({
+ opacity: 0,
+ });
+
+ if (!tooltip || !tooltip.opacity) {
+ return;
+ }
+
+ if (tooltip.dataPoints.length > 0) {
+ tooltip.dataPoints.forEach(function (dataPoint) {
+ var content = [dataPoint.xLabel, dataPoint.yLabel].join(": ");
+ var $tooltip = $("#tooltip-" + dataPoint.datasetIndex);
+
+ $tooltip.html(content);
+ $tooltip.css({
+ opacity: 1,
+ top: dataPoint.y + "px",
+ left: dataPoint.x + "px",
+ });
+ });
+ }
+ };
+ var randomScalingFactor = function() {
+ return Math.round(Math.random() * 100);
+ };
+ var lineChartData = {
+ labels: ["January", "February", "March", "April", "May", "June", "July"],
+ datasets: [{
+ pointHitRadius: 100,
+ label: "My First dataset",
+ data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()]
+ }, {
+ pointHitRadius: 100,
+ label: "My Second dataset",
+ data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()]
+ }]
+ };
+
+ window.onload = function() {
+ var chartEl = document.getElementById("chart1");
+ var chart = new Chart(chartEl, {
+ type: "line",
+ data: lineChartData,
+ options: {
+ title:{
+ display: true,
+ text: "Chart.js - Custom Tooltips using Data Points"
+ },
+ tooltips: {
+ enabled: false,
+ custom: customTooltips
+ }
+ }
+ });
+ };
+ </script>
+</body>
+
+</html>
xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
index: index,
- datasetIndex: datasetIndex
+ datasetIndex: datasetIndex,
+ x: element._model.x,
+ y: element._model.y
};
}
model.caretPadding = helpers.getValueOrDefault(tooltipPosition.padding, 2);
model.labelColors = labelColors;
+ // data points
+ model.dataPoints = tooltipItems;
+
// We need to determine alignment of the tooltip
tooltipSize = getTooltipSize(this, model);
alignment = determineAlignment(this, tooltipSize);
expect(tooltip._view.x).toBeCloseToPixel(269);
expect(tooltip._view.y).toBeCloseToPixel(155);
});
+
+ it('Should have dataPoints', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ },
+ options: {
+ tooltips: {
+ mode: 'single'
+ }
+ }
+ });
+
+ // Trigger an event over top of the
+ var pointIndex = 1;
+ var datasetIndex = 0;
+ var meta = chartInstance.getDatasetMeta(datasetIndex);
+ var point = meta.data[pointIndex];
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+ var evt = new MouseEvent('mousemove', {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + point._model.x,
+ clientY: rect.top + point._model.y
+ });
+
+ // Manully trigger rather than having an async test
+ node.dispatchEvent(evt);
+
+ // Check and see if tooltip was displayed
+ var tooltip = chartInstance.tooltip;
+
+ expect(tooltip._view instanceof Object).toBe(true);
+ expect(tooltip._view.dataPoints instanceof Array).toBe(true);
+ expect(tooltip._view.dataPoints.length).toEqual(1);
+ expect(tooltip._view.dataPoints[0].index).toEqual(pointIndex);
+ expect(tooltip._view.dataPoints[0].datasetIndex).toEqual(datasetIndex);
+ expect(tooltip._view.dataPoints[0].xLabel).toEqual(
+ chartInstance.config.data.labels[pointIndex]
+ );
+ expect(tooltip._view.dataPoints[0].yLabel).toEqual(
+ chartInstance.config.data.datasets[datasetIndex].data[pointIndex]
+ );
+ expect(tooltip._view.dataPoints[0].x).toBeCloseToPixel(point._model.x);
+ expect(tooltip._view.dataPoints[0].y).toBeCloseToPixel(point._model.y);
+ });
});