Skip to content

Commit

Permalink
Support tooltips
Browse files Browse the repository at this point in the history
Based largely on work by @Sly1024 in #73
  • Loading branch information
drewnoakes committed Aug 25, 2017
1 parent 77b668d commit 1815596
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 2 deletions.
26 changes: 26 additions & 0 deletions builder/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@

var js = "";

document.getElementById('css-code').style.display = chart.options.tooltip ? 'block' : 'none';

if (chart.options.timestampFormatter) {
chartOptions.timestampFormatter = "SmoothieChart.timeFormatter";
}
Expand Down Expand Up @@ -399,6 +401,7 @@
bindRange({target: canvas, name: 'Chart width', propertyName: 'width', min: 20, max: 1000});
bindRange({target: chart, name: 'Delay', propertyName: 'delay', min: 0, max: 2000});
bindCheckBox({target: chart.options, name: 'Scroll Backwards', propertyName: 'scrollBackwards'});
bindCheckBox({target: chart.options, name: 'Show Tooltip', propertyName: 'tooltip'});

// Series
startControlSection('Series');
Expand Down Expand Up @@ -548,6 +551,16 @@
border: 1px solid black;
box-sizing: border-box;
}

div.smoothie-chart-tooltip {
background: #444;
padding: 1em;
margin-top: 20px;
font-family: consolas;
color: white;
font-size: 10px;
pointer-events: none;
}
</style>
</head>
<body onload="onLoad()">
Expand All @@ -569,6 +582,19 @@ <h3>Code</h3>
<label for="javascript-code">JavaScript:</label><br>
<textarea readonly id="javascript-code" style="min-height:140px;"></textarea>
</div>
<div id="css-code">
<label for="css-code">CSS:</label><br>
<textarea readonly id="css-code" style="min-height:160px;">div.smoothie-chart-tooltip {
background: #444;
padding: 1em;
margin-top: 20px;
font-family: consolas;
color: white;
font-size: 10px;
pointer-events: none;
}
</textarea>
</div>
</div>

</body>
Expand Down
4 changes: 4 additions & 0 deletions smoothie.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ export interface IChartOptions {

labels?: ILabelOptions;

tooltip?: boolean;
tooltipLine?: { lineWidth: number, strokeStyle: string };
tooltipFormatter?: (timestamp: number, data: {series: TimeSeries, index: number, value: number}[]) => string;

/** Allows the chart to stretch according to its containers and layout settings. Default is <code>false</code>, for backwards compatibility. */
responsive?: boolean;
}
Expand Down
134 changes: 132 additions & 2 deletions smoothie.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
* v1.29: Support responsive sizing, by @drewnoakes
* v1.29.1: Include types in package, and make property optional, by @TrentHouliston
* v1.30: Fix inverted logic in devicePixelRatio support, by @scanlime
* v1.31: Support tooltips, by @Sly1024 and @drewnoakes
*/

;(function(exports) {
Expand All @@ -103,6 +104,18 @@
}
}
return arguments[0];
},
binarySearch: function(data, value) {
var low = 0,
high = data.length;
while (low < high) {
var mid = (low + high) >> 1;
if (value < data[mid][0])
high = mid;
else
low = mid + 1;
}
return low;
}
};

Expand Down Expand Up @@ -264,7 +277,13 @@
* fontSize: 15,
* fontFamily: 'sans-serif',
* precision: 2
* }
* },
* tooltip: false // show tooltip when mouse is over the chart
* tooltipLine: { // properties for a vertical line at the cursor position
* lineWidth: 1,
* strokeStyle: '#BBBBBB'
* },
* tooltipFormatter: SmoothieChart.tooltipFormatter // formatter function for tooltip text
* }
* </pre>
*
Expand All @@ -276,8 +295,24 @@
this.currentValueRange = 1;
this.currentVisMinValue = 0;
this.lastRenderTimeMillis = 0;

this.mousemove = this.mousemove.bind(this);
this.mouseout = this.mouseout.bind(this);
}

/** Formats the HTML string content of the tooltip. */
SmoothieChart.tooltipFormatter = function (timestamp, data) {
var timestampFormatter = this.options.timestampFormatter || SmoothieChart.timeFormatter,
lines = [timestampFormatter(new Date(timestamp))];

for (var i = 0; i < data.length; ++i) {
lines.push('<span style="color:' + data[i].series.options.strokeStyle + '">' +
this.options.yMaxFormatter(data[i].value, this.options.labels.precision) + '</span>');
}

return lines.join('<br>');
};

