Related
I have built a map based on a standard map provided by Highcharts. However, it has been customized to show two sets of data on the tooltip instead of one and also so that when one clicks on a state they are linked to another page with information for that state. The problem I am facing is I need to change the name of one of the states. I have the code to do that, but since I've already customized the map, I'm having a hard time getting everything to work together. Basically when I add the code for the name change, it removes one of sets of data in the tooltip for all the states. Anyone able to help me out with this (the great #ppotaczek perhaps :) ? Specifically, I need to change Distrito Federal to CDMX in the tooltip while not removing the the two data sets from the tooltip.
Here's a jsfiddle: https://jsfiddle.net/sstoker/3cdaqkyx/6/#save
Here's the relevant javascript code:
// Prepare demo data
// Data is joined to map using value of 'hc-key' property by default.
// See API docs for 'joinBy' for more info on linking data and map.
const data = [
['mx-3622', 0.00],
['mx-bc', 5.59],
['mx-bs', 4.05],
['mx-so', 4.77],
['mx-cl', 6.91],
['mx-na', 8.88],
['mx-cm', 8.01],
['mx-qr', 4.87],
['mx-mx', 5.01],
['mx-mo', 0.089],
['mx-df', 8.12],
['mx-qt', 7.32],
['mx-tb', 3.17],
['mx-cs', 1.15],
['mx-nl', 6.88],
['mx-si', 6.64],
['mx-ch', 2.19],
['mx-ve', 0.66],
['mx-za', 8.03],
['mx-ag', 10],
['mx-ja', 3.35],
['mx-mi', 3.91],
['mx-oa', 0.8],
['mx-pu', 1.53],
['mx-gr', 0.0],
['mx-tl', 2.95],
['mx-tm', 5.47],
['mx-co', 9.46],
['mx-yu', 8.62],
['mx-dg', 4.47],
['mx-gj', 8.33],
['mx-sl', 4.35],
['mx-hg', 4.75]
];
const data1 = [
['mx-3622', 0.0],
['mx-bc', 13],
['mx-bs', 21],
['mx-so', 17],
['mx-cl', 10],
['mx-na', 3],
['mx-cm', 8],
['mx-qr', 16],
['mx-mx', 15],
['mx-mo', 31],
['mx-df', 6],
['mx-qt', 9],
['mx-tb', 24],
['mx-cs', 28],
['mx-nl', 11],
['mx-si', 12],
['mx-ch', 26],
['mx-ve', 30],
['mx-za', 7],
['mx-ag', 1],
['mx-ja', 23],
['mx-mi', 22],
['mx-oa', 29],
['mx-pu', 27],
['mx-gr', 32],
['mx-tl', 25],
['mx-tm', 14],
['mx-co', 2],
['mx-yu', 4],
['mx-dg', 19],
['mx-gj', 5],
['mx-sl', 20],
['mx-hg', 18]
];
data.forEach(function(el, i) {
el.push(data1[i][1])
});
// Create the chart
var chart = Highcharts.mapChart('container', {
chart: {
backgroundColor: '#f3f7fa',
map: 'countries/mx/mx-all',
},
title: {
text: ''
},
subtitle: {
text: ''
},
legend: {
title: {
text:'<span style="font-size:9.5px"> \< Bajo | Desarrollo democrático | Alto \> </span>',
}
},
plotOptions: { //For point links
map: {
point: {
events: {
click: function() {
$('#map1').trigger(this['hc-key']);
}
}
}
}
},
exporting: {
buttons: {
contextButton: {
align: 'center',
x: 0
}
},
chartOptions: {
chart: {
events: {
load: function() {
this.renderer.image('http://165.22.82.145/wp-content/uploads/2019/09/IDDMEX-Logo.svg',
480, // x
335, // y
75, // width
40, // height
).add();
}
}
}
}
},
mapNavigation: {
enabled: false,
buttonOptions: {
verticalAlign: 'bottom'
}
},
colorAxis: {
min: 0,
maxColor: 'blue',
stops: [
[.0,'#6497b1'],
[.249,'#6497b1'],
[.25,'#005b96'],
[.499,'#005b96'],
[.5,' #03396c'],
[.749,'#03396c'],
[.75,'#011f4b']
]
},
series: [{
keys: ['hc-key', 'value', 'rank'],
data: data,
name: 'Índice 2018',
states: {
hover: {
color: '#f3bbd3'
},
},
dataLabels: {
enabled: false,
format: '{point.name}',
}
}],
tooltip: {
pointFormat: ' {point.name} {point.value} <br>Ranking: {point.rank}'
},
});
});
and here is some javascript code that changes the name, but that is removing the second data series when I add it.
tooltip: {
pointFormatter: function() {
if (this['hc-key'] === 'mx-df') {
return 'CDMX: ' + this.value;
}
return this.name + ' ' + this.value;
}
}
Here's a solution to this problem. Add this code to the tooltip object.
pointFormatter: function() {
var firstRow = (this['hc-key'] === 'mx-df')
? "CDMX"
: this.name;
firstRow = firstRow + " " + this.value;
var secondRow = "Ranking: " + this.rank;
return (firstRow + "<br />" + secondRow);
}
I'm using C3.js library to create a stacked bar chart (my current code is in jsfiddle on the bottom). The problem is that by default the columns are... stacked. Since I need to create columns with min, average and max values, I'd like the values to rather contain each other, not stack. E.g. if I have min = 10, average = 50 and max = 100, I'd like the bar to be of the height 100, not 160. Is there any built in way to support such behavior?
My current code:
<div id="chart"></div>
<script>
var chart = c3.generate({
bindTo: '#chart',
data: {
columns: [
['min', 10, 25, 15],
['avg', 50, 33, 51],
['max', 100, 75, 200]
],
type: 'bar',
groups: [
['min', 'avg', 'max']
]
}
});
</script>
Here is a jsfiddle with my code:
https://jsfiddle.net/gguej6n0/
Right, I'm adding this as another answer as it's completely different plus if I changed my original answer the comments would make no sense..
This time I'm taking advantage of err... feature (bug?) found in c3 that caused another user to unintentionally get the effect that you wanted.
c3.js generate a stacked bar from JSON payload
Basically, if you supply the data as json you can get the effect you want if you supply each datum as a separate point
e.g. doing this will overplot min and max on the same column even if they are meant to be stacked
[{ "x-axis": "0",
"min": 30
},
{ "x-axis": "0",
"max": 40
}],
Whereas, this way will stack them:
[{ "x-axis": "0",
"min": 30,
"max": 40
}],
So what we need is a routine to turn the original column-based data into a json object where every datum is parcelled up separately:
http://jsfiddle.net/gvn3y0q6/5/
var data = [
['min', 10, 25, 15, 12],
['avg', 50, 33, 51, 24],
['max', 100, 75, 200, 76]
];
var json = [];
data.forEach (function (datum) {
var series = datum[0];
for (var i = 1; i < datum.length; i++) {
var jdatum = {"x-axis": i-1};
jdatum[series] = datum[i];
json.push (jdatum);
}
});
var chart = c3.generate({
data: {
x: "x-axis",
json:json,
keys: {
x: "x-axis",
value: ["max", "avg", "min"]
},
groups: [
['max', 'avg', 'min']
],
type: 'bar',
},
bar: {
width: {
ratio: 0.95
}
},
axis: {
x: {
padding: {
left: 0.5,
right: 0.5,
}
}
},
tooltip: {
grouped: true,
contents: function (d, defaultTitleFormat, defaultValueFormat, color) {
var data = this.api.data.shown().map (function(series) {
var matchArr = series.values.filter (function (datum) {
return datum.value != undefined && datum.x === d[0].x;
});
matchArr[0].name = series.id;
return matchArr[0];
});
return this.getTooltipContent(data, defaultTitleFormat, defaultValueFormat, color);
}
}
});
This time, hiding a series doesn't affect the other series. There's still some tooltip jiggery-pokery needed though otherwise we only get one of the values reported in the tooltip when hovering over one of the 'stacks'. It looks like the data includes a lot of empty value points, which leads me to think this is a bug of some sort I'm taking advantage of here..
So bearing in mind this might get fixed at some point in the future (or maybe left in, if someone points out it's useful for doing this sort of plot) - then this seems to do what you want
I'm not aware of anything built-in that will make the bars merge into one another as you describe, but could a side-by-side view help? If so, remove the grouping.
See https://jsfiddle.net/gguej6n0/1/
var chart = c3.generate({
bindTo: '#chart',
data: {
columns: [
['min', 10, 25, 15],
['avg', 50, 33, 51],
['max', 100, 75, 200]
],
type: 'bar'
},
bar: {
width: {
ratio: 0.3
}
}
});
I don't think c3 has an 'overplot' bars option, but you could massage your data...
Basically process the data beforehand so max is actually max - avg, and avg is actually avg - min
Then the tooltip routine restores the correct totals for showing to a user
Just be careful if you pass the data onto anything else and remember the data has been changed and restore it (or keep a copy)
https://jsfiddle.net/gguej6n0/5/
var data = [
['min', 10, 25, 15],
['avg', 50, 33, 51],
['max', 100, 75, 200]
];
for (var n = data.length-1; n > 0; n--) {
for (var m = 1; m < data[n].length; m++) {
data[n][m] -= data[n-1][m];
}
}
var chart = c3.generate({
bindTo: '#chart',
data: {
columns: data,
type: 'bar',
groups: [
['min', 'avg', 'max']
]
},
tooltip: {
contents: function (d, defaultTitleFormat, defaultValueFormat, color) {
var dc = d.map (function (dd) {
return {value: dd.value, x: dd.x, id: dd.id, name: dd.name, index: dd.index};
})
for (var n= 1; n < dc.length; n++) {
dc[n].value += dc[n-1].value;
}
return this.getTooltipContent(dc, defaultTitleFormat, defaultValueFormat, color);
}
}
});
I've been trying to display somewhat complex data on my webpage and chose chart.js to do so.
Therefor I need to group multiple stacked bars horizontally.
I already found this fiddle for "normal" bars but couldn't quite change it to work with horizontalBar yet.
Stackoverflow question: Chart.js stacked and grouped bar chart
The original Fiddle (http://jsfiddle.net/2xjwoLq0/) has
Chart.defaults.groupableBar = Chart.helpers.clone(Chart.defaults.bar);
And I just replaced the .bar everywhere in the code with .horizontalBar (well knowing that this won't make the cut).
Chart.defaults.groupableBar = Chart.helpers.clone(Chart.defaults.horizontalBar);
Since that didn't quite work, I tried adding the second stacked modifier as suggested for horizontal bars here:
Horizontal stacked bar chart with chart.js and flipped the functions for X and Y calculation (calculateBarY/calculateBarX)
Which quite work either because the stacks won't get merged onto each other correctly.
http://jsfiddle.net/2xjwoLq0/3/
I would appreciate if anyone could help me out on this one.
Looking for something similar, I took a look on example you gave, and decide to write something.
Rather than trying to fix the code or reusing the 'groupableBar', I get Chart.js code from Chart.controllers.horizontalBar and rewrite some part in functions calculateBarY, calculateBarHeight.
Just reused the getBarCount function from your example.
Chart.defaults.groupableHBar = Chart.helpers.clone(Chart.defaults.horizontalBar);
Chart.controllers.groupableHBar = Chart.controllers.horizontalBar.extend({
calculateBarY: function(index, datasetIndex, ruler) {
var me = this;
var meta = me.getMeta();
var yScale = me.getScaleForId(meta.yAxisID);
var barIndex = me.getBarIndex(datasetIndex);
var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0;
var stackIndex = this.getMeta().stackIndex;
if (yScale.options.stacked) {
if(ruler.datasetCount>1) {
var spBar=ruler.categorySpacing/ruler.datasetCount;
var h=me.calculateBarHeight(ruler);
return topTick + (((ruler.categoryHeight - h) / 2)+ruler.categorySpacing-spBar/2)+(h+spBar)*stackIndex;
}
return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing;
}
return topTick +
(ruler.barHeight / 2) +
ruler.categorySpacing +
(ruler.barHeight * barIndex) +
(ruler.barSpacing / 2) +
(ruler.barSpacing * barIndex);
},
calculateBarHeight: function(ruler) {
var returned=0;
var me = this;
var yScale = me.getScaleForId(me.getMeta().yAxisID);
if (yScale.options.barThickness) {
returned = yScale.options.barThickness;
}
else {
returned= yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight;
}
if(ruler.datasetCount>1) {
returned=returned/ruler.datasetCount;
}
return returned;
},
getBarCount: function () {
var stacks = [];
// put the stack index in the dataset meta
Chart.helpers.each(this.chart.data.datasets, function (dataset, datasetIndex) {
var meta = this.chart.getDatasetMeta(datasetIndex);
if (meta.bar && this.chart.isDatasetVisible(datasetIndex)) {
var stackIndex = stacks.indexOf(dataset.stack);
if (stackIndex === -1) {
stackIndex = stacks.length;
stacks.push(dataset.stack);
}
meta.stackIndex = stackIndex;
}
}, this);
this.getMeta().stacks = stacks;
return stacks.length;
}
});
var data = {
labels: ["January", "February", "March"],
datasets: [
{
label: "Dogs",
backgroundColor: "rgba(255,0,0,0.2)",
data: [20, 10, 25],
stack: 1,
xAxisID: 'x-axis-0',
yAxisID: 'y-axis-0'
},
{
label: "Cats",
backgroundColor: "rgba(255,255,0,0.2)",
data: [70, 85, 65],
stack: 1,
xAxisID: 'x-axis-0',
yAxisID: 'y-axis-0'
},
{
label: "Birds",
backgroundColor: "rgba(0,255,255,0.2)",
data: [10, 5, 10],
stack: 1,
xAxisID: 'x-axis-0',
yAxisID: 'y-axis-0'
},
{
label: ":-)",
backgroundColor: "rgba(0,255,0,0.2)",
data: [20, 10, 30],
stack: 2,
xAxisID: 'x-axis-1',
yAxisID: 'y-axis-0'
},
{
label: ":-|",
backgroundColor: "rgba(0,0,255,0.2)",
data: [40, 50, 20],
stack: 2,
xAxisID: 'x-axis-1',
yAxisID: 'y-axis-0'
},
{
label: ":-(",
backgroundColor: "rgba(0,0,0,0.2)",
data: [60, 20, 20],
stack: 2,
xAxisID: 'x-axis-1',
yAxisID: 'y-axis-0'
},
]
};
var ctx = document.getElementById("myChart").getContext("2d");
new Chart(ctx, {
type: 'groupableHBar',
data: data,
options: {
scales: {
yAxes: [{
stacked: true,
type: 'category',
id: 'y-axis-0'
}],
xAxes: [{
stacked: true,
type: 'linear',
ticks: {
beginAtZero:true
},
gridLines: {
display: false,
drawTicks: true,
},
id: 'x-axis-0'
},
{
stacked: true,
position: 'top',
type: 'linear',
ticks: {
beginAtZero:true
},
id: 'x-axis-1',
gridLines: {
display: true,
drawTicks: true,
},
display: false
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"></script>
<canvas id="myChart"></canvas>
Also put example on jsfiddle here: https://jsfiddle.net/b7gnron7/4/
Code is not strongly tested, you might found some bugs especially if you try to display only one stacked group (use horizontalBar instead in this case).
Your post is a little bit old... not sure that you still need a solution, but it could be useful for others ^_^
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.
I am hoping someone out there can tell me if what I am trying to do is even possible with the FLOT Javascript library. I am trying to show a chart (below) with dual axis and three data sets. One data set is on the left axis and two data sets on the right axis. What I really want to be able to do is stack the two data sets on the right axis since they should show cumulatively. Thus far I have been unable to get this chart to respond to the stack: true setting at all.
If anyone could help me out with it I would GREATLY appreciate it. My code and a snapshot of the chart currently. I am trying to stack the blue and green areas which correspond to the right axis (y2).
$(function () {
var previousPoint;
var completes = [[1346954400000, 5], [1346997600000, 5], [1347040800000, 7], [1347084000000, 9], [1347127200000, 12], [1347170400000, 15], [1347213600000, 16], [1347256800000, 20], [1347300000000, 20], [1347343200000, 20], [1347386400000, 25]];
var holds = [[1346954400000, 2], [1346997600000, 2], [1347040800000, 6], [1347084000000, 12], [1347127200000, 12], [1347170400000, 15], [1347213600000, 24], [1347256800000, 24], [1347300000000, 24], [1347343200000, 24], [1347386400000, 25]];
var screeners = [[1346954400000, 10298], [1346997600000, 7624], [1347040800000, 5499], [1347084000000, 2100], [1347127200000, 8075], [1347170400000, 4298], [1347213600000, 1134], [1347256800000, 507], [1347300000000, 0], [1347343200000, 800], [1347386400000, 120]];
var ds = new Array();
ds.push({
data:completes,
label: "Complete",
yaxis: 2,
lines: {
show: true,
fill: true,
order: 2,
}
});
ds.push({
data:screeners,
label: "Pre-Screened",
yaxis: 1,
lines: {
show: true,
fill: true,
order: 1,
}
});
ds.push({
data:holds,
label: "Holds",
yaxis: 2,
lines: {
show: true,
fill: true,
order: 3,
}
});
//tooltip function
function showTooltip(x, y, contents, areAbsoluteXY) {
var rootElt = 'body';
$('<div id="tooltip2" class="tooltip">' + contents + '</div>').css( {
position: 'absolute',
display: 'none',
top: y - 35,
left: x - 5,
border: '1px solid #000',
padding: '1px 5px',
'z-index': '9999',
'background-color': '#202020',
'color': '#fff',
'font-size': '11px',
opacity: 0.8
}).prependTo(rootElt).show();
}
//Display graph
$.plot($("#placeholder1"), ds, {
grid:{
hoverable:true
},
xaxes: [ { mode: 'time', twelveHourClock: true, timeformat: "%m/%d %H:%M" } ],
yaxes: [ { min: 0,
tickFormatter: function numberWithCommas(x)
{
return x.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ",");
},
}
],
y2axis: [ ],
legend: { show: true }
});
});
This is very straightforward. The stacking plugin isn't well documented, but in the source code, you can see there are two ways to specify that you want stacking turned on.
Two or more series are stacked when their "stack" attribute is set to
the same key (which can be any number or string or just "true"). To
specify the default stack, you can set
series: {
stack: null or true or key (number/string) }
or specify it for a specific series
$.plot($("#placeholder"), [{ data: [ ... ], stack: true }])
In this case, we want to specify it within the two series objects that we want stacked, which would look like this:
ds.push({
data:completes,
label: "Complete",
yaxis: 2,
stack: true, //added
lines: {
show: true,
fill: true,
order: 2,
}
});
ds.push({
data:screeners,
label: "Pre-Screened",
yaxis: 1,
lines: {
show: true,
fill: true,
order: 1,
}
});
ds.push({
data:holds,
label: "Holds",
yaxis: 2,
stack: true, //added
lines: {
show: true,
fill: true,
order: 3,
}
});
Add those two stack:true bits and include the stack plugin into your javascript sources and that will do it. See it in action here: http://jsfiddle.net/ryleyb/zNXBd/