Highcharts tooltip not working - javascript

I cannot figure out how to get the 'to' and 'from' dates from my data into the tooltips. Tried various methods I found around SO. Anyone got any tips? I normally load data from CSV. Right now the data is hard-coded in the code.
var options = {
chart: {
zoomType: 'y',
borderWidth: '0',
borderRadius: '15',
renderTo: 'container',
inverted: true,
backgroundColor: {
linearGradient: [0, 0, 500, 500],
stops: [
[0, 'rgb(44, 44, 58)'],
[1, 'rgb(62, 62, 62)']
]
},
plotBackgroundColor: 'rgba(255, 255, 255, .9)'
},
tooltip: {
formatter: function () {
var point = this.point;
return '<b>' + point.category +
'</b><br/>' + Highcharts.dateFormat('%b %e, %Y', this.y) +
' - ' + Highcharts.dateFormat('%b %e, %Y', this.series[0]);
}
},
legend: {
enabled: false
},
title: {
text: 'EVMS Calendar'
},
xAxis: {
categories: []
},
plotOptions: {
series: {
grouping: false
}
},
yAxis: {
type: 'datetime',
minRange: '604800000',
startOnTick: false,
endOnTick: false,
title: {
text: ''
}
},
series: []
},
categories = [];;
//// This is the data processing section \\\\
// Hard Coded Data
var data ="valid data";
// Split the lines
var lines = data.split('\n');
// Iterate over the lines and add categories or series
// Split the data by comma
// Get the number of items in the object (iLen)
// Series start
// Series type is columnrange
// Servies name is item 0 of the line (employees name)
$.each(lines, function (lineNo, line) {
var items = line.split(','),
iLen = items.length,
series = {
type: 'columnrange',
data: [],
name: items[0]
};
// Start categories
// for each items (0) get the row data (dates) and push to categories(line number, from and to)
categories.push(items[0]);
for (var i = 1; i < iLen; i += 2) {
var from = (new Date(items[i])).getTime(),
to = (new Date(items[i + 1])).getTime();
if (!isNaN(from) && !isNaN(to)) {
series.data.push([lineNo, from, to]);
}
};
options.series.push(series);
});
options.xAxis.categories = categories;
// Create the chart
var chart = new Highcharts.Chart(options);

ah, my bad. Its this.point.high / low. I found out by looking through the elements in chrome

Related

Have an issue with JavaScript, AJAX code displaying data

