Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom tooltip: add data points infos #3201

Merged
merged 11 commits into from
Oct 19, 2016
12 changes: 12 additions & 0 deletions docs/01-Chart-Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,18 @@ afterBody | `Array[tooltipItem], data` | Text to render after the body section
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[CustomTooltipDataPoint], data` | List of matching point informations.

#### CustomTooltipDataPoint
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the section immediately below this describes the same thing. At a minimum we should add the new x and y properties to that code snippet as well. Maybe we should consider merging these sections as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @etimberg ,

I am not sure what you want me to do here. Could you explain more ?

Which sections do you want to merge ? If you are talking about CustomTooltipDataPoint and the one just before, dataPoints is an array so it should be defined as an external section right ?

Also for x and y, which code snippet are you talking about ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the snippet here https://github.com/chartjs/Chart.js/blob/master/docs/01-Chart-Configuration.md#tooltip-item-interface

I was also thinking to merge with the same section to that 2nd column of the last line of the table becomes Array[TooltipItemInterface]. In other words, merging the CustomTooltipDataPoint and TooltipItemInterface sections since they describe the same thing.

Also, for the tooltip item do we need the x and y properties? the user can look them up if necessary.

Copy link
Contributor Author

@bydooweedoo bydooweedoo Oct 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, yes up to you we can merge the two interface together.

I didn't know TooltipItemInterface and the CustomTooltipDataPoint were referring to the same thing, that's why I separate the two in the first place.

In my opinion, x and y properties are usefull. I am using them in several of my projects to be able to show individual tooltips at each points position.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok. That's my bad, I should have been clearer. If you can merge the doc sections and fix the merge conflict I'll merge this


Name | Type | Description
--- | --- | ---
index | Number | Matching point index.
datasetIndex | Number | Matching dataset index.
xLabel | String | Matching label on X axis.
yLabel | String | Matching label on Y axis.
pointX | Number | X position of matching point.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking, while the containing object isdataPoints, wouldn't x and y be better names?

dataPoints.forEach(function(point) {
    point.x; // compared to point.pointX
    point.y; // compared to point.pointY
});

Would also be more consistent with index (and not pointIndex).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think you are right :)

It makes more sense. I will change this.

pointY | Number | Y position of matching point.

#### Tooltip Item Interface

Expand Down
16 changes: 8 additions & 8 deletions docs/09-Advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,12 @@ Scale instances are given the following properties during the fitting process.
{
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,
Expand All @@ -225,7 +225,7 @@ Scale instances are given the following properties during the fitting process.
```

#### 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
{
Expand Down Expand Up @@ -260,10 +260,10 @@ To work with Chart.js, custom scale types must implement the following interface
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.
Expand All @@ -280,7 +280,7 @@ Optionally, the following methods may also be overwritten, but an implementation

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() {},

Expand Down Expand Up @@ -350,7 +350,7 @@ The following methods may optionally be overridden by derived dataset controller
// 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() {}
}
```
Expand Down Expand Up @@ -419,7 +419,7 @@ Plugins should derive from Chart.PluginBase and implement the following interfac

### 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:

Expand Down
104 changes: 104 additions & 0 deletions samples/dataPoints-customTooltips.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<!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.pointY + "px",
left: dataPoint.pointX + "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>
7 changes: 5 additions & 2 deletions src/core/core.tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ module.exports = function(Chart) {
xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
index: index,
datasetIndex: datasetIndex
datasetIndex: datasetIndex,
pointX: element._model.x,
pointY: element._model.y
};
}

Expand Down Expand Up @@ -312,7 +314,8 @@ module.exports = function(Chart) {
x: Math.round(tooltipPosition.x),
y: Math.round(tooltipPosition.y),
caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2),
labelColors: labelColors
labelColors: labelColors,
dataPoints: tooltipItems
});

// We need to determine alignment of
Expand Down
60 changes: 60 additions & 0 deletions test/core.tooltip.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,4 +458,64 @@ describe('tooltip tests', function() {
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].pointX).toBeCloseToPixel(point._model.x);
expect(tooltip._view.dataPoints[0].pointY).toBeCloseToPixel(point._model.y);
});
});