Highcharts - how can I center labels on a datetime x-axis? - javascript

I was having a hard time trying to figure out how to center labels on a datetime x-axis in Highcharts without using categories and tickPlacement (since tickPlacement only works on categories).
My axis was dynamically created so I could not simply set an x-offset or padding, as this would cause axes of different intervals to look strange.
After messing around with the config options I think I may have found a solution using the x-axis formatter and some css / jquery noodling in the Highcharts callback. See my answer below.

The trick is to use the x-axis labels object like this:
xAxis: {
type: 'datetime',
labels: {
useHTML: true,
align: 'center',
formatter: function () {
//using a specific class for the labels helps to ensure no other labels are moved
return '<span class="timeline_label">' + Highcharts.dateFormat(this.dateTimeLabelFormat, this.value) + '</span>';
}
}
You can see that the formatter will keep whatever dateTimeLabelFormat has been set by the user or default.
Then have a callback that does something like this:
function (chart) {
var $container = $(chart.container);
var $labels = $container.find('.highcharts-axis-labels .timeline_label');
var $thisLabel, $nextLabel, thisXPos, nextXPos, delta, newXPos;
$labels.each(function () {
$thisLabel = $(this).parent('span');
thisXPos = parseInt($thisLabel.css('left'));
$nextLabel = $thisLabel.next();
nextXPos = $nextLabel.length ? parseInt($nextLabel.css('left')) : chart.axes[0].left + chart.axes[0].width;
delta = (nextXPos - thisXPos) / 2.0;
newXPos = thisXPos + delta;
if ($nextLabel.length || $(this).width() + newXPos < nextXPos) {
$thisLabel.css('left', newXPos + 'px');
} else {
$thisLabel.remove();
}
});
});
In short, this will go through each label and determine how much it should be moved over (using css) by calculating the distance between itself and the next label. When it reaches the the last label, it either moves it over using the end of the axis for the calculation or removes it if it won't fit. This last part is just the decision I decided to make, you can probably choose to do something else like word wrap, etc.
You can see the jsfiddle here
Hope this helps some people. Also, if there are any improvements it would be great to see them here.