I need some tips from you out there to come over a good solution on my problem with JavaScript, AJAX and JSON data. I want to fill a generic set with barcharts (I am using HighCharts) on my web page. The data is in JSON format which from the start I only used date and value as pair data set. The solution works fine of I had only one bar chart it, but I have a lot of charts on my page and I need to show all of them (up to twelve).
Now I want to adjust for displaying more than one graph. In the code below the DataMacro array works fine with the chart. It also has a hard coded ID matching a . Now I have a series of in the page like id=barchart11, id=barchar21, and so on. In the dataset I have made a tag called PanelCodeUI that I am going to use looping through the dataset. The problem is how to do that. The each-loop will now fill in all date,value for all vessels.
And further it I need to restructure the function which is displaying the barchart. The best thing would be to call a function with a data array and panelCodeUI id just replacing the name of the barchart and set in the datamacro as is. But I don’t know how to do this. The data is mixed between all vessels and I need to collect all data before sending to a function. So is the problem with AJAX and JavaScript with is asynchron. I need to ensure that it behaves correctly and fast.
Maybe I need to change my dataset, or I need to do this in several step like finding all vessel IDs then do another AJAX call to get date,value pair from a vessel and then displaying. I hope there is a way to do this with this data set and hope somebody can help me on this
Here is a bit of the JSON data set:
[
{"__type":"Demo.Entities.OilProductionLast5DaysEntity","Date":1465084800000,"Value":844,"VesselId":1,"SectorId":2,"PanelCodeUI":"21","VesselCodeUI":"21","VesselSorting":1},
{"__type":"Demo.Entities.OilProductionLast5DaysEntity","Date":1465084800000,"Value":8720,"VesselId":4,"SectorId":1,"PanelCodeUI":"11","VesselCodeUI":"12","VesselSorting":2},
{"__type":"Demo.Entities.OilProductionLast5DaysEntity","Date":1465084800000,"Value":948,"VesselId":5,"SectorId":1,"PanelCodeUI":"11","VesselCodeUI":"11","VesselSorting":1},
{"__type":"Demo.Entities.OilProductionLast5DaysEntity","Date":1465084800000,"Value":0,"VesselId":6,"SectorId":3,"PanelCodeUI":"31","VesselCodeUI":"31","VesselSorting":1},
{"__type":"Demo.Entities.OilProductionLast5DaysEntity","Date":1465171200000,"Value":2067,"VesselId":1,"SectorId":2,"PanelCodeUI":"21","VesselCodeUI":"21","VesselSorting":1}
]
And here is the JavaScript code so far:
$(function () {
var datamacro = [];
$.ajax({
type: "POST",
url: '../Services/HighChartService.asmx/GetOilProductionLast5DaysByActiveVessels',
data: '',
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (seriedata) {
console.log(JSON.stringify(seriedata.d));
var productions = seriedata.d;
$.each(productions, function (index, productions) {
var yval = productions.Value;
var xval = productions.Date;
var x = [xval, yval];
datamacro.push(x);
//alert("productions Name: " + productions.Date + "\nID: " + productions.Value);
});
$(function () {
//var bchart = '#barchart' + vesselindex.toString();
// want this to be looped with generic names like #barchart11, #barchart21, #barchart31 and so on
$('#barchart11').highcharts({
chart: {
type: 'column'
},
title: {
text: 'LAST FIVE DAYS'
},
subtitle: {
text: ''
},
xAxis: {
type: "datetime",
tickInterval: 24 * 3600 * 1000,
labels: {
rotation: -45,
align: 'right'
},
dateTimeLabelFormats: { // don't display the dummy year
day: '%e. %b',
},
//crosshair: true
},
credits: {
enabled: false
},
yAxis: {
labels: {
enabled: false
},
title: {
text: null
}
},
tooltip: {
formatter: function () {
return Highcharts.dateFormat('%d/%m/%Y', new Date(this.x)) + '<br/>' + ' in barrels: ' + this.y;
}
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}, series: {
pointRange: 24 * 3600 * 1000, // one day
pointInterval: 3600 * 1000
}
},
series: [{
//name: '',
showInLegend: false,
data: datamacro,
dataLabels: {
enabled: true,
rotation: -90,
color: '#FFFFFF',
align: 'right',
format: '{point.y:.1f}', // one decimal
y: 10, // 10 pixels down from the top
style: {
fontSize: '13px',
fontFamily: 'Verdana, sans-serif'
}
}
}]
});
});
},
error: function (r) {
alert(r.responseText);
},
failure: function (r) {
alert(r.responseText);
}
});
});
If i understand correctly, you would like to draw a chart for each different panelCodeUI ?
If that's the case, change your code after AJAX success with that :
var productions = seriedata.d;
var listPanelCodeUI = productions.map(function(p){return p.PanelCodeUI}).filter(function(item, pos, self) {
return self.indexOf(item) == pos;
});
//listPanelCodeUI : [21,11,31]
listPanelCodeUI.sort();
listPanelCodeUI.forEach(function(e){
datamacro = [];
//Create a div for each panelCodeUI
$("body").append("<div id='barchart" + e + "'></div>");
var divId = "#barchart"+e;
//Filter productions for specific panelCodeUI
var data = productions.filter(function(p){return p.panelCodeUI === e});
data.forEach(function(d){
var yval = d.Value;
var xval = d.Date;
var x = [xval, yval];
datamacro.push(x);
});
$(function () {
$(divId).highcharts({
...
})
})
}
That's what you need to parse your data:
charts = [];
$.each(productions.map(function(el) {
return el.PanelCodeUI;
}).filter(function(el, index, arr) {
return arr.indexOf(el) === index;
}), function(index,PanelCodeUI) {
var serie = productions.filter(function(el) {
return el.PanelCodeUI === PanelCodeUI;
});
$.each(serie, function(index, production) {
datamacro.push([production.Value, production.Date]);
});
drawChart('#barchart' + PanelCodeUI, 'LAST FIVE DAYS', datamacro);
});
Also i made this helper function to create the charts:
function drawChart(containerID, chartTitle, data) {
charts.push(new Highchart.Chart({
chart: {
type: 'column',
renderTo: containerID
},
title: {
text: chartTitle
},
subtitle: {
text: ''
},
xAxis: {
type: "datetime",
tickInterval: 24 * 3600 * 1000,
labels: {
rotation: -45,
align: 'right'
},
dateTimeLabelFormats: { // don't display the dummy year
day: '%e. %b',
},
//crosshair: true
},
credits: {
enabled: false
},
yAxis: {
labels: {
enabled: false
},
title: {
text: null
}
},
tooltip: {
formatter: function() {
return Highcharts.dateFormat('%d/%m/%Y', new Date(this.x)) + '<br/>' + ' in barrels: ' + this.y;
}
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
},
series: {
pointRange: 24 * 3600 * 1000, // one day
pointInterval: 3600 * 1000
}
},
series: [{
//name: '',
showInLegend: false,
data: data,
dataLabels: {
enabled: true,
rotation: -90,
color: '#FFFFFF',
align: 'right',
format: '{point.y:.1f}', // one decimal
y: 10, // 10 pixels down from the top
style: {
fontSize: '13px',
fontFamily: 'Verdana, sans-serif'
}
}
}]
}));
}

