Style specific bar column with Flot - javascript

I'm working with Flot to create a bar chart. However, I need to add special styling to certain columns. Is this possible at all?
My HTML looks like this:
<div id="monthly-usage" style="width: 100%; height: 400px;"></div>
And my JS like this:
somePlot = null;
$(function() {
//Data from this year and last year
var thisYear = [
[3, 231.01],
[4, 219.65],
[5, 222.47],
[6, 223.09],
[7, 248.43],
[8, 246.22]
];
var lastYear = [
[3, 171.7],
[4, 130.62],
[5, 163.03],
[6, 166.46],
[7, 176.16],
[8, 169.04]
];
var usageData = [{
//Usage this year
label: "2014",
data: thisYear,
bars: {
show: true,
barWidth: .3,
fill: true,
lineWidth: 0,
order: 1,
fillColor: 'rgba(194, 46, 52, .85)'
},
color: '#c22e34'
}, {
//Usage last year to compare with current usage
label: "2013",
data: lastYear,
bars: {
show: true,
barWidth: .3,
fill: true,
lineWidth: 0,
order: 2,
fillColor: 'rgba(73, 80, 94, .85)'
},
color: '#49505e'
}];
//X-axis labels
var months = [
[0, "Jan"],
[1, "Feb"],
[2, "Mar"],
[3, "Apr"],
[4, "Maj"],
[5, "Jun"],
[6, "Jul"],
[7, "Aug"],
[8, "Sep"],
[9, "Okt"],
[10, "Nov"],
[11, "Dec"]
];
//Draw the graph
somePlot = $.plot(('#monthly-usage'), usageData, {
grid: {
color: '#646464',
borderColor: 'transparent',
hoverable: true
},
xaxis: {
ticks: months,
color: '#d4d4d4'
},
yaxis: {
tickSize: 50,
tickFormatter: function(y, axis) {
return y + " kWh";
}
},
legend: {
show: false
}
});
var ctx = somePlot.getCanvas().getContext("2d"); // get the context from plot
var data = somePlot.getData()[0].data; // get your series data
var xaxis = somePlot.getXAxes()[0]; // xAxis
var yaxis = somePlot.getYAxes()[0]; // yAxis
var offset = somePlot.getPlotOffset(); // plots offset
var imageObj = new Image(); // create image
imageObj.onload = function() { // when finish loading image add to canvas
xPos = xaxis.p2c(data[4][0]) + offset.left;
yPos = yaxis.p2c(data[4][1]) + offset.top;
ctx.drawImage(this, xPos, yPos);
xPos = xaxis.p2c(data[5][0]) + offset.left;
yPos = yaxis.p2c(data[5][1]) + offset.top;
ctx.drawImage(this, xPos, yPos);
};
imageObj.src = 'path/to/file.png'; // set it's source to kick off load
});
});
Optimally, I would like to insert an icon in bar 5 and 6 that warns the user. Alternatively, I'd like to change the color of bars 5 and 6. Any ideas on how to fix this?
EDIT: I've updated my JS according to Mark's answer which works.
#Mark, how can I position the images correctly. They are a bit off. I need the image inside the red bar and not besides the bar. I'm trying to finetune this but it doesn't seem as if I can use for instance "0.5". I use side by side bars which is different from your version.
xPos = xaxis.p2c(data[4][0]) + offset.left;
yPos = yaxis.p2c(data[4][1]) + offset.top;

