I want to show the tooltip on the right side of the cursor.
I looked in the documentation/examples but I can't find a way to force the tooltips to stay on the right side of the cursor.
Can anyone tell me how to do it?
With tooltip positioner I only can set a default position.
Tooltip positioner is much more than just default position. The function arguments contain info about your point position & tooltip dimensions, using which it should be fairly simple to position it to the right.
Highchart/stock allows you to define your alternate positioner as follows
tooltip:{
positioner:function(boxWidth, boxHeight, point){
...
}
}
Note that you have three arguments (boxWidth, boxHeight, point) at your disposal, these seem to be sufficient for most of the use cases to calculate a desired tooltip position. boxWidth and boxHeight are the width and height that your tooltip will require, hence you can use them for edge cases to adjust your tooltip and prevent it from spilling out of the chart or even worse getting clipped.
The default tooltip positioner that comes with highstock is as follows (Source)
/**
* Place the tooltip in a chart without spilling over
* and not covering the point it self.
*/
getPosition: function (boxWidth, boxHeight, point) {
// Set up the variables
var chart = this.chart,
plotLeft = chart.plotLeft,
plotTop = chart.plotTop,
plotWidth = chart.plotWidth,
plotHeight = chart.plotHeight,
distance = pick(this.options.distance, 12), // You can use a number directly here, as you may not be able to use pick, as its an internal highchart function
pointX = point.plotX,
pointY = point.plotY,
x = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance),
y = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip
alignedRight;
// It is too far to the left, adjust it
if (x < 7) {
x = plotLeft + pointX + distance;
}
// Test to see if the tooltip is too far to the right,
// if it is, move it back to be inside and then up to not cover the point.
if ((x + boxWidth) > (plotLeft + plotWidth)) {
x -= (x + boxWidth) - (plotLeft + plotWidth);
y = pointY - boxHeight + plotTop - distance;
alignedRight = true;
}
// If it is now above the plot area, align it to the top of the plot area
if (y < plotTop + 5) {
y = plotTop + 5;
// If the tooltip is still covering the point, move it below instead
if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
y = pointY + plotTop + distance; // below
}
}
// Now if the tooltip is below the chart, move it up. It's better to cover the
// point than to disappear outside the chart. #834.
if (y + boxHeight > plotTop + plotHeight) {
y = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below
}
return {x: x, y: y};
}
With all the above information, I think you have sufficient tools to implement your requirement by simply modifying the function to make float to right instead of the default left.
I will go ahead and give you the simplest implementation of positioning tooltip to right, you should be able to implement the edge cases based on the aftermentioned default tooltip positioner's code
tooltip: {
positioner: function(boxWidth, boxHeight, point) {
return {x:point.plotX + 20,y:point.plotY};
}
}
Read more # Customizing Highcharts - Tooltip positioning
The better solution to get your tooltip always on the right side of the cursor is the following:
function (labelWidth, labelHeight, point) {
return {
x: point.plotX + labelWidth / 2 + 20,
y: point.plotY + labelHeight / 2
};
}
Related
Still in the process of improving my competence about D3, I got stuck with a problem where I'm trying to plot a zoomable curve in a SVG element with margins (so that I need a clipPath rect to avoid that plot invades margins when zoomed) but the clipPath margins cut the display of d3.symbols off the plot.
This is the relevant code for the plot
var margin = {top: 20, right: 60, bottom: 30, left: 30},
w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", w)
.attr("height", h);
// The curve I want to plot: y=x^2
var my_curve = d3.range(10).map(function(d) { var my_y = d * d; return { "x" : d, "y" : my_y }; });
var x_range_min = d3.min(my_curve, function(d) { return d.x; });
var x_range_max = d3.max(my_curve, function(d) { return d.x; });
var y_range_min = d3.min(my_curve, function(d) { return d.y; });
var y_range_max = d3.max(my_curve, function(d) { return d.y; });
var xScale = d3.scaleLinear().domain([x_range_min, x_range_max]).range([0, w]);
var yScale = d3.scaleLinear().domain([y_range_max, y_range_min]).range([0, h]);
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
// symbols
svg.selectAll(".my_pts").data(my_curve).enter().append("path")
.attr("class", "my_pts")
.attr("d", d3.symbol().type(d3.symbolTriangle).size(200))
.attr("transform", function(d) { return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")"; })
// with this zoomed line does not enter margin area
.attr("clip-path", "url(#clip)");
...as you can see only part of the triangle symbol is depicted, I guess because the path is drawn at 0,0 and cut by the clipPath before the translation can be performed.
I have also posted this fiddle https://jsfiddle.net/fabio_p/988c1sjv/
where you can find a more complete version of the code, with the brush & zoom function, so that you can see why the clipPath is needed (if you have never encountered the issue with margins before)
My question is the following: is there a workaround to this problem? I was hoping to find a way to directly draw the symbol in the right place without the need of a later translation (possibly with a "custom symbol type"), but it seems it goes beyond my current skills, since I was unable to produce any actual solution.
Any suggestion would be welcome. Thanks in advance.
Create a group with the clip path:
var clipping = svg.append("g")
.attr("clip-path", "url(#clip)");
And append both the line and the symbols to the group:
clipping.append("path")
.data([my_curve])
//etc...
Here is your updated fiddle: https://jsfiddle.net/cvtvrL2q/
Let's say I am doing temperature graph for 2 cities. I want to have minimum and maximum temperature for each month and average month temperature in one bar.
I have found (and changed a bit) an example which works perfectly for one city (jsfiddle here). That's the result I want.
Stackoverflow wants me to put some code here because of jsfiddle,
but I really don't know if it is good idea to paste here allt that
long JS code
However I need it for two (or more) cities for each month. For now, I have this (jsfiddle here), but the average points are not placed inside of bars, instead the appear somewhere in the middle.
Is there a way how to have multiple cities with average points placed inside of each bar ? I don't know what I am missing.
Thanks in advance.
Not a perfect solution, but should be good starting point:
(function(H) {
H.wrap(H.seriesTypes.columnrange.prototype, 'drawPoints', function(p) {
var s = this,
chart = s.chart,
xAxis = s.xAxis,
yAxis = s.yAxis,
path, x, y,
r = chart.renderer,
radius = s.barW / 2;
p.call(this);
H.each(this.points, function(p) {
if(p.options.avg) {
y = yAxis.toPixels(p.options.avg * (-1), true);
x = p.plotX + s.columnMetrics.offset + radius;
path = [
'M', x - radius, y - radius,
'L', x + radius, y - radius,
'L', x + radius, y + radius,
'L', x - radius, y + radius,
'Z'
];
if(p.avgShape) {
p.avgShape.attr({
d: path
});
} else {
p.avgShape = r.path(path).attr({
fill: s.options.avgColor
}).add(s.group);
}
}
});
});
})(Highcharts)
Demo: http://jsfiddle.net/r6fha9he/3/
One hacky thing to mention - values are multiplied by *(-1), because toPixels() won't recognize inverted chart, and I would like to avoid using axis.translate(). The same is not necessary for non-inverted chart. In case you are more interested in translate, search source code for:
/**
* Translate from axis value to pixel position on the chart, or back
*
*/
translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) {
I'm working with Highcharts spider web chart at the moment and wanted to see if I can do the following
How do I zoom polar charts? (or is it possible?)
How do I put background-color in each of the segment?
How do I zoom polar charts? (or is it possible?)
If you want a zoom like zoomType then no. zoomType is disabled for polar charts by highcharts-more.js. From source:
// Disable certain features on angular and polar axes
chart.inverted = false;
chartOptions.chart.zoomType = null;
How do I put background-color in each of the segment?
You can use math and Chart.renderer to create and fill paths to color the background of the segments. For example you might do it like this:
var colors = [ "pink", "yellow", "blue", "red", "green", "cyan", "teal", "indigo" ];
var parts = 6;
for(var i = 0; i < parts; i++) {
centerX = chart.plotLeft + chart.yAxis[0].center[0];
centerY = chart.plotTop + chart.yAxis[0].center[1];
axisLength = chart.yAxis[0].height;
angleOffset = -Math.PI/2;
angleSegment = Math.PI/(parts/2);
firstPointX = centerX + (axisLength * Math.cos(angleOffset + (angleSegment * i)));
firstPointY = centerY + (axisLength * Math.sin(angleOffset + (angleSegment * i)));
secondPointX = centerX + (axisLength * Math.cos(angleOffset + (angleSegment * (i+1))));
secondPointY = centerY + (axisLength * Math.sin(angleOffset + (angleSegment * (i+1))));
chart.renderer.path([
'M', centerX, centerY,
'L', firstPointX, firstPointY,
'L', secondPointX, secondPointY,
'Z'
]).attr({
fill: colors[i % colors.length],
'stroke-width': 1,
'opacity': 1
}).add();
}
As seen in this JSFiddle demonstration. You just have to match number of categories with the parts variable.
I am using Kineticjs. I am trying to have bound (dragBoundFunc) on the drag like the one below (http://jsfiddle.net/m1erickson/n5xMs):
dragBoundFunc: function (pos) {
var X = pos.x;
var Y = pos.y;
if (X < minX) {
X = minX;
}
if (X > maxX) {
X = maxX;
}
if (Y < minY) {
Y = minY;
}
if (Y > maxY) {
Y = maxY;
}
return ({
x: X,
y: Y
});
}
});
and I am trying use this approach for a group which is drag-able and re-sizable like this http://jsbin.com/iyimuy/125/edit but I am not able to make it work.I would love to hear some comments.
You seem to be overthinking the problem.
For example, here’s how to enforce the right boundary (in pseudo-code).
You have moved darth beyond the right side of yoda if:
darthRight > yodaRight.
Calculate darthRight:
In dragBoundFunc, the pos variable gives you the current top-left x/y of darth.
darthRight = posX + darthWidth
Calculate yodaRight:
yodaRight = yodaX + yodaWidth
To correct for darths move outside yoda:
Pull darths left side back inside yoda until its a full darthWidth inside yodaRight.
posX = yodaRight - darthWidth
Done!
Now repeat for the other 3 boundaries...
I have a url link in the tooltip, however, if the tooltip goes outside of the chart area and you try to click on the url, the tooltip disappears.
Any work-arounds for this?
Thanks!
I've found in cases like these, it's best to position the tooltip manually:
http://api.highcharts.com/highcharts#tooltip.positioner
That way, unwanted hiding is controllable.
EDIT:
If you extend the tooltip prototype, you can manipulate the X and Y
Tooltip.prototype.move = function (x, y, anchorX, anchorY) {
var tooltip = this,
now = tooltip.now,
animate = tooltip.options.animation !== false && !tooltip.isHidden;
if(x > ?????)
{
x = x - 50; // or how ever many pixels you want to move it to
}
// get intermediate values for animation
extend(now, {
x: animate ? (2 * now.x + x) / 3 : x,
y: animate ? (now.y + y) / 2 : y,
anchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
anchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY
});
// move to the intermediate value
tooltip.label.attr(now);
// run on next tick of the mouse tracker
if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) {
// never allow two timeouts
clearTimeout(this.tooltipTimeout);
// set the fixed interval ticking for the smooth tooltip
this.tooltipTimeout = setTimeout(function () {
// The interval function may still be running during destroy, so check that the chart is really there before calling.
if (tooltip) {
tooltip.move(x, y, anchorX, anchorY);
}
}, 32);
}
}