Updating highcharts live data not working

I have some UV Sensors (currently running on Thingspeak) - but I need to have multiple series on the same chart, so I made a sample .php page on my website.
I have the basic chart working nicely, but I have not been able to get it to do live updates - my coding skills are very lacking & I would appreciate any help I can get!
The sample chart is here: http://www.sesupply.co.nz/test.php
I have the code on JSFiddle here: https://jsfiddle.net/SESupply/9xn65qrL/9/
// variables for the first series
var series_1_channel_id = 43330;
var series_1_field_number = 4;
var series_1_read_api_key = '7ZPHNX2SXPM0CA1K';
var series_1_results = 480;
var series_1_color = '#d62020';
var series_1_name = 'Zims Sensor';
// variables for the second series
var series_2_channel_id = 45473;
var series_2_field_number = 2;
var series_2_read_api_key = 'N12T3CWQB5IWJAU9';
var series_2_results = 480;
var series_2_color = '#00aaff';
var series_2_name = 'UVM30A';
// chart title
var chart_title = 'UV Sensors Zim / UVM30A';
// y axis title
var y_axis_title = 'UV Index';
// user's timezone offset
var my_offset = new Date().getTimezoneOffset();
// chart variable
var my_chart;
// when the document is ready
$(document).on('ready', function () {
// add a blank chart
addChart();
// add the first series
addSeries(series_1_channel_id, series_1_field_number, series_1_read_api_key, series_1_results, series_1_color, series_1_name);
// add the second series
addSeries(series_2_channel_id, series_2_field_number, series_2_read_api_key, series_2_results, series_2_color, series_2_name);
});
// add the base chart
function addChart() {
// variable for the local date in milliseconds
var localDate;
// specify the chart options
var chartOptions = {
chart: {
renderTo: 'chart-container',
defaultSeriesType: 'spline',
zoomType: 'x', // added here
panning: true,
panKey: 'shift',
backgroundColor: '#ffffff',
events: {
load: addSeries
}
},
title: {
text: chart_title
},
subtitle: {
text: 'Click and drag to zoom in. Hold down shift key to pan.'
},
plotOptions: {
series: {
marker: {
radius: 2
},
animation: true,
step: false,
borderWidth: 0,
turboThreshold: 0
}
},
scrollbar: {
enabled: true
// barBackgroundColor: 'gray',
// barBorderRadius: 7,
// barBorderWidth: 0,
// buttonBackgroundColor: 'gray',
// buttonBorderWidth: 0,
// buttonArrowColor: 'yellow',
// buttonBorderRadius: 7,
// rifleColor: 'yellow',
// trackBackgroundColor: 'white',
// trackBorderWidth: 1,
// trackBorderColor: 'silver',
// trackBorderRadius: 7
},
tooltip: {
// reformat the tooltips so that local times are displayed
formatter: function () {
var d = new Date(this.x + (my_offset * 60000));
var n = (this.point.name === undefined) ? '' : '<br>' + this.point.name;
return this.series.name + ':<b>' + this.y + '</b>' + n + '<br>' + d.toDateString() + '<br>' + d.toTimeString().replace(/\(.*\)/, "");
}
},
xAxis: {
type: 'datetime',
title: {
text: 'Date'
}
},
rangeSelector: {
enabled: true,
buttons: [{
type: 'minute',
count: 60,
text: 'Hour'
}, {
type: 'day',
count: 1,
text: 'Day'
}, {
type: 'week',
count: 1,
text: 'Week'
}, {
type: 'all',
text: 'All'
}]
},
yAxis: {
title: {
text: y_axis_title
}
},
exporting: {
enabled: true
},
legend: {
enabled: true
},
credits: {
text: 'ThingSpeak.com',
href: 'https://thingspeak.com/',
style: {
color: '#D62020'
}
}
};
// draw the chart
my_chart = new Highcharts.Chart(chartOptions);
}
// add a series to the chart
function addSeries(channel_id, field_number, api_key, results, color, name) {
var field_name = 'field' + field_number;
// get the data with a webservice call
$.getJSON('https://api.thingspeak.com/channels/' + channel_id + '/fields/' + field_number + '.json?offset=0&round=2&results=' + results + '&api_key=' + api_key, function (data) {
// blank array for holding chart data
var chart_data = [];
// iterate through each feed
$.each(data.feeds, function () {
var point = new Highcharts.Point();
// set the proper values
var value = this[field_name];
point.x = getChartDate(this.created_at);
point.y = parseFloat(value);
// add location if possible
if (this.location) {
point.name = this.location;
}
// if a numerical value exists add it
if (!isNaN(parseInt(value))) {
chart_data.push(point);
}
});
// add the chart data
my_chart.addSeries({
data: chart_data,
name: name,
color: color
});
});
setTimeout(addSeries, 1000);
}
cache: false;
// converts date format from JSON
function getChartDate(d) {
// offset in minutes is converted to milliseconds and subtracted so that chart's x-axis is correct
return Date.parse(d) - (my_offset * 60000);
}
I have tried following the livedata example but seem to be failing miserably. The sensors update about every 60 seconds (only during the day - as there is no UV at night, I put the sensors into "sleep" mode to save battery power)