SmoothieChart.defaultChartOptions = {
millisPerPixel: 20,
enableDpiScaling: true,
Expand Down Expand Up @@ -310,6 +345,12 @@
precision: 2
},
horizontalLines: [],
tooltip: false,
tooltipLine: {
lineWidth: 1,
strokeStyle: '#BBBBBB'
},
tooltipFormatter: SmoothieChart.tooltipFormatter,
responsive: false
};

Expand Down Expand Up @@ -437,6 +478,78 @@
this.start();
};

SmoothieChart.prototype.getTooltipEl = function () {
// Use a single tooltip element across all chart instances
var el = SmoothieChart.tooltipEl;
if (!el) {
el = SmoothieChart.tooltipEl = document.createElement('div');
el.className = 'smoothie-chart-tooltip';
el.style.position = 'absolute';
el.style.display = 'none';
document.body.appendChild(el);
}
return el;
};

SmoothieChart.prototype.updateTooltip = function () {
var el = this.getTooltipEl();

if (!this.mouseover || !this.options.tooltip) {
el.style.display = 'none';
return;
}

var time = this.lastRenderTimeMillis - (this.delay || 0);

// Round time down to pixel granularity, so motion appears smoother.
time -= time % this.options.millisPerPixel;

// x pixel to time
var t = this.options.scrollBackwards
? time - this.mouseX * this.options.millisPerPixel
: time - (this.canvas.offsetWidth - this.mouseX) * this.options.millisPerPixel;

var data = [];

// For each data set...
for (var d = 0; d < this.seriesSet.length; d++) {
var timeSeries = this.seriesSet[d].timeSeries,
// find datapoint closest to time 't'
closeIdx = Util.binarySearch(timeSeries.data, t);

if (closeIdx > 0 && closeIdx < timeSeries.data.length) {
data.push({ series: this.seriesSet[d], index: closeIdx, value: timeSeries.data[closeIdx][1] });
}
}

if (data.length) {
el.innerHTML = this.options.tooltipFormatter.call(this, t, data);
el.style.display = 'block';
} else {
el.style.display = 'none';
}
};

SmoothieChart.prototype.mousemove = function (evt) {
this.mouseover = true;
this.mouseX = evt.offsetX;
this.mouseY = evt.offsetY;
this.mousePageX = evt.pageX;
this.mousePageY = evt.pageY;

var el = this.getTooltipEl();
el.style.top = Math.round(this.mousePageY) + 'px';
el.style.left = Math.round(this.mousePageX) + 'px';
this.updateTooltip();
};

SmoothieChart.prototype.mouseout = function () {
this.mouseover = false;
this.mouseX = this.mouseY = -1;
if (SmoothieChart.tooltipEl)
SmoothieChart.tooltipEl.style.display = 'none';
};

/**
* Make sure the canvas has the optimal resolution for the device's pixel ratio.
*/
Expand Down Expand Up @@ -488,6 +601,9 @@
return;
}

this.canvas.addEventListener('mousemove', this.mousemove);
this.canvas.addEventListener('mouseout', this.mouseout);

// Renders a frame, and queues the next frame for later rendering
var animate = function() {
this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(function() {
Expand All @@ -506,6 +622,8 @@
if (this.frame) {
SmoothieChart.AnimateCompatibility.cancelAnimationFrame(this.frame);
delete this.frame;
this.canvas.removeEventListener('mousemove', this.mousemove);
this.canvas.removeEventListener('mouseout', this.mouseout);
}
};

Expand Down Expand Up @@ -577,6 +695,7 @@
}

this.resize();
this.updateTooltip();

this.lastRenderTimeMillis = nowMillis;

Expand Down Expand Up @@ -767,6 +886,18 @@
context.restore();
}

if (chartOptions.tooltip && this.mouseX >= 0) {
// Draw vertical bar to show tooltip position
context.lineWidth = chartOptions.tooltipLine.lineWidth;
context.strokeStyle = chartOptions.tooltipLine.strokeStyle;
context.beginPath();
context.moveTo(this.mouseX, 0);
context.lineTo(this.mouseX, dimensions.height);
context.closePath();
context.stroke();
this.updateTooltip();
}

// Draw the axis values on the chart.
if (!chartOptions.labels.disabled && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max)) {
var maxValueString = chartOptions.yMaxFormatter(this.valueRange.max, chartOptions.labels.precision),
Expand Down Expand Up @@ -822,4 +953,3 @@
exports.SmoothieChart = SmoothieChart;

})(typeof exports === 'undefined' ? this : exports);

0 comments on commit 1815596

Please sign in to comment.