Highcharts Plot Reciprocal Values For Column Height - javascript
I am trying to plot a categorical multi axis column chart of rankings. The number 1 ranking should be the tallest column and lowest ranking the shortest.
Essentially I would like the height of the bar to be it's reciprocal.
It is very close to:
var player_name_array = ["Aaron Rodgers", "Andrew Luck", "Drew Brees", "Russell Wilson", "Peyton Manning", "Ryan Tannehill", "Tony Romo", "Matt Ryan", "Cam Newton", "Ben Roethlisberger", "Eli Manning", "Philip Rivers", "Colin Kaepernick", "Teddy Bridgewater", "Marcus Mariota", "Matthew Stafford", "Robert Griffin III", "Joe Flacco", "Jay Cutler", "Sam Bradford"];
var series_array = [{"name":"espn_ranking","data":[38,33,63,64,67,95,75,85,96,76,999,999,999,999,999,999,999,999,999,999]}];
rankings_chart = new Highcharts.Chart({
chart: {
renderTo:'rankings_chart',
type: 'column'
},
title: {
text: 'Draft Rankings'
},
subtitle: {
text: 'Source: The Internet'
},
xAxis: {
categories: player_name_array,
crosshair: true
},
yAxis: {
type: 'logarithmic',
//reversed: true,
title: {
text: 'Draft Rankings'
}
},
tooltip: {
headerFormat: '<span style="font-size:14px"><b>{point.key}</b></span><table>',
pointFormat: '<tr><td style="color:{series.color};padding:0">{series.name}: </td>' +
'<td style="padding:0"><b>{point.y}</b></td></tr>',
footerFormat: '</table>',
shared: true,
useHTML: true
},
plotOptions: {
series: {
stacking:'normal',
},
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
rangeSelector: {
selected: 1
},
series: series_array
});
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="rankings_chart" ></div>
The problem with this is the columns come down from the top and ranking of 1 is still the smallest column.
Is there any way to add a function for the height of each column?
Set up your data to be the inverse of the player ranking:
var rankings = [38,33,63,64,67,95,75,85,96,76,999,999,999,999,999,999,999,999,999,999]
var inv_rankings = [];
for (i = 0; i < rankings.length; i++) {
inv_rankings[i] = 1 / rankings[i]
}
Set your Highcharts data to be your inverse ranking:
series: {
name: "espn_ranking",
data: inv_rankings
}
Use a formatter for the data labels and tooltip to return the reciprocal of the reciprocal (i.e. the original value):
plotOptions: {
series: {
dataLabels: {
enabled: true,
formatter: function() {
return 1 / this.y;
}
}
},
tooltip: {
pointFormatter: function() {
return 1 / this.y;
}
}
}
Working fiddle
This was an interesting and fun puzzle to work out!
I thought the answer by nagyben was an excellent one, and I based my code on their method of inversing the scores to come up with a relative rank.
Here's a working fiddle I created based on that concept, along with several enhancements, which I've shared below: https://jsfiddle.net/brightmatrix/j4ug40qm/
I added a sort function to rank the data in descending order, no matter how the data were arranged in the original two arrays. In order to do this, I first combined both arrays into a single Javascript object.
I changed the format to a column (vertical) chart to a bar (horizontal) chart. This will make the player names more readable for your users.
I updated the tooltip to show the player's rank whenever a user hovers their cursor over a particular bar.
I removed certain chart elements that aren't needed in this kind of chart, such as the legend, gridlines, and axis labels (since you're simply doing a rank, the true value of each bar isn't relevant to the user).
Here's the sort code that I built into this:
// combine both arrays into a single object so we can then sort them by rank
var rankArray = [];
$.each(player_name_array, function(index) {
tempArray = {};
tempArray['name'] = player_name_array[index];
tempArray['y'] = 1 / series_array[index];
rankArray.push(tempArray);
});
// sort the objects by rank (the "y" value) in descending order (due to the inverse)
// see accepted answer by Stobor at:
// http://stackoverflow.com/questions/979256/sorting-an-array-of-javascript-objects
rankArray.sort(function(a, b) {
return parseFloat(b.y) - parseFloat(a.y);
});
And here's the updated chart options:
rankings_chart = new Highcharts.Chart({
chart: {
renderTo:'rankings_chart',
type: 'bar' // chose a bar vs. column chart so the player names are easier to read
},
title: { text: 'Draft Rankings' },
subtitle: { text: 'Source: The Internet' },
legend: { enabled: false }, // there is no legend needed for this chart
xAxis: {
type: 'category',
tickmarkPlacement: 'on' // place the lines directly on the player's name
},
yAxis: {
// we're measuring by rank, so there's no need for labels, gridlines, or a title
labels: { enabled: false },
title: { enabled: false },
gridLineWidth: 0
},
tooltip: {
pointFormatter: function() {
// show the rank of the player, based on their sort order
return 'ranked #' + parseInt(this.x+1);
}
},
plotOptions: {
bar: {
groupPadding: 0.1,
pointPadding: 0.2,
borderWidth: 0
}
},
series : [{
name : 'Draft Data',
data : rankArray // our array will look like this: [{name: 'NAME', y: 0}, ...]
}]
});
Here's the result:
I hope this has been helpful to you in your chart conversion.
Since you seem to already have set an upper limit of 999 for your data, i would suggest simply plotting 1000-x.
One way to do that is to let highcharts do the math in a function for the series:
series : [{
name : 'Draft Data',
data : (function () {
// generate an array of random data
var data = [];
for (var x = 0;x <= series_array.length; x += 1) {
data.push([
1000-series_array[x];
]);
}
return data;
}())
}]
JSFiddle: http://jsfiddle.net/hmjnz/4/
In this case i would hide the yaxis label and instead put annotations on the columns with the rank.
Alternatively i'd debate whether a column chart is an adequate representation for this type of data.
Related
Highcharts.js question: is it possible to keep some part of chart in visible area always, even if "overscoll" option is set
I need to show some empty space to far right of the chart. To do so I use "overscroll" option (https://api.highcharts.com/highstock/xAxis.overscroll). But if user zoom in chart and pans chart to far right there can be empty space without any part of candlestick chart displayed (https://screencast-o-matic.com/watch/cqnfFq3CII). Please advise is it possible to implement following chart behaviour and how to do so: to keep some part of chart in visible area always, even if "overscoll" option is set and user pans chart to the far right? Thanks! Here is my code: var ohlc = JSON.parse(ohlcStringified), volume = JSON.parse(volumeStringified); var interval = ohlc[ohlc.length - 1].x - ohlc[ohlc.length - 2].x; var chart = Highcharts.stockChart('container', { chart: { borderWidth: 1, panning: true, }, title: { text: 'Chart' }, legend: { enabled: true }, rangeSelector: { selected: 1, enabled: false }, scrollbar: { enabled: false }, xAxis: { minPadding: 0.2, overscroll: 50 * interval, }, yAxis: [{ height: '40%' }, { top: '40%', height: '30%', offset: 0 }, { top: '70%', height: '30%', offset: 0 }], series: [{ type: 'candlestick', id: 'candlestick', name: 'AAPL', data: ohlc, tooltip: { valueDecimals: 2 }, dataGrouping: { enabled: false, } }, { type: 'column', id: 'volume', name: 'Volume', data: volume, yAxis: 1, dataGrouping: { enabled: false, } }] }); Here is live demo: http://jsfiddle.net/ogorobets/bfcs9gx7/2/
It's possible, however, it requires some custom logic. It can be achieved using xAxis.events.afterSetExtremes callback where you can check if the current axis minimum is greater than your limit (a value lower than maximum xData value). When it is true, set new axis extremes with your limit as a minimum value. Check the code and demo posted below. Code: xAxis: { minPadding: 0.2, overscroll: 50 * interval, events: { afterSetExtremes: function() { var chart = this.chart, xData = chart.series[0].xData, maxValue = xData[xData.length - 5], min = chart.xAxis[0].min, max = chart.xAxis[0].max if (min > maxValue) { chart.xAxis[0].setExtremes(maxValue, max, true, false); } } } } Demo: https://jsfiddle.net/BlackLabel/p6d73nk8/ API reference: https://api.highcharts.com/highcharts/xAxis.events.afterSetExtremes https://api.highcharts.com/class-reference/Highcharts.Axis#setExtremes
Dynamic update multible series Highcharts with single point XML file
I am a bit out of my comfort zone, since I normally do analytics and not fancy front-ends. However, I would like to have a real-time demo of some of my work, so it becomes easier to understand and not just numbers in a matrix. I have looked around and found something semi-relevant and come this far: (It has four series like I want to and it iterates - to some degree) https://jsfiddle.net/023sre9r/ var series1 = this.series[0], series2 = this.series[1], series3 = this.series[2], series4 = this.series[3]; But I am totally lost on how to remove the random number generators without loosing nice things like the number of data points in a view (seems to depend on the for loop?!). Remove the extra title "Values" right next to my real y-axis title. And of cause how to get a new data point from a XML-file every second. Ideally I want to have an XML-file containing 4 values, which I update approximately every 200ms in MATLAB. And every second I would like my 4 series chart to update. Is it not relatively easy, if you know what you are doing?! :-) Thanks in advance!
I simplified your example and added clear code showing how to fetch data from server and append it to your chart using series.addPoint method. Also if you want to use XML, just convert it to JS object / JSON. const randomData = () => [...Array(12)] .map((u, i) => [new Date().getTime() + i * 1000, Math.random()]) Highcharts.chart('container', { chart: { renderTo: 'container', type: 'spline', backgroundColor: null, animation: Highcharts.svg, // don't animate in old IE marginRight: 10, events: { load () { const chart = this setInterval(() => { // Fetch example below (working example: https://github.com/stpoa/live-btc-chart/blob/master/app.js) // window.fetch('https://api.cryptonator.com/api/ticker/btc-usd').then((response) => { // return response.json() // }).then((data) => { // chart.series[0].addPoint({ x: data.timestamp * 1000, y: Number(data.ticker.price) }) // }) chart.series.forEach((series) => series.addPoint([new Date().getTime(), Math.random()], true, true)) }, 3000) } } }, title: { text: null }, xAxis: { type: 'datetime', tickPixelInterval: 150 }, yAxis: [{ title: { text: 'Temperature [°C]', margin: 30 }, plotLines: [{ value: 0, width: 1, color: '#808080' }] }, { }], tooltip: { formatter: function() { return '<b>' + this.series.name + '</b><br/>' + Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' + Highcharts.numberFormat(this.y, 4); } }, legend: { enabled: true }, exporting: { enabled: false }, rangeSelector: { enabled: false }, navigator: { enabled: false }, scrollbar: { enabled: false }, series: [{ name: 'Setpoint', data: randomData() }, { name: 'Return', data: randomData() }, { name: 'Supply', data: randomData() }, { name: 'Output', data: randomData() }] }) Live example: https://jsfiddle.net/9gw4ttnt/ Working one with external data source: https://jsfiddle.net/111u7nxs/
Highcharts: how to define x-axis for dynamic chart
I am using Highcharts to automatically plot an array which is output from a sensor and is updated every second or so. The array is x elements long. This particular sensor measures the light intensity at x steps between some min and max wavelengths (that is, it measures the intensity at increments of (max-min)/x--in my case this increment is not an integer). I also have a static data set that is plotted on the same axes with the same x-axis requirements. I am able to successfully graph this data dynamically, but the x-axis scale is wrong. Instead of ranging from min to max, Highcharts defaults to a range of min to min+x. I'd like to change the x-axis increment so that the scaling is correct. Can a min, max, and number of data points or step be defined to generate the x-axis? Or is there a way to define the x- and y-axis values as individual arrays that are plotted against each other? Some other way? I have done lots of searching and experimenting but have come up short. The relevant snippet of my code is below. function showData(result) { // 'result' is an array that comes from the sensor var numbers = int(split(resultString, ",")); chart.series[1].setData(numbers); socket.send('a'); // send a byte to tell it to start sending new data loop++; //increase loop every time the server receives data chart.setTitle({ text: 'Spectrum, reading #' + loop }); } //showData $(function () { chart = new Highcharts.Chart({ chart: { renderTo: 'container', type: 'area', load: function() { chart = this; showData(); } }, title: { text: 'Spectrum, reading 0' }, xAxis: { title: { text: 'Wavelength [nm]', allowDecimals: false, }, }, yAxis: { title: { text: '' }, }, tooltip: { pointFormat: '{point.x}' }, plotOptions: { area: { pointStart: 340, marker: { enabled: false, symbol: 'circle', radius: 2, states: { hover: { enabled: true } } } } }, series: [{ name: '404 nm laser spectrum', data: [130,130,114,113,113,116,112,111,112,112,115,113,113,115, 112,114,113,113,114,115,113,114,113,114,115,115,117,119,124, 136,145,164,190,217,252,363,482,491,417,285,188,156,140,132, 127,122,117,118,117,115,116,115,116,118,116,116,117,116,117, 116,113,117,114,113,115,112,116,114,114,116,114,114,116,113, 116,115,114,115,115,114,115,115,115,116,114,115,116,114,118, 114,116,116,115,118,114,113,117,113,116,116,115,116,115,115, 115,114,117,116,117,118,120,118,122,119,128,127,130,134,136, 138,140,137,139,134,136,134,132,133,134,131,132,130,130,131, 128,128,131,129,131,131,134,136,134,140,139,137,143,140,138, 141,136,134,132,127,126,126,123,123,118,119,122,118,120,117, 116,118,116,118,116,115,117,116,115,116,115,115,116,114,119, 113,114,116,115,116,114,114,116,116,113,117,116,114,118,112, 115,114,113,116,115,114,115,113,116,114,114,116,115,115,114, 112,114,114,113,114,115,113,117,114,115,112,114,114,113,115, 114,114,115,113,112,115,112,113,115,112,116,113,113,115,116, 113,116,113,115,113,114,115,115,114,116,114,116,113,116,117, 113,115,116,115,117,115,114,117,113,115,118,114,116,115,115, 116,114,113,116,114,117,115,114,117,115,114,115,116,116,116, 117,117,114,0], color: '#36D39F' }, { name: 'Current measured spectrum', data: numbers, color: '#4A235A' }] }); }); EDIT: here's a demo showing how mine currently functions: https://jsfiddle.net/bgzgc1d9/2/. The x-axis should range from 340 to 850 with 288 data points evenly spaced on this interval
How to display several labels in Highchart.js?
I have basic Waterfall chart (HighChart.js) basic Waterfall chart image example How display two labels on each column? Besides, first label must be at top of column and another label must be on bottom of column. So it should be exactly like on image. Now I have two ideas: First label is rendered on first chart, second value will be rendered on additional hidden chart (link to example: [http://jsfiddle.net/gk14kh3q/1/][3]) $(function () { $('#container').highcharts({ chart: { type: 'waterfall' }, title: { text: 'Highcharts Waterfall' }, xAxis: { type: 'category' }, yAxis: { title: { text: 'USD' } }, legend: { enabled: false }, series: [{ dataLabels: { enabled: true, // here need align label } }, { dataLabels: { enabled: true, // here need align label } }] }); }); Combine two label in one by using formatting function and useHTML property. And after that set position to labels by using css or external js May be some others practices exist? I'll be very pleased for help. Even discussion can be usefull. Thank you! P.S. How I could insert icons to chart like on provided image?
You can use chart.renderer.label for adding new dataLabels for your column points. You can also use chart.renderer.image for adding arrows to your chart: var addLabels = function(chart) { $('.custom').remove(); var series = chart.series[0], url; Highcharts.each(series.data, function(p, i) { if (i) { chart.renderer.label(p.secondLabel, p.plotX + chart.plotLeft, p.yBottom + chart.plotTop).attr({ 'text-anchor': 'middle' }).addClass('custom').add(); if (p.y > 0) { url = 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Arrow_up.svg/1000px-Arrow_up.svg.png'; } else { url = 'https://upload.wikimedia.org/wikipedia/en/f/f1/Down_Arrow_Icon.png' } chart.renderer.image(url, p.plotX + chart.plotLeft, chart.plotTop + chart.plotHeight - 15, 8, 13).addClass('custom').add() } }); }; Here you can find an example how it can work: http://jsfiddle.net/zc2fb8vt/
Data and tooltip not working for graph using highcharts
I am trying to get a basic column chart working where I will trend for maphours(y-axis), reducehours(y-axis) for a particular job(tooltip) on a particular day(x-axis). I am populating the data array with y-axis values and jobname value which I want to show in the tooltip. The x-axis value is a datestamp(a daily value i.e one date throughout the x-axis). Problem - The graph container is getting initiated but it is not picking up the y-axis values and tooltip. The code where i am populating the arrays - var obj = data; var jobSummaryMappersPT = []; var jobSummaryReducersPT = []; var jobSummaryMappersDB = []; var jobSummaryReducersDB = []; var datestamp = []; for(i=0;i<obj.length-1;i++){ if(obj[i].grid=='PT'){ jobSummaryMappersPT.push({mappers: obj[i].maphours, jobname: obj[i].jobname}); // Filling of Array after Ajax Call jobSummaryReducersPT.push({reducers: obj[i].reducehours, jobname: obj[i].jobname}); //Filling of Array datestamp.push(obj[i].date.replace('T00:00:00.000Z', ' ')); }else{ jobSummaryMappersDB.push({mappers: obj[i].maphours, jobname: obj[i].jobname}); // Filling of Array after Ajax Call jobSummaryReducersDB.push({reducers: obj[i].reducehours, jobname: obj[i].jobname}); //Filling of Array } } job_summary_chart(jobSummaryMappersPT, jobSummaryReducersPT, jobSummaryMappersDB, jobSummaryReducersDB, datestamp); The function where I am initiating my Highcharts - function job_summary_chart(jobSummaryMappersPT, jobSummaryReducersPT, jobSummaryMappersDB, jobSummaryReducersDB, datestamp){ $(function () { $('#job_summary_pt').highcharts({ // Other Highchart Properties chart: { type: 'column' }, title: { text: 'Job Summary', x: -20 //center }, subtitle: { text: 'Source: DFTBA', x: -20 }, xAxis: { categories: datestamp, labels: { rotation: -45, y: 30, align: 'center', step: 4 } }, yAxis: { title: { text: 'GbHrs' }, plotLines: [{ value: 0, width: 1, color: '#808080' }] }, tooltip: { valueSuffix: ' GbHrs', formatter: function() { return ' ' + 'Jobname: ' + this.point.jobname + '<br />' } }, legend: { layout: 'vertical', align: 'right', verticalAlign: 'middle', borderWidth: 0 }, series: [{ data: jobSummaryMappersPT },{ data: jobSummaryReducersPT }] }); i did a console.log on jobSummaryMappersPT and the values are as follows: [Object { mappers=97, jobname="abcd.pig"}, Object { mappers=33, jobname="abcd.pig"}, Object { mappers=31, jobname="abcd.pig"}, Object { mappers=15, jobname="abcd.pig"}, Object { mappers=15, jobname="abcd.pig"}, Object { mappers=12, jobname="abcd.pig"}] I think the issue is the "=" sign after mappers and jobname. In the data property, it accepts colons = ":" and thats why the graph is not picking up values? How do i fix this to get the graph working? and get the tooltip to show the jobname?
If I understand correctly, your problem is not only with the tooltip, the chart could not even render with your data. You set mappers and reducers as the property names of Y value, but how can highcharts know those properties are where it should pick Y values? Changing properties mappers and reducers to y should make it work: jobSummaryMappersPT.push({y: obj[i].maphours, jobname: obj[i].jobname}); // Filling of Array after Ajax Call jobSummaryReducersPT.push({y: obj[i].reducehours, jobname: obj[i].jobname}); //Filling of Array The code for tooltip looks fine to me.