Highcharts error #15: www.highcharts.com/errors/15

I'm trying to use yahoo finance data to generate a Highcharts candlestick chart like this http://www.highcharts.com/stock/demo/candlestick-and-volume. But I keep getting this error: http://www.highcharts.com/errors/15
Highcharts Error #15
Highcharts expects data to be sorted
This happens when you are trying to create a line series or a stock chart where the data is not sorted in ascending X order. For performance reasons, Highcharts does not sort the data, instead it is required that the implementer pre-sorts the data.
My code is as follows.
$(function () {
$.getJSON('http://websitescraper.heroku.com/?url=http://ichart.finance.yahoo.com/table.csv?s=000338.sz&callback=?', function (csvdata) {
//console.log(csvdata);
var arr = csvdata.split('\n').slice(1);
var data = [];
for (var i = arr.length-1; i >= 0; --i) {
//console.log(arr[i]);
var line = arr[i].split(',');
line[0] = Date.parse(line[0]);
line = $.map(line, function(v) {
return parseFloat(v);
});
line = line.slice(0,6);
//var j = JSON.stringify(line.slice(0,0+6));
console.log(line);
data.push(line);
}
data = JSON.stringify(data.slice(1));
console.log(data);
run(data);
});
});
function run(data) {
// split the data set into ohlc and volume
var ohlc = [],
volume = [],
dataLength = data.length,
// set the allowed units for data grouping
/*groupingUnits = [[
'week', // unit name
[1] // allowed multiples
], [
'month',
[1, 2, 3, 4, 6]
]],*/
i = 0;
for (i; i < dataLength; i += 1) {
ohlc.push([
data[i][0], // the date
data[i][1], // open
data[i][2], // high
data[i][3], // low
data[i][4] // close
]);
volume.push([
data[i][0], // the date
data[i][5] // the volume
]);
}
// create the chart
$('#container2').highcharts('StockChart', {
rangeSelector: {
selected: 1
},
title: {
text: 'Shanghai Composite Index Historical'
},
yAxis: [{
labels: {
align: 'right',
x: -3
},
title: {
text: 'OHLC'
},
height: '60%',
lineWidth: 2
}, {
labels: {
align: 'right',
x: -3
},
title: {
text: 'Volume'
},
top: '65%',
height: '35%',
offset: 0,
lineWidth: 2
}],
series: [{
type: 'candlestick',
upLineColor: 'red',
downLineColor: 'green',
name: 'SSE',
data: ohlc,
/*dataGrouping: {
units: groupingUnits
}*/
}, {
type: 'column',
name: 'Volume',
data: volume,
yAxis: 1
/*dataGrouping: {
units: groupingUnits
}*/
}]
});
}
Can somebody help? Thanks a lot!
The problem is the data = JSON.stringify(data.slice(1));. It turns the array to a string, therefore Highstock doesn't recognize it. Remove JSON.stringify and it will work fine:
data = data.slice(1);
Here's the DEMO.