You can't do exactly what you ask with standard options, but there are a couple of possible approaches:
Write your own draw method and use the hooks to install it in place of the standard flot drawing code. This obviously entails a lot of work, but you'll have complete control over how to render your data. (That said, I wouldn't recommend it.)
Break your data into two different data sets. One data set would have dummy values (e.g. 0, or whatever your minimum is) for bars 5 and 6. The second data set would have dummy values for all bars except 5 and 6. You could then style the "two" data sets independently, giving each, for example a different color. Graph the two sets as a stacked bar chart with whatever additional styling tweaks are appropriate for your chart.
(As a FYI, there's a fair bit of information and examples at jsDataV.is. Look at the "Book" section; chapter 2 is dedicated to flot.)

flot gives you access to the HTML5 Canvas it's drawing on; so you just add your icon on there yourself. Borrowing from my own answer here.
var ctx = somePlot.getCanvas().getContext("2d"); // get the context from plot
var data = somePlot.getData()[0].data; // get your series data
var xaxis = somePlot.getXAxes()[0]; // xAxis
var yaxis = somePlot.getYAxes()[0]; // yAxis
var offset = somePlot.getPlotOffset(); // plots offset
$.get("someImage.txt", function(img) { // grad some image, I'm loading it from a base64 resource
var imageObj = new Image(); // create image
imageObj.onload = function() { // when finish loading image add to canvas
var xPos = xaxis.p2c(data[4][0]) + offset.left;
var yPos = yaxis.p2c(data[4][2]) + offset.top;
ctx.drawImage(this, xPos, yPos);
xPos = xaxis.p2c(data[5][0]) + offset.left;
yPos = yaxis.p2c(data[5][3]) + offset.top;
ctx.drawImage(this, xPos, yPos);
};
imageObj.src = img; // set it's source to kick off load
});
Example here.
Looks like:

Related

Chart Js flickering or switching as i move mouse on canvas

I created a chart on my page using ChartJs and then i placed three buttons so that when clicked, it represents the data in a different chart type.
but the issue i am having is that if i move the mouse pointer across the canvas after switching to a different graph, it keeps switching or flickering and this is not good, how can i get it to stay on the current chart type.
i tried clearing the canvas and context object before rendering the chart but yet, it still refuses to work, here's my code..
var chartx;
var cv;
var mnts, vals, mx, steps; // for the chart labels..
//retrieve the json data from this url..
var setting = {
type:'GET',
url:'/ajax/stats/summary'
};
$(function(){
'use strict';
cv = document.getElementById('dboard'); //the canvas object
pullChartData('line'); //draw the line graph first
});
function pullChartData(chtype){
shwChldx(); //show the GIF loader
clearCanvas(); //clear the canvas
$.ajax(setting)
.done(function(response){
o = JSON.parse(response);
mnts = String(o.days).split(';'); //array of horizontal labels
vals = String(o.values).split(';'); //array of values
mx = Number(o.mx); //maximum value among chart data
steps = Number(o.step); //get the step to use on chart
setChartType(chtype);
hideChldx()
}).fail(function(xhr,status,error){
hideChldx();
notifyCVS('error loading CHART data...');
});
}
function shwChldx(){ //show loader GIF
$('#tkldr').css('display', 'block');
}
function hideChldx(){ //hide loader GIF
$('#tkldr').css('display', 'none');
}
function drawChart(cdiv, ctype){ //draw graph function
chartx = new Chart(cdiv, {
type: ctype,
data: {
labels: mnts,
datasets: [{
data: vals,
label: ' - visits at on this day',
borderColor: '#324463',
fillColor: 'rgba(109,177,117,0.71)',
borderWidth: 1,
fill: true
}]
},
options: {
backgroundColor: '#FFF',
titleFontSize: 16,
titleFontColor: '#0066ff',
titleMarginBottom: 10,
bodyFontColor: '#000',
bodyFontSize: 12,
displayColors: false,
xPadding:10,
yPadding:10,
},
legend: {
display: false,
labels: {
display: false
}
},
scales: {
yAxes: [{
ticks: {
beginAtZero:true,
fontSize: 12,
max: mx,
stepSize: Number(steps),
}
}],
xAxes: [{
ticks: {
beginAtZero:true,
fontSize: 11
}
}]
}
}
});
}
//clear the canvas object before anything..
function clearCanvas(){
ctx = cv.getContext('2d');
ctx.clearRect(0, 0, cv.width, cv.height);
notifyCVS('retrieving data, please wait...');
}
//this is the function referenced on the buttons..
//example - <button onclick="setChartType('bar')"></button>
// and i have 3 buttons for 'line', 'bar', 'horizontalBar' types
function setChartType(type){
clearCanvas();
ctx = cv.getContext('2d');
ctx.clearRect(0, 0, cv.width, cv.height);
drawChart(cv, type);
}
//i call this function to write on the canvas when there is
//no activity so that its not just a blank white object..
function notifyCVS(txt){
ctx = cv.getContext('2d');
ctx.clearRect(0, 0, cv.width, cv.height);
ctx.fillStyle = "#777777";
ctx.font = "Regular 12px Arial";
ctx.fillText(String(txt), (cv.width / 2) - 17, (cv.height / 2) + 8);
}
so this is my approach, the chart and everything works, but after clicking the different buttons the behaviour changes on mousemove and i dont know where the problem is from, thats just the problem, the flickering behaviour.
please what should i do.
There is no need to literally clear the canvas. But you need to destroy the chartjs object before you reinitialize it. So, it's like re-creating a chartjs object on the same canvas.
All you need to do is check if the instance already exists before creating the object in drawChart method.
function drawChart(cdiv, ctype){
if(chartx){
chartx.destroy();
}
chartx = new Chart(.....
......
}

Trying to use HighCharts to build solidgauge chart with multiple layer

I'm trying to build some chart like this one:
Chart Visual
But the main struggle is to add two different series that complement each other.
I really appreciate any help that someone could give me.
Many thanks in advance.
You can achieve it, but the process of implementation is not so easy. I prepared the example which shows how to do that, and I will try to explain what I did, step by step.
First, you need to define your data array just like that:
var data = [40, 30, 10, 20]
Then define your chart configuration, and inside of chart.events.load function handler put whole logic of creating desired effect.
Next step is iterate on all data positions, and create its own specific point, series, yAxis and pane, basing on calculations like below:
load() {
var chart = this,
series = [],
panes = [],
yAxes = [],
radius = 112,
innerRadius = 88,
pointAngle,
prevPointAngle = 0,
pointPadding = (radius - innerRadius) / 4,
colors = Highcharts.getOptions().colors,
additionalPointPadding = 2;
data.forEach(function(p, i) {
pointAngle = (p * 360) / 100 // Calculate point angle
// Prepare pane for each point
panes.push({
startAngle: prevPointAngle + pointPadding + additionalPointPadding,
endAngle: (pointAngle + prevPointAngle) - pointPadding - additionalPointPadding,
background: [{
backgroundColor: '#fff',
borderWidth: 0
}]
})
// Prepare yAxis for specific pane
yAxes.push({
min: 0,
max: 100,
lineWidth: 0,
tickPositions: [],
pane: i
})
// Prepare series with specific point
series.push({
name: 'Exercise ' + i,
data: [{
color: colors[i],
radius: radius + '%',
innerRadius: innerRadius + '%',
y: 100,
percents: p
}],
yAxis: i
})
prevPointAngle += pointAngle
})
And finally, update our chart by new objects:
chart.update({
pane: panes,
yAxis: yAxes,
series: series
},true, true)
Last thing you have to know, that your chart configuration should have the same amount of empty objects in pane array, like the data positions, e.g:
var data = [10, 80, 10]
(...)
pane: [{},{},{}]
Here is the example which shows the final effect: https://jsfiddle.net/yamu5z9r/
Kind regards!

Chart.js — drawing an arbitrary vertical line

How can I draw an vertical line at a particular point on the x-axis using Chart.js?
In particular, I want to draw a line to indicate the current day on a LineChart. Here's a mockup of the chart:
http://i.stack.imgur.com/VQDWR.png
Update - this answer is for Chart.js 1.x, if you are looking for a 2.x answer check the comments and other answers.
You extend the line chart and include logic for drawing the line in the draw function.
Preview
HTML
<div>
<canvas id="LineWithLine" width="600" height="400"></canvas>
</div>
Script
var data = {
labels: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"],
datasets: [{
data: [12, 3, 2, 1, 8, 8, 2, 2, 3, 5, 7, 1]
}]
};
var ctx = document.getElementById("LineWithLine").getContext("2d");
Chart.types.Line.extend({
name: "LineWithLine",
draw: function () {
Chart.types.Line.prototype.draw.apply(this, arguments);
var point = this.datasets[0].points[this.options.lineAtIndex]
var scale = this.scale
// draw line
this.chart.ctx.beginPath();
this.chart.ctx.moveTo(point.x, scale.startPoint + 24);
this.chart.ctx.strokeStyle = '#ff0000';
this.chart.ctx.lineTo(point.x, scale.endPoint);
this.chart.ctx.stroke();
// write TODAY
this.chart.ctx.textAlign = 'center';
this.chart.ctx.fillText("TODAY", point.x, scale.startPoint + 12);
}
});
new Chart(ctx).LineWithLine(data, {
datasetFill : false,
lineAtIndex: 2
});
The option property lineAtIndex controls which point to draw the line at.
Fiddle - http://jsfiddle.net/dbyze2ga/14/
Sharing my solution for chartjs.org version 2.5. I wanted to use a plugin, to make the implementation reusable.
const verticalLinePlugin = {
getLinePosition: function (chart, pointIndex) {
const meta = chart.getDatasetMeta(0); // first dataset is used to discover X coordinate of a point
const data = meta.data;
return data[pointIndex]._model.x;
},
renderVerticalLine: function (chartInstance, pointIndex) {
const lineLeftOffset = this.getLinePosition(chartInstance, pointIndex);
const scale = chartInstance.scales['y-axis-0'];
const context = chartInstance.chart.ctx;
// render vertical line
context.beginPath();
context.strokeStyle = '#ff0000';
context.moveTo(lineLeftOffset, scale.top);
context.lineTo(lineLeftOffset, scale.bottom);
context.stroke();
// write label
context.fillStyle = "#ff0000";
context.textAlign = 'center';
context.fillText('MY TEXT', lineLeftOffset, (scale.bottom - scale.top) / 2 + scale.top);
},
afterDatasetsDraw: function (chart, easing) {
if (chart.config.lineAtIndex) {
chart.config.lineAtIndex.forEach(pointIndex => this.renderVerticalLine(chart, pointIndex));
}
}
};
Chart.plugins.register(verticalLinePlugin);
Usage is simple then:
new Chart(ctx, {
type: 'line',
data: data,
label: 'Progress',
options: options,
lineAtIndex: [2,4,8],
})
The code above inserts red vertical lines at positions 2,4 and 8, running through points of first dataset at those positions.
I'd highly recommend to use the Chartjs-Plugin-Annotation.
An example can be found at CodePen
var chartData = {
labels: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"],
datasets: [
{
data: [12, 3, 2, 1, 8, 8, 2, 2, 3, 5, 7, 1]
}
]
};
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
new Chart(ctx, {
type: "line",
data: chartData,
options: {
annotation: {
annotations: [
{
type: "line",
mode: "vertical",
scaleID: "x-axis-0",
value: "MAR",
borderColor: "red",
label: {
content: "TODAY",
enabled: true,
position: "top"
}
}
]
}
}
});
};
Have a look here for more Details: https://stackoverflow.com/a/36431041
I had to go through the trouble of figuring out how to do something similar with ChartJS 2.0 so I thought I would share.
This is based on the new way of overriding a chart prototype as explained here: https://github.com/chartjs/Chart.js/issues/2321
var ctx = document.getElementById('income-chart');
var originalDraw = Chart.controllers.line.prototype.draw;
Chart.controllers.line.prototype.draw = function (ease) {
originalDraw.call(this, ease);
var point = dataValues[vm.incomeCentile];
var scale = this.chart.scales['x-axis-0'];
// calculate the portion of the axis and multiply by total axis width
var left = (point.x / scale.end * (scale.right - scale.left));
// draw line
this.chart.chart.ctx.beginPath();
this.chart.chart.ctx.strokeStyle = '#ff0000';
this.chart.chart.ctx.moveTo(scale.left + left, 0);
this.chart.chart.ctx.lineTo(scale.left + left, 1000000);
this.chart.chart.ctx.stroke();
// write label
this.chart.chart.ctx.textAlign = 'center';
this.chart.chart.ctx.fillText('YOU', scale.left + left, 200);
};
With chart.js 3.8.0 I've used a combo between line/bar chart with timeline (xAxis) and percentage (yAxis). See docs
The dataset configuration provides a option to set the maxBarThickness (I've applied 2) and then apply the max value of the y-axis on each data entry of the bar chart.
Example of dataset configuration:
datasets: [
{
type: 'line'
data: [
{x: "2022-07-18", y: 10},
{x: "2022-07-19", y: 60},
{x: "2022-07-20", y: 30}
],
....
},
{
type: 'bar',
data: [
{x: "2022-07-19", y: 100}
],
maxBarThickness: 2,
...
}
]
Example of the output:
Here's a pen that achieves a similar effect without the chartjs-plugin-annotation, or hacking how Chart.js renders, or any other plugins: https://codepen.io/gkemmey/pen/qBWZbYM
Approach
Use a combo bar / line chart, and use the bar chart to draw the vertical lines.
Use two y-axes: one for the bar chart (which we don't display), and one for all your other line chart datasets.
Force the bar chart y-axes to min: 0 and max: 1. Anytime you want to draw a vertical line, add a data object like { x: where_the_line_goes, y: 1 } to your bar chart dataset.
The pen also adds some custom data to the bar chart dataset and a legend filter and label callback to exclude the bar chart dataset from the legend, and control the label on the vertical line.
Pros
No other dependencies. No custom monkey patching / extending.
The annotations plugin doesn't seem to be actively maintained. For instance, atm, their event handlers throw an error about "preventing default on passive events"
Maybe a pro: The annotations plugin always shows the labels of lines drawn, and you have to use their event callbacks to get a show-on-hover effect. Chart.js tooltips show on hover by default.
Cons
We're adding custom data in the dataset config, and hoping it doesn't conflict with anything Chart.js is doing. It's data Chart.js doesn't expect to be there, but as of 2.8, also doesn't break it.
Enhanced version of #Tomáš Dvořák answer
Supports:
custom text for labels
custom color for line+label
custom alignment for labels
custom X/Y offset for labels
const verticalLinePlugin = {
getLinePosition: function (chart, pointIndex) {
const meta = chart.getDatasetMeta(0); // first dataset is used to discover X coordinate of a point
const data = meta.data;
return data[pointIndex]._model.x;
},
renderVerticalLine: function (chartInstance, pointIndex, label, color, alignment, xOffset, yOffset) {
const lineLeftOffset = this.getLinePosition(chartInstance, pointIndex);
const scale = chartInstance.scales['y-axis-0'];
const context = chartInstance.chart.ctx;
if (xOffset == undefined) xOffset = 0;
if (yOffset == undefined) yOffset = 0;
// render vertical line
context.beginPath();
context.strokeStyle = color;
context.moveTo(lineLeftOffset, scale.top);
context.lineTo(lineLeftOffset, scale.bottom);
context.stroke();
// write label
context.fillStyle = color;
context.textAlign = alignment;
context.fillText(label, lineLeftOffset + xOffset, (scale.bottom - scale.top) / 2 + scale.top + yOffset);
},
afterDatasetsDraw: function (chart, easing) {
if (chart.config.lineAtIndex) {
labelIndex = 0;
chart.config.lineAtIndex.forEach((pointIndex) => {
if (chart.config.verticalLinesLabels != undefined) { // if array of labels exists...
label = chart.config.verticalLinesLabels[labelIndex]; // chart.config.verticalLinesLabels must contain all elements; use elements ="" for lines not requiring labels
color = chart.config.verticalLinesColors[labelIndex]; // chart.config.verticalLinesColors must contain all elements
alignment = chart.config.verticalLinesAlignments[labelIndex]; // chart.config.verticalLinesAlignments must contain all elements
xOff = chart.config.verticalLinesX[labelIndex]; // chart.config.verticalLinesX must contain all elements
yOff = chart.config.verticalLinesY[labelIndex]; // chart.config.verticalLinesY must contain all elements
} else {
label = "";
}
this.renderVerticalLine(chart, pointIndex, label, color, alignment, xOff, yOff)
labelIndex++;
});
}
}
};
Chart.plugins.register(verticalLinePlugin);
Usage:
myChart.config.verticalLinesLabels = ["aaa", "bbb", "ddd"];
myChart.config.verticalLinesColors = ["#FF0000", 'rgb(0,255,0)', 'rgba(0,0,255,0.5)'];
myChart.config.verticalLinesAlignments = ["left", "center", "right"]; // Set label aligment (note: it is inverted because referred to line, not to label)
myChart.config.verticalLinesX = [10,5,0]; // Set label X offset
myChart.config.verticalLinesY = [10,5,0]; // Set label Y offset
myChart.config.lineAtIndex = [10,30,50]; // Mandatory to enable all previous ones
myChart.update()

Fill Chart.js bar chart with diagonal stripes or other patterns

I am trying to fill a bar graph with stripes so that it looks like the attached image. Is there a way to do this? How about other patterns?
tl;dr
Just pass a CanvasPattern or CanvasGradient to the dataset's backgroundColor property as the official docs say.
Excuse me, what?
This can be done through a 3rd party library like patternomaly, but if you just want a few simple pattern it is unnecessary since you can easily create a custom function that takes a color and give you back a canvas pattern:
function createDiagonalPattern(color = 'black') {
// create a 10x10 px canvas for the pattern's base shape
let shape = document.createElement('canvas')
shape.width = 10
shape.height = 10
// get the context for drawing
let c = shape.getContext('2d')
// draw 1st line of the shape
c.strokeStyle = color
c.beginPath()
c.moveTo(2, 0)
c.lineTo(10, 8)
c.stroke()
// draw 2nd line of the shape
c.beginPath()
c.moveTo(0, 8)
c.lineTo(2, 10)
c.stroke()
// create the pattern from the shape
return c.createPattern(shape, 'repeat')
}
Then just call it in your datasets (don't forget to add a border if you need that):
datasets: [{
label: 'Good questions',
data: [3, 4, 1, 6, 10],
backgroundColor: createDiagonalPattern('green'),
// create a border with the same color
borderColor: 'green',
borderWidth: 1,
}],
Edge cases
Keep in mind that canvas has anti-aliasing so when you draw stuff around the corners it can mess up your pattern. To mitigate this just draw your lines from an edge.
If you create the diagonal line between the corners like this:
c.beginPath()
c.moveTo(0, 0)
c.lineTo(10, 10)
c.stroke()
Then the pattern wouldn't look seamless because the corner crops off parts so you lose that infinite effect:
Demo
var element = document.getElementById('chart');
var ctx = element.getContext("2d");
function createDiagonalPattern(color = 'black') {
let shape = document.createElement('canvas')
shape.width = 10
shape.height = 10
let c = shape.getContext('2d')
c.strokeStyle = color
c.beginPath()
c.moveTo(2, 0)
c.lineTo(10, 8)
c.stroke()
c.beginPath()
c.moveTo(0, 8)
c.lineTo(2, 10)
c.stroke()
return c.createPattern(shape, 'repeat')
}
var graph = new Chart(element, {
type: 'bar',
data: {
labels: ['jan', 'feb', 'mar', 'apr', 'may'],
datasets: [
{
label: 'Good questions',
data: [3, 4, 1, 6, 10],
backgroundColor: createDiagonalPattern('green'),
borderColor: 'green',
borderWidth: 1,
},
{
label: 'Bad questions',
data: [2, 7, 3, 5, 1],
backgroundColor: createDiagonalPattern('#FF0000'),
borderColor: '#FF0000',
borderWidth: 1,
},
],
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
},
}],
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<canvas id="chart" ></canvas>
Old question, but now there's the patternomaly add-on :-)
https://github.com/ashiguruma/patternomaly
It contains 21 pre-defined patterns you can use in chart.js.
There is a section on patterns and gradients in the ChartJS documentation that allows to pass a CanvasPattern or CanvasGradient object instead of a string colour.
Read about it here:
http://www.chartjs.org/docs/latest/general/colors.html
Looking at the bar and global options, this doesn't seem possible using only chartjs.
http://www.chartjs.org/docs/#bar-chart-chart-options
http://www.chartjs.org/docs/#getting-started-global-chart-configuration
As chartsjs uses a canvas element to display its elements, you won't be able to implement a CSS solution either. If you really want to do this, you could try editing the chartjs library. Or just pick a solid color.

correct positioning with Highcharts renderer on a bar chart

I have a Highcharts bar chart that I'm trying to add custom shapes to based on the bar values and position. To start with, I'm just trying to use highcharts.renderer.path, to add a line for each bar, as tall as the bar, positioned on the x axis based on a hard coded value. Here's a picture of what I mean:
This should be easy, and it is when the chart.type = "column". In the highcharts callback, I would use getBBox() on each bar, and translate() to convert the x axis value to a pixel value.
However, I've run into several problems when trying to do this with chart.type = "bar". First, all x and y values are switched (I assume this is how the author created the bar chart from a column chart in the first place). This is true for all the properties of the chart as well: plotLeft is now the top, plotTop is now the left.
This should work:
function (chart) {
$.each(chart.series[0].data, function (pointIndex, point) {
var plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
yStart = chart.plotTop+elem.x;
xStart = chart.plotLeft+elem.height;
plotLine.path = ["M", xStart, yStart+1, "L", xStart, yStart+point.pointWidth];
plotLine.attr = {
'stroke-width': 1,
stroke: point.color,
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
});
});
Full example: http://jsfiddle.net/Bh3J4/9/
The second issue may be a bug that can't be overcome. It appears that when there is more than one data point, all of the x and y values get mixed up between the points. Notice in the fiddle that the colors don't match the positions. I've created an issue on GitHub.
When there's just one point, it's not a problem. When there are two points, I could easily switch the values to get the right positioning. However when there are 3 or more points, I can't seem to figure out the logic for how the values get mixed up.
The third issue, is that the translate function doesn't seem to work on the xAxis for a bar chart, even though it does on the yAxis.
chart.yAxis[0].translate(4); // correct for bottom axis
chart.xAxis[0].translate(1); // incorrect for side axis
Is there another way to achieve what I'm looking for? Am I missing something in that Fiddle that's not actually a bug?
I was able to achieve the result I wanted, but I don't know if it's coincidental or a workaround for an actual bug. Regardless, it seems that using the x value from the reverse sorted array helped me line everything up correctly. Here's the callback function for highcharts:
function (chart) {
var benchmarks = { A: 1.5, B: 3.6, C: 2 },
reverseData = _.clone(chart.series[0].data).reverse();
_.each(chart.series[0].data, function (point, pointIndex) {
var plotLine = {},
elem = point.graphic.element.getBBox(),
reverseElem = reverseData[pointIndex].graphic.element.getBBox(),
benchmark = benchmarks[point.category],
yStart = chart.plotTop+reverseElem.x,
xStart = chart.plotLeft+chart.yAxis[0].translate(benchmark),
yEnd = yStart+point.pointWidth-1;
plotLine.path = ["M", xStart, yStart+1, "L", xStart, yEnd];
plotLine.attr = {
'stroke-width': 1,
stroke: "red",
zIndex: 5
};
chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
var margin = 5,
xPadding = 10,
yPadding = 5,
xSplit = xPadding/2,
ySplit = yPadding/2,
text,
box;
text = chart.renderer.text("Top Perf Avg " + benchmark, xStart, yEnd+margin+16).attr({
color: "#646c79",
align: "center",
"font-family": "Arial, sans-serif",
"font-size": 9,
"font-weight": "bold",
style: "text-transform: uppercase",
zIndex: 7
}).add();
box = text.getBBox();
chart.renderer.path(["M", box.x-xSplit, box.y-ySplit,
"l", (box.width/2)+xSplit-margin, 0,
margin, -margin,
margin, margin,
(box.width/2)+xSplit-margin, 0,
0, box.height+yPadding,
-(box.width+xPadding), 0,
0, -(box.height+yPadding)])
.attr({
'stroke-width': 1,
stroke: "#cccccc",
fill: "#ffffff",
zIndex: 6
}).add();
});
}
See the complete working graph here: http://jsfiddle.net/Bh3J4/18/
In the fact, Highcharts rotate everything using transform, so use the same to rotate these lines, see example: http://jsfiddle.net/Bh3J4/19/
function (chart) {
var d = chart.series[0].data,
len = d.length;
for(var i =0; i < len; i++){
var point = d[i],
plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
console.log(point,point.color);
xStart = point.plotX - point.pointWidth / 2;
yStart = point.plotY;
plotLine.path = ["M", xStart, yStart, "L", xStart+point.pointWidth, yStart];
plotLine.attr = {
transform: 'translate(491,518) rotate(90) scale(-1,1) scale(1 1)',
'stroke-width': 1,
stroke: point.color,
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
};
}
Slight adjustment that seems to give precise alignment:
Pls note: changes to calc of xStart/yStart and change to transform translate parameter.
My approach was to make it work for column chart and then get translate refined.
The only unsatisfactory part is that xStart needs: xStart = elem.x+chart.plotLeft; in 'column' mode vs xStart = elem.x; in 'bar' mode...
function (chart) {
var d = chart.series[0].data,
len = d.length;
for(var i =0; i < len; i++){
var point = d[i],
plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
console.log(point,point.color);
xStart = elem.x;
yStart = chart.plotHeight - (elem.height/2) + chart.plotTop;
plotLine.path = ["M", xStart, yStart, "L", xStart+point.pointWidth, yStart];
plotLine.attr = {
transform: 'translate(542.5,518) rotate(90) scale(-1,1) scale(1 1)',
'stroke-width': 5,
stroke: 'blue',
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
};
}

Categories