Based on the existing answer, there is a much simpler solution that also works when resizing the browser window (or otherwise forcing the chart to redraw), even when the tick count changes: http://jsfiddle.net/McNetic/eyyom2qg/3/
It works by attaching the same event handler to both the load and the redraw events:
$('#container').highcharts({
chart: {
events: {
load: fixLabels,
redraw: fixLabels
}
},
[...]
The handler itself looks like this:
var fixLabels = function() {
var labels = $('div.highcharts-xaxis-labels span', this.container).sort(function(a, b) {
return +parseInt($(a).css('left')) - +parseInt($(b).css('left'));
});
labels.css('margin-left',
(parseInt($(labels.get(1)).css('left')) - parseInt($(labels.get(0)).css('left'))) / 2
);
$(labels.get(this.xAxis[0].tickPositions.length - 1)).remove();
};
As you see, the extra wrapping of labels is unnecessary (at least if you do not have more than one xAxis). Basically, it works like this:
Get all existing labels (when redrawn, this includes newly added ones). 2. Sort by css property 'left' (they are not sorted this way after some redrawing)
Calculate offset between the first two labels (the offset is the same for all labels)
Set half of the offset as margin-left of all labels, effectively shifting them half the offset to the right.
Remove the rightmost label (moved outside of chart, by sometimes partly visible).

Related

Can you remove specific grid lines and point labels using highcharts without css?

Currently I'm using css to remove my y-axis grid lines and labels like in the below image.
CSS:
.highcharts-yaxis-grid path:nth-child(1),.highcharts-yaxis-grid path:nth-child(2),
.highcharts-yaxis-grid path:nth-child(4),.highcharts-yaxis-grid path:nth-child(5){ stroke:transparent !important; }
.highcharts-yaxis-labels text:nth-child(1),.highcharts-yaxis-labels text:nth-child(2),
.highcharts-yaxis-labels text:nth-child(4),.highcharts-yaxis-labels text:nth-child(5){ fill:transparent !important; }
Whenever I download the chart as an image they reappear which makes sense as it doesn't see the css file I'm using.
I've tried to search for an answer on highcharts or anything on the net and haven't had luck, so here I am presenting this question.
For example in the load event you can remove specific labels and gridLines:
chart: {
events: {
load: function() {
var chart = this,
yAxis = chart.yAxis[0],
gridLines = yAxis.gridGroup.element.children,
ticks = yAxis.ticks,
tickPositions = yAxis.tickPositions;
gridLines[2].remove();
ticks[tickPositions[2]].label.element.remove();
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/5m0s7th2/
API Reference: https://api.highcharts.com/highcharts/chart.events.load
Using the accepted answer I did this in a loop to avoid losing my middle gridline and tick val.
events: {
load: function() {
var chart = this,
yAxis = chart.yAxis[0],
gridLines = yAxis.gridGroup.element.children,
ticks = yAxis.ticks,
tickPositions = yAxis.tickPositions;
var i = 4;
while(i > -1){
if(i !== 2){
gridLines[i].remove(); // the whole array element is removed from gridLines, so we're doing this backwards
ticks[tickPositions[i]].label.element.remove();
}
i--;
}
}
}
It became a problem when I was starting from 0 using either the accepted answer or my loop. Hope this helps anyone who may have a ton of gridlines.

Location of the values ​in the Highcharts.js bar chart

I'm trying to make bar chart looks like in picture here:
Here is the result that I got
Maybe someone can suggest, how can I sticked value to xAxis?
Series I want to plot are :
series: [{
name: 'text',
data: [{"color":"#17a78b","y":3.36},
{"color":"#17a78b","y":2.1},{"color":"#17a78b","y":1.67},
{"color":"#17a78b","y":2.07},{"color":"#17a78b","y":-3.89},
{"color":"#17a78b","y":2.73},{"color":"#17a78b","y":2.34},
{"color":"#17a78b","y":2.91},{"color":"#56e8cb","y":4.94},
{"color":"#56e8cb","y":2.99},{"color":"#56e8cb","y":-2.5},
{"color":"#56e8cb","y":3.77}]
}]
Or maybe there is some way to style negative and positive value
You can make it using Highcharts.SVGElement.translate method. Each data label is SVG element which you can translate depend on your needs. This solution is more flexible because you can add more custom logic by analyzing point and label heights. Check demo and code I posted you below.
Code:
chart: {
type: 'column',
events: {
render: function() {
var chart = this,
series = chart.series[0],
offset = 3,
pointHeight,
textHeight,
translateY;
series.points.forEach(function(point) {
textHeight = point.dataLabel.getBBox().height;
pointHeight = point.shapeArgs.height;
if (pointHeight < textHeight) {
translateY = (pointHeight - textHeight / 2) + textHeight + offset;
} else {
translateY = (pointHeight - textHeight / 2) - offset;
}
translateY = (point.y < 0) ? -translateY : translateY;
point.dataLabel.translate(0, translateY);
});
}
}
}
Demo:
https://jsfiddle.net/30bxv9rc/
Api reference:
https://api.highcharts.com/class-reference/Highcharts.SVGElement#translate
https://api.highcharts.com/highcharts/chart.events.render
First set plotOptions.column.stacking to normal so that data labels are displayed inside the columns.
Next, you will need to update each point of data in the series through another function after the chart is rendered. Referring to my example, data points are looped; its value is accessed to check positive or negative; and subsequently verticalAlign and y attributes are updated so that label is displayed according to your requirement.

highstocks - legend position and refresh legend values on mousemove of multiple charts

Here is the scenario. I've multiple highstocks say 10 charts on a single page. Currently I've written 500 lines of code to position the legend, show tooltip and refresh the legend values on mousemove.
No. of legends vary per chart. On mousemove values of all the legends are updated. I need to optimize the code I am using highstocks v1.2.2.
Above screenshot shows 2 charts. Return, Basket, vs Basket Spread are legends and it's values are updated on every mousemove.
Please find this fiddle for example. In my case legends are positioned and updated values on mouse move with hundreds of lines of code. When I move the mouse the legend values of Return and Basket of first chart and the legend values of vs Basket Spread are updated. It's working fine but with lots of javascript code. So I need to optimize it less code or with highstocks built-in feature.
Update
User #wergeld has posted new fiddle. As I've shown in screenshot when cross-hair is being moved over any chart, the legend values of all the charts should be updated.
Is there anyway to implement the same functionality with less code or is there built-in feature available in highstocks ???
Using this as a reference.
Basic example would be to use the events.mouseover methods:
plotOptions: {
series: {
point: {
events: {
mouseOver: function () {
var theLegendList = $('#legend');
var theSeriesName = this.series.name;
var theYValue = this.y;
$('li', theLegendList).each(function (l) {
if (this.innerText.split(':')[0] == theSeriesName) {
this.innerText = theSeriesName + ': ' + theYValue;
}
});
}
}
}
}
}
This is assuming I have modded the <li> to be:
$('<li>')
.css('color', serie.color)
.text(serie.name + ': NA')
.click(function () {
toggleSeries(i);
})
.appendTo($legend);
You would then need to handle the mouseout event but I do not know what you want to do there.
Working example.
EDIT:
Here is a version using your reference OHLC chart to put the values in a different legend location when any point in the chart is hovered.
plotOptions: {
series: {
point: {
events: {
mouseOver: function () {
//using the ohlc and volumn data sets created at runtime.
var stockVal = ohlc[this.index][4]; // show close value
var stockVolume = volume[this.index][1];
var theChart = $('#container').highcharts();
var theLegendList = $('#legend');
$('li', theLegendList).each(function (l) {
var legendTitle = theChart.series[l].name;
if (l === 0) {
this.innerText = legendTitle + ': ' + stockVal;
}
if (l === 1) {
this.innerText = legendTitle + ': ' + stockVolume;
}
});
}
}
}
}
}

Maximum bar width in Highcharts column charts

I'm using Highcharts to create some vertical bars (a.k.a. "column charts") a lot like here: highcharts.com/demo/column-basic
Thing is, sometimes there are 30 bars in the chart, sometimes only two. Where then are two really wide bars in the chart, it looks really weird, so the decision was made to set a maximum width for the bars. That way if there are only two then they wouldn't get wider than, say, 50px, but if there were 50 then Highcharts could size them in the way it sees fit.
So my problem is that Highcharts doesn't have a way to explicitly set the maximum width of the columns. Has anyone found a solution to that?
Obviously this question was asked a long time ago when this feature didn't exist, but as of Highcharts 4.1.8 you can do this without any workarounds using the plotOptions.column.maxPointWidth setting:
$('#container').highcharts({
/* Other chart settings here... */
plotOptions: {
column: {
/* Here is the setting to limit the maximum column width. */
maxPointWidth: 50
}
}
/* Other chart settings here... */
});
Below is the JSFiddle of the basic column chart example, modified to show this working:
http://jsfiddle.net/vu3dubhb/
And the documentation for this setting is here: http://api.highcharts.com/highcharts#plotOptions.column.maxPointWidth
Update: As of Highcharts version 4.1.8 , see Adam Goodwin's answer, below.
For many years, the only way to set the maximum width of the columns, was dynamically via JavaScript.
Basically:
Check the current bar width.
If it is greater than desired, reset the series' pointWidth.
Force a redraw to make the change visible.
Something like:
var chart = new Highcharts.Chart ( { ... ...
//--- Enforce a Maximum bar width.
var MaximumBarWidth = 40; //-- Pixels.
var series = chart.series[0];
if (series.data.length) {
if (series.data[0].barW > MaximumBarWidth) {
series.options.pointWidth = MaximumBarWidth;
/*--- Force a redraw. Note that redraw() will not
fire in this case!
Hence we use something like setSize().
*/
chart.setSize (400, 300);
}
}
See the demo at jsFiddle.
Ultimate solution is to wrap drawPoints and overwrite shaperArgs of each point, especially x-position and width:
(function(H) {
var each = H.each;
H.wrap(H.seriesTypes.column.prototype, 'drawPoints', function(proceed) {
var series = this;
if(series.data.length > 0 ){
var width = series.barW > series.options.maxPointWidth ? series.options.maxPointWidth : series.barW;
each(this.data, function(point) {
point.shapeArgs.x += (point.shapeArgs.width - width) / 2;
point.shapeArgs.width = width;
});
}
proceed.call(this);
})
})(Highcharts)
Use:
$('#container').highcharts({
chart: {
type: 'column'
},
series: [{
maxPointWidth: 50,
data: [ ... ]
}]
});
And live demo: http://jsfiddle.net/83M3T/1/
a better way would be has suggested
http://highcharts.uservoice.com/forums/55896-general/suggestions/2079099-manually-set-the-column-width
check this fiddle
http://jsfiddle.net/gh/get/jquery/1.7.2/highslide-software/highcharts.com/tree/master/samples/highcharts/plotoptions/column-pointwidth-20/
just add
series: {
pointWidth: 20
}
in you plot options and thats it . :) enjoy cheers ...
function(chart){
var series = chart.series[0];
//--- Enforce a Maximum bar width
if (series && series.data.length) {
if (series.data[0].pointWidth > MaximumBarWidth) {
for(var i=0;i< chart.series.length;i++)
chart.series[i].update({
pointWidth:MaximumBarWidth
});
}
}
}
http://jsfiddle.net/pXZRV/40/
I used the spacingTop attribute to enforce a maximum pointWidth on a bar chart:
if (series[0].data[0].pointWidth > maximumBarWidth) {
this.options.chart.spacingTop = this.options.chart.height - (maximumBarWidth * series.length) - 30;
this.isDirtyBox = true;
this.redraw();
}
So if there is one Bar over a certain Width the spacing will be adjusted and the chart is drawn smaller.
This does not change the size of the container but keeps the bars beneath a certain size and your pointPadding and groupPadding will not be affected.
You could do the same with spacingRight and would have a consistent Chart without ugly spaces between the bars.
In my case, setting pointRange to 1 was what I needed. If a category with a box was adjacent to a category without a box (only outliers in a scatter series) the box would be wider than the category.
There is a good explanation here.
I faced the same issue using HighCharts and this is how I fixed it !!
-- Create a wrapper div for the chart, with some minimum width and overflow:auto. (To enable horizontal scroll)
-- Set the "pointWidth" of each bar to the required value. (say, pointWidth: 75)
-- Now set the chartWidth dynamically, based on the number of bars.
Use chartWidth = (number of bars) * (pointWidth) * 2
So, if you have 15 bars, chartWidth = 15*75*2 = 2250px, where 2 is used to create larger chart width, to accommodate spacing between bars.
--In this manner, You can have any number of bars of same width in the scrollable chart ... :)

jqplot tooltip on bar chart

I'm using the jquery plugin jqplot for plotting some bar charts.
on hover, I'd like to display the tick for the bar and its value on a tooltip. I've tried
highlighter: { show: true,
showTooltip: true, // show a tooltip with data point values.
tooltipLocation: 'nw', // location of tooltip: n, ne, e, se, s, sw, w, nw.
tooltipAxes: 'both', // which axis values to display in the tooltip, x, y or both.
lineWidthAdjust: 2.5 // pixels to add to the size line stroking the data point marker
}
but it doesn't work. the bar visually gets lighter, and there's a small dot on the top (which would ideally go away--probably from line chart renderer stuff), but there is no tooltip anywhere. Anyone know how I can do this? I'll have lots of bars so the x-axis will be cluttered and kind of a mess if I show them down there only.
I go through jqplot.highlighter.js and find an undocumented property: tooltipContentEditor.
I use it to customize the tooltip to display x-axis label.
Use something like this:
highlighter:{
show:true,
tooltipContentEditor:tooltipContentEditor
},
function tooltipContentEditor(str, seriesIndex, pointIndex, plot) {
// display series_label, x-axis_tick, y-axis value
return plot.series[seriesIndex]["label"] + ", " + plot.data[seriesIndex][pointIndex];
}
nevermind, I did a roundabout way to create my own tooltip via jquery.
I left my highlighter settings as they were in my question (though you probably don't need the tooltip stuff).
In my js file after the bar chart is set up (after $.jqplot('chart', ...) I set up an on mouse hover binding, as some of the examples showed. I modified it like this:
$('#mychartdiv').bind('jqplotDataHighlight',
function (ev, seriesIndex, pointIndex, data ) {
var mouseX = ev.pageX; //these are going to be how jquery knows where to put the div that will be our tooltip
var mouseY = ev.pageY;
$('#chartpseudotooltip').html(ticks_array[pointIndex] + ', ' + data[1]);
var cssObj = {
'position' : 'absolute',
'font-weight' : 'bold',
'left' : mouseX + 'px', //usually needs more offset here
'top' : mouseY + 'px'
};
$('#chartpseudotooltip').css(cssObj);
}
);
$('#chartv').bind('jqplotDataUnhighlight',
function (ev) {
$('#chartpseudotooltip').html('');
}
);
explanation:
ticks_array is previously defined, containing the x axis tick strings. jqplot's data has the current data under your mouse as an [x-category-#, y-value] type array. pointIndex has the current highlighted bar #. Basically we will use this to get the tick string.
Then I styled the tooltip so that it appears close to where the mouse cursor is. You will probably need to subtract from mouseX and mouseY a bit if this div is in other positioned containers.
you can then style #chartpseudotooltip in your css. If you want the default styles you can just add it to .jqplot-highlighter-tooltip in the the jqplot.css.
hope this is helpful to others!
I am using the version of the highlighter plugin on the following link:
https://github.com/tryolabs/jqplot-highlighter
The parameters I am using:
highlighter: {
show:true,
tooltipLocation: 'n',
tooltipAxes: 'pieref', // exclusive to this version
tooltipAxisX: 20, // exclusive to this version
tooltipAxisY: 20, // exclusive to this version
useAxesFormatters: false,
formatString:'%s, %P',
}
The new parameters ensure a fixed location where the tooltip will appear. I prefer to place it on the upper left corner to avoid problems with resizing the container div.

Categories

Resources