How to divide data from the series in highcharts

I want to process data from a .csv file to:
Divide the data coming in by 10, e.g., 588 => 58.8
Remove outliers from the data or to change to zero, e.g., 8888 => 0
Here is my javascript, I appreciate the help!!
$.get('http://www.geoinc.org/Dropbox/geo/sites/GC_ROOM/charts/hassayampa.csv', function(data)
{
// Split the lines
var lines = data.split('\n');
var i = 0;
var csvData = [];
// Iterate over the lines and add categories or series
$.each(lines, function(lineNo, line)
{
csvData[i] = line.split(',');
i = i + 1;
});
var columns = csvData[0];
var categories = [], series = [];
for(var colIndex=0,len=columns.length; colIndex<len; colIndex++)
{
//first row data as series's name
var seriesItem=
{
data:[],
name:csvData[0][colIndex]
};
for(var rowIndex=1,rowCnt=csvData.length; rowIndex<rowCnt; rowIndex++)
{
//first column data as categories,
if (colIndex == 0)
{
categories.push(csvData[rowIndex][0]);
}
else if(parseFloat(csvData[rowIndex][colIndex])) // <-- here
{
seriesItem.data.push(parseFloat(csvData[rowIndex][colIndex]));
}
};
//except first column
if(colIndex>0)series.push(seriesItem);
}
// Create the chart
var chart = new Highcharts.Chart(
{
chart:
{
renderTo: 'test',
type: 'line',
zoomType: 'x',
},
title: {
text: 'Daily Average Temperature',
x: -20 //center
},
subtitle: {
text: 'Source: HASSAYAMPA',
x: -20
},
xAxis:
{
categories: categories,
labels:
{
step: 80,
},
tickWidth: 0
},
yAxis:
{
title: {
text: 'Temperature (\xB0C)'
},
//min: 0
},
tooltip:
{
formatter: function()
{
return '<b>'+ this.series.name +'</b><br/>'+ this.x +': '+ this.y +'\xB0C';
}
},
legend:
{
layout: 'vertical',
//backgroundColor: '#FFFFFF',
//floating: true,
align: 'left',
//x: 100,
verticalAlign: 'top',
//y: 70,
borderWidth: 0
},
plotOptions:
{
area:
{
animation: false,
stacking: 'normal',
lineColor: '#666666',
lineWidth: 1,
marker:
{
lineWidth: 1,
lineColor: '#666666'
}
}
},
series: series
});
});
I'm not sure what you are asking, but I'll take a shot at it...
First things first, this snippet of code is not sound. It'll not only skip NaNs but 0s as well (which is valid numeric data):
else if(parseFloat(csvData[rowIndex][colIndex]))
{
seriesItem.data.push(parseFloat(csvData[rowIndex][colIndex]));
}
Instead I'd do:
//first column data as categories,
if (colIndex == 0)
{
categories.push(csvData[rowIndex][0]);
}
else
{
var fVal = parseFloat(csvData[rowIndex][colIndex]);
if (!isNaN(fVal))
{
fVal = fVal / 10.0; //<-- here's the division!!
seriesItem.data.push(fVal);
}
}
As far as how to exclude outliers, the big question there is how do you want to exclude outliers? A simple min/max criteria? Then just check that fVal is within those limits before seriesItem.data.push...

