Apexcharts max width on item bars - javascript

How can I change the maximum width/height an item bar has in Apexcharts? (https://apexcharts.com/)
I have an horizontal bar chart whose data are loaded dynamically and the number of users (items) varies. When there are few or only one user the bar looks very huge, I would like to set the height to max 50px.
The code:
var timeChart = {
formatter: function (value) {
var v = formatFromSeconds(value);
return v;
}
};
var desv_time = function (value) {
var v = formatFromSeconds(value);
if (value>0)return "+"+v;
else return v;
}
//CHART
var chart1 = new ApexCharts( document.querySelector("#chart-desv"),
{
chart: {
id: 'chart1',
type: 'bar'
},
plotOptions: {
bar: {
horizontal: true,
},
},
dataLabels: {
enabled: true,
formatter: desv_time,
style: {
colors: ["#000"]
}
},
series: [],
xaxis: {labels: {
formatter: desv_time,
}},
tooltip: {
followCursor: true,
x: {
formatter: function(value) {
return value;
},
},
y: {
formatter: desv_time,
title: {formatter: (seriesName) => 'Time difference',}
},
marker: {show:false}
},
title: { text: "Usual workday duration: 08:00", align: "center" }
});
chart1.render();
Screenshot (very huge bar with one user):

I'm using an approach similar to #Oscar Schafer's
I've found that, for my case, the width could be based on the length of my series.
The premises are:
When we have few items, e.g. 1 or 2, the chart width will be filled with only these columns. Then, if I have few items, the columnWidth should be proportionally smaller (close to 20% in my case)
When we have more items, they all share the chart width and fill it more smoothly. Then, if I have a lot of items, the column should be proportionally larger (close to 80%)
I've used the LOGISTIC-GROWTH MODEL as a function to calculate the bar width
Here is the code for to calculate the column width:
// f(x) = c / (1 + a*exp(-x*b)) -> LOGISTIC GROWTH MODEL
var optimalColumnWidthPercent = 20 + (60 / (1 + 30*Math.exp(-seriesLength /3)));
// 20: minimum width should be close to 20 (when only one item)
// 20+60: maximum width should be close 80
// 30 and 3: the a and b from the function, I've selected after testing some cenarios from seriesLength from 1 to 12 and finding which worked for me
...
plotOptions: {
bar: {
columnWidth: optimalColumnWidthPercent + "%"
},
},

For a horizontal bar chart, you can use the barHeight property to set the height of the bars. Unfortunately it can't be set as a pixel value but instead is "the percentage of the available height in the grid-rect" (from docs).
Effectively it works as a maximum height, so the bars will shrink if there's more data that needs to fit.
plotOptions: {
bar: {
barHeight: '70%'
}
}

Here's how I achieve this behaviour in VueJS.
Template part
<apexchart :options="barChartOptions" :series="series"></apexchart>
Script Part
computed() {
chartOptions() {
return {
chart: {
type: 'bar',
height: 100 + this.myData.length() * barWidth,
}
}
}
}
Where myData is an array of data that contains the information to be plotted and barWidth is the width of each bar that I want.
Note that this essentially fixes the width of each bar and then resizes the height of the chart as a linear function of the number of bars.

For a horizontal bar chart, you can use the columnWidth property to set the widthof the bars. Unfortunately it can't be set as a pixel value but instead is "the percentage of the available widthin the grid-rect" (from docs).
Effectively it works as a maximum width, so the bars will shrink if there's more data that needs to fit.
here,
plotOptions: {
bar: {
horizontal: false,
borderRadius: 10,
columnWidth: '45%',
},
},

Related

Highcharts Tick Positions are not aligned on multiple Y-axises

I have multiple charts on the same page, and the goal was to have same max values for each of the metrics on all charts to make comparison of charts easier ( https://www.screencast.com/t/vtF6h5ZRWS )
I've tried with max values and min values, but it doesn't works, probably because of multiple y-axises.
Now I'm trying to use tickpositions (which I'm calculating in backend) and passing it to chart. But here is the problem with tick alignment on opposite axes and it appears as shown https://www.screencast.com/t/iwnGOhJFb
Below is the small part of code how I set the tick positions and the fiddle of simpler version of chart that I have (I had more y axis's)
yAxis: [{ // Primary yAxis
labels: {
format: '{value}',
style: {
color: Highcharts.getOptions().colors[2]
}
},
title: {
text: 'Impressions',
style: {
color: Highcharts.getOptions().colors[2]
}
},
tickPositions: [0, 5500000, 11000000, 15000000]
}
http://jsfiddle.net/j82oen9c/8/
How can I achieve tick alignments on all y-axis's ?
In order to align the ticks on both axes, you can use two different approaches.
FIrst one is by define your own Axis.tickPositioner function which returns calculated array of tickcs adjusted to your needs.
Moving on, you need to get the same amount of ticks on both axes, and they should lay on the same positions on axes, so the positioner function should receive two arguments - maxValueOfCurrentAxis and tickAmount. I wrote the function like that:
var positioner = function(tAmount, axisMax) {
var positions = []
for (var i = 0; i < tAmount; i++) {
positions.push(Math.round(((axisMax / tAmount) * i) * 100) / 100)
}
positions.push(axisMax)
return function() {
return positions
}
}
and now, we need to assign this function with specific parameters as the Axis.tickPositioner, and define the amount of ticks and max values of both axes:
var tickAmount = 3
var firstAxisMax = 15000000
var secondAxisMax = 2
yAxis: [{ // Primary yAxis
labels: {
format: '{value}',
style: {
color: Highcharts.getOptions().colors[2]
}
},
title: {
text: 'Impressions',
style: {
color: Highcharts.getOptions().colors[2]
}
},
tickPositioner: positioner.apply(this, [tickAmount, firstAxisMax]),
}, { // Secondary yAxis
gridLineWidth: 0,
tickPositioner: positioner.apply(this, [tickAmount, secondAxisMax]),
title: {
text: 'eCPM',
style: {
color: Highcharts.getOptions().colors[0]
}
},
labels: {
format: '{value} $',
style: {
color: Highcharts.getOptions().colors[0]
}
},
opposite: true
}],
Live example: http://jsfiddle.net/vL324nqx/
The second way out of that issue, but not so much adjusted to your needs, is by using Axis.tickPixelInterval property on both axes. There is no need to explain it more precisely, because there are clear informations about that in API.
Live example: http://jsfiddle.net/mqget4hb/
API Reference:
https://api.highcharts.com/highcharts/yAxis.tickPositioner
https://api.highcharts.com/highcharts/yAxis.tickPixelInterval

c3 chart shows data when I want it to hide

I'm currently using c3 chart and what I would like to do is hide some data by default and then allow the user to view it they needed. (Perfect scenario would be to toggle the series one at a time only). But the main issue is I can't seem to have the chart render correctly with just one series. Here is an example. Any ideas why it showing a little of the other series? It should only show "c" (the green one)
var somethingWeLoadedFromTheServer = [
{a:23, b:45, c:12},
{a:34, b:19, c:38},
{a:25, b:62, c:56},
{a:44, b:88, c:74}
];
var chart = c3.generate({
data: {
json: somethingWeLoadedFromTheServer,
keys: {
value: ['a', 'b','c'],
//x: 'c'
},
type: 'bar',
hide: ['a','b']
},
bar: {
width: {
ratio: 0.5 // this makes bar width 50% of length between ticks
}
},
axis: {
//rotated: true
}
// axis: {
// y: {
// tick: {
// count: 3,
// format: d3.format('.2f')
// }
// }
// }
});
http://jsfiddle.net/Lbej86Lf/
Yeh, the json based bar charts can be a little buggy - I've actually used the effect you're seeing to help support 'non-stacked' stacked bar charts i.e. overlaying charts at the same place
This works though, hide the relevant series in the first onrendered call back - first being indicated by a flag. It should be possible to use oninit instead but doing that makes the bars 1px wide for some reason, grr (I see you filed a c3 bug, I'll add my findings onto that)
var firstRun = true;
var chart = c3.generate({
data: {
json: somethingWeLoadedFromTheServer,
keys: {
value: ['a', 'b','c'],
//x: 'c'
},
type: 'bar',
//hide: ['a','b']
},
bar: {
width: {
ratio: 0.5 // this makes bar width 50% of length between ticks
}
},
axis: {
//rotated: true
},
onrendered: function () {
if (firstRun) {
this.api.hide(['a', 'b']);
firstRun = false;
}
},
// axis: {
// y: {
// tick: {
// count: 3,
// format: d3.format('.2f')
// }
// }
// }
});
http://jsfiddle.net/Lbej86Lf/1/

How to set Google Chart height based on number of rows?

I have a Google stacked bar chart that pulls data from a database and draws charts based on said data. I was able to search around and find a way to dynamically set the height based on the number of rows - but for one of my search filters, the charts look way off.
The code is below and works for 4 out of 5 of my filters, but in the 5th filter the number of rows becomes much larger (around 40-50).
Code:
var paddingHeight = 40;
var rowHeight = data.getNumberOfRows() * 50;
var chartHeight = rowHeight + paddingHeight;
var options = {
titlePosition: 'none',
width: 1400,
height: chartHeight,
legend: { position: 'top', maxLines: 3 },
bar: { groupWidth: '50%' },
isStacked: true,
hAxis: {
title: 'Business Hours (excluding weekends & holidays)'
},
colors: ['#0066ff', '#33cc33', '#ffcc00', '#ff0000'],
annotations: {
alwaysOutside: true,
textStyle: {
color: '#000000'
}
}
}
Produces the following results. This first image is what 4 of my 5 filters looks like.
The second image is the one in question that shows I'm clearly doing something wrong.
What is the cause of this and how can I resolve it?
Edit: I suspect my issue is one of the following:
Either my data rows are not being counted correctly as I refresh the page with the latest search parameters or I have a problem with how my divs are set up and interacting with each other. I've tried adjusting the math behind the chartHeight and removing the padding and it seems to have the same effect either way. Am I on the right track or is there something else I'm missing?
need to set options for both height and chartArea.height
here, recommend using rowHeight for chartArea.height
var paddingHeight = 40;
var rowHeight = data.getNumberOfRows() * 50;
var chartHeight = rowHeight + paddingHeight;
var options = {
titlePosition: 'none',
width: 1400,
height: chartHeight,
chartArea: {
height: rowHeight,
},
legend: { position: 'top', maxLines: 3 },
bar: { groupWidth: '50%' },
isStacked: true,
hAxis: {
title: 'Business Hours (excluding weekends & holidays)'
},
colors: ['#0066ff', '#33cc33', '#ffcc00', '#ff0000'],
annotations: {
alwaysOutside: true,
textStyle: {
color: '#000000'
}
}
}
you'll want to leave some room for the legend, y-axis, etc...
chartArea has the following properties
top
left
height
width
you'll want to set top to 20 or something if legend is on top

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

Making two series point in opposite directions in highcharts

I have a column chart with two series, one of which I want to go down and the other up, like this:
However both of the series have positive y-values, which I can't change, e.g.
blue = [1746181, 1884428, 2089758, 2222362, 2537431, 2507081, 2443179,
2664537, 3556505, 3680231, 3143062, 2721122, 2229181, 2227768,
2176300, 1329968, 836804, 354784, 90569, 28367, 3878];
grey = [1656154, 1787564, 1981671, 2108575, 2403438, 2366003, 2301402, 2519874,
3360596, 3493473, 3050775, 2759560, 2304444, 2426504, 2568938, 1785638,
1447162, 1005011, 330870, 130632, 21208];
Using highcharts options, is it possible to have a chart like this? The example I used for the screenshot is this jsFiddle if it's useful to anyone, however it has a series with negative values which is not an option for me. Instead my data is more like this fiddle
I would try to use two separate yAxes: http://jsfiddle.net/zares7x9/2/, where one of them is reversed:
yAxis: [{
title: {
text: null
},
top: '5%',
height: '45%',
labels: {
formatter: function () {
return (Math.abs(this.value) / 1000000) + 'M';
}
},
min: 0,
max: 4000000
}, {
title: {
text: null
},
labels: {
formatter: function () {
return (Math.abs(this.value) / 1000000) + 'M';
}
},
offset: 0,
showFirstLabel: false, // hide 0-value
reversed: true, //reverse
top: '50%',
height: '45%',
min: 0,
max: 4000000
}],
Setting top and height allows you to render axes like one. Note, that you need to set for one of the series yAxis: 1, to inform Highcharts which series belongs to which axis.

Categories