How can I change the width of the bars in a highchart?

With a bar chart like this one, is is possible to change the width of the bars to represent another data attribute, say the weight of the fruits. The heavier the fruit is, the thicker the bar.
You play with the script here. I am open to other javascript plotting libraries that could do that as long as they are free.
$(function () {
var chart;
$(document).ready(function() {
chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
type: 'column'
},
title: {
text: 'Column chart with negative values'
},
xAxis: {
categories: ['Apples', 'Oranges', 'Pears', 'Grapes', 'Bananas']
},
tooltip: {
formatter: function() {
return ''+
this.series.name +': '+ this.y +'';
}
},
credits: {
enabled: false
},
series: [{
name: 'John',
data: [5, 3, 4, 7, 2]
// I would like something like this (3.5, 6 etc is the width) :
// data: [[5, 3.4], [3, 6], [4, 3.4], [7, 2], [2, 5]]
}, {
name: 'Jane',
data: [2, -2, -3, 2, 1]
}, {
name: 'Joe',
data: [3, 4, 4, -2, 5]
}]
});
});
});​
pointWidth is what you require to set the width of the bars. try
plotOptions: {
series: {
pointWidth: 15
}
}
This display bars with the width of 15px. Play around here. Just made an edit to the already existing code.
I use a set of area charts to simulate a variable-width-column/bar-chart. Say, each column/bar is represented by a rectangle area.
See my fiddle demo (http://jsfiddle.net/calfzhou/TUt2U/).
$(function () {
var rawData = [
{ name: 'A', x: 5.2, y: 5.6 },
{ name: 'B', x: 3.9, y: 10.1 },
{ name: 'C', x: 11.5, y: 1.2 },
{ name: 'D', x: 2.4, y: 17.8 },
{ name: 'E', x: 8.1, y: 8.4 }
];
function makeSeries(listOfData) {
var sumX = 0.0;
for (var i = 0; i < listOfData.length; i++) {
sumX += listOfData[i].x;
}
var gap = sumX / rawData.length * 0.2;
var allSeries = []
var x = 0.0;
for (var i = 0; i < listOfData.length; i++) {
var data = listOfData[i];
allSeries[i] = {
name: data.name,
data: [
[x, 0], [x, data.y],
{
x: x + data.x / 2.0,
y: data.y,
dataLabels: { enabled: true, format: data.x + ' x {y}' }
},
[x + data.x, data.y], [x + data.x, 0]
],
w: data.x,
h: data.y
};
x += data.x + gap;
}
return allSeries;
}
$('#container').highcharts({
chart: { type: 'area' },
xAxis: {
tickLength: 0,
labels: { enabled: false}
},
yAxis: {
title: { enabled: false}
},
plotOptions: {
area: {
marker: {
enabled: false,
states: {
hover: { enabled: false }
}
}
}
},
tooltip: {
followPointer: true,
useHTML: true,
headerFormat: '<span style="color: {series.color}">{series.name}</span>: ',
pointFormat: '<span>{series.options.w} x {series.options.h}</span>'
},
series: makeSeries(rawData)
});
});
Fusioncharts probably is the best option if you have a license for it to do the more optimal Marimekko charts…
I've done a little work trying to get a Marimekko charts solution in highcharts. It's not perfect, but approximates the first Marimekko charts example found here on the Fusion Charts page…
http://www.fusioncharts.com/resources/chart-tutorials/understanding-the-marimekko-chart/
The key is to use a dateTime axis, as that mode provides you more flexibility for the how you distribute points and line on the X axis which provides you the ability to have variably sized "bars" that you can construct on this axis. I use 0-1000 second space and outside the chart figure out the mappings to this scale to approximate percentage values to pace your vertical lines. Here ( http://jsfiddle.net/miken/598d9/2/ ) is a jsfiddle example that creates a variable width column chart.
$(function () {
var chart;
Highcharts.setOptions({
colors: [ '#75FFFF', '#55CCDD', '#60DD60' ]
});
$(document).ready(function() {
var CATEGORY = { // number out of 1000
0: '',
475: 'Desktops',
763: 'Laptops',
1000: 'Tablets'
};
var BucketSize = {
0: 475,
475: 475,
763: 288,
1000: 237
};
chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
type: 'area'
},
title: {
text: 'Contribution to Overall Sales by Brand & Category (in US$)<br>(2011-12)'
},
xAxis: {
min: 0,
max: 1000,
title: {
text: '<b>CATEGORY</b>'
},
tickInterval: 1,
minTickInterval: 1,
dateTimeLabelFormats: {
month: '%b'
},
labels: {
rotation: -60,
align: 'right',
formatter: function() {
if (CATEGORY[this.value] !== undefined) {
return '<b>' + CATEGORY[this.value] + ' (' +
this.value/10 + '%)</b>';
}
}
}
},
yAxis: {
max: 100,
gridLineWidth: 0,
title: {
text: '<b>% Share</b>'
},
labels: {
formatter: function() {
return this.value +'%'
}
}
},
tooltip: {
shared: true,
useHTML: true,
formatter: function () {
var result = 'CATEGORY: <b>' +
CATEGORY[this.x] + ' (' + Highcharts.numberFormat(BucketSize[this.x]/10,1) + '% sized bucket)</b><br>';
$.each(this.points, function(i, datum) {
if (datum.point.y !== 0) {
result += '<span style="color:' +
datum.series.color + '"><b>' +
datum.series.name + '</b></span>: ' +
'<b>$' + datum.point.y + 'K</b> (' +
Highcharts.numberFormat(
datum.point.percentage,2) +
'%)<br/>';
}
});
return (result);
}
},
plotOptions: {
area: {
stacking: 'percent',
lineColor: 'black',
lineWidth: 1,
marker: {
enabled: false
},
step: true
}
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'top',
x: 0,
y: 100,
borderWidth: 1,
title: {
text : 'Brand:'
}
},
series: [ {
name: 'HP',
data: [
[0,298],
[475,109],
[763,153],
[1000,153]
]
}, {
name: 'Dell',
data: [
[0,245],
[475,198],
[763,120],
[1000,120]
]
}, {
name: 'Sony',
data: [
[0,335],
[475,225],
[763,164],
[1000,164]
]
}]
},
function(chart){
// Render bottom line.
chart.renderer.path(['M', chart.plotLeft, chart.plotHeight + 66, 'L', chart.plotLeft+chart.plotWidth, chart.plotHeight + 66])
.attr({
'stroke-width': 3,
stroke: 'black',
zIndex:50
})
.add();
for (var category_idx in CATEGORY) {
chart.renderer.path(['M', (Math.round((category_idx / 1000) * chart.plotWidth)) + chart.plotLeft, 66, 'V', chart.plotTop + chart.plotHeight])
.attr({
'stroke-width': 1,
stroke: 'black',
zIndex:4
})
.add();
}
});
});
});
It adds an additional array to allow you to map category names to second tic values to give you a more "category" view that you might want. I've also added code at the bottom that adds vertical dividing lines between the different columns and the bottom line of the chart. It might need some tweaks for the size of your surrounding labels, etc. that I've hardcoded in pixels here as part of the math, but it should be doable.
Using a 'percent' type accent lets you have the y scale figure out the percentage totals from the raw data, whereas as noted you need to do your own math for the x axis. I'm relying more on a tooltip function to provide labels, etc than labels on the chart itself.
Another big improvement on this effort would be to find a way to make the tooltip hover area and labels to focus and be centered and encompass the bar itself instead of the right border of each bar that it is now. If someone wants to add that, feel free to here.
If I got it right you want every single bar to be of different width. I had same problem and struggled a lot to find a library offering this option. I came to the conclusion - there's none.
Anyways, I played with highcharts a little, got creative and came up with this:
You mentioned that you'd like your data to look something like this: data: [[5, 3.4], [3, 6], [4, 3.4]], with the first value being the height and the second being the width.
Let's do it using the highcharts' column graph.
Step 1:
To better differentiate the bars, input each bar as a new series. Since I generated my data dynamically, I had to assign new series dynamically:
const objects: any = [];
const extra = this.data.length - 1;
this.data.map((range) => {
const obj = {
type: 'column',
showInLegend: false,
data: [range[1]],
animation: true,
borderColor: 'black',
borderWidth: 1,
color: 'blue'
};
for (let i = 0; i < extra; i++) {
obj.data.push(null);
}
objects.push(obj);
});
this.chartOptions.series = objects;
That way your different series would look something like this:
series: [{
type: 'column',
data: [5, 3.4]
}, {
type: 'column',
data: [3, 6]
}, {
type: 'column',
data: [4, 3.4]
}]
Step 2:
Assign this as plot options for highcharts:
plotOptions: {
column: {
pointPadding: 0,
borderWidth: 0,
groupPadding: 0,
shadow: false
}
}
Step 3:
Now let's get creative - to have the same starting point for all bars, we need to move every single one to the graph's start:
setColumnsToZero() {
this.data.map((item, index) => {
document.querySelector('.highcharts-series-' + index).children[0].setAttribute('x', '0');
});
}
Step 4:
getDistribution() {
let total = 0;
// Array including all of the bar's data: [[5, 3.4], [3, 6], [4, 3.4]]
this.data.map(item => {
total = total + item[0];
});
// MARK: Get xAxis' total width
const totalWidth = document.querySelector('.highcharts-axis-line').getBoundingClientRect().width;
let pos = 0;
this.data.map((item, index) => {
const start = item[0];
const width = (start * totalWidth) / total;
document.querySelector('.highcharts-series-' + index).children[0].setAttribute('width', width.toString());
document.querySelector('.highcharts-series-' + index).children[0].setAttribute('x', pos.toString());
pos = pos + width;
this.getPointsPosition(index, totalWidth, total);
});
}
Step 4:
Let's get to the xAxis' points. In the first functions modify the already existing points, move the last point to the end of the axis and hide the others. In the second function we clone the last point, modify it to have either 6 or 3 total xAxis points and move each of them to the correct position
getPointsPosition(index, totalWidth, total) {
const col = document.querySelector('.highcharts-series-' + index).children[0];
const point = (document.querySelector('.highcharts-xaxis-labels').children[index] as HTMLElement);
const difference = col.getBoundingClientRect().right - point.getBoundingClientRect().right;
const half = point.getBoundingClientRect().width / 2;
if (index === this.data.length - 1) {
this.cloneNode(point, difference, totalWidth, total);
} else {
point.style.display = 'none';
}
point.style.transform = 'translateX(' + (+difference + +half) + 'px)';
point.innerHTML = total.toString();
}
cloneNode(ref: HTMLElement, difference, totalWidth, total) {
const width = document.documentElement.getBoundingClientRect().width;
const q = total / (width > 1000 && ? 6 : 3);
const w = totalWidth / (width > 1000 ? 6 : 3);
let val = total;
let valW = 0;
for (let i = 0; i < (width > 1000 ? 6 : 3); i++) {
val = val - q;
valW = valW + w;
const clone = (ref.cloneNode(true) as HTMLElement);
document.querySelector('.highcharts-xaxis-labels').appendChild(clone);
const half = clone.getBoundingClientRect().width / 2;
clone.style.transform = 'translateX(' + (-valW + difference + half) + 'px)';
const inner = Math.round(val * 100) / 100;
clone.innerHTML = inner.toString();
}
}
In the end we have a graph looking something like this (not the data from this given example, but for [[20, 0.005], [30, 0.013333333333333334], [20, 0.01], [30, 0.005555555555555555], [20, 0.006666666666666666]] with the first value being the width and the second being the height):
There might be some modifications to do to 100% fit your case. F.e. I had to adjust the xAxis' points a specific starting and end point - I spared this part.

Categories