问题
My goal is to cluster two or more column stacks having corresponding goal lines.
What is the best way to get this chart?
My initial thought was to use a ComboChart
. I can only accomplish a single stack bar with multiple lines.
I was able to try a 'bar' type chart (below snippet) and accomplished two clustered bar stacks using two axis but cannot get lines. I also think this could be limiting in the future to only two stacks.
Any ideas?? Thanks as always!
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Year', 'Sales', 'Expenses', 'Goal Line 1', 'Profit', 'Bonus', 'Goal Line 2'],
['2014', 1000, 400, 400, 200, 50, 500],
['2015', 1170, 460, 400, 250, 2000, 500],
['2016', 660, 1120, 400, 300, 10, 500],
['2017', 1030, 540, 400, 350, 15, 500]
]);
var options = {
bars: 'vertical',
isStacked: true,
series: {
0: { targetAxisIndex: 1 },
1: { targetAxisIndex: 1 },
2: { targetAxisIndex: 1 }, //this should be a line
3: { targetAxisIndex: 2 },
4: { targetAxisIndex: 2 },
5: { targetAxisIndex: 2 } //this should be a line
},
vAxes: {
},
height: 400,
xcolors: ['#1b9e77', '#d95f02', '#7570b3']
};
var chart = new google.charts.Bar(document.getElementById('chart_div'));
chart.draw(data, google.charts.Bar.convertOptions(options));
}
<script type="text/javascript" src="https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1.1','packages':['bar']}]}"></script>
<div id="chart_div"></div>
<br/>
============UPDATE=============
So I've tried now a ColumnChart with two axis. I can see that I get two stacks now but they are one in front of the other. You can see that the red/orange stack sticks out from behind the blue/purple stack.
Is it possible to offset stack 1 to the left and stack 2 to the right? Then I think visually the chart could work for my application.
google.charts.load('current', {
'packages': ['corechart']
});
google.charts.setOnLoadCallback(drawVisualization);
function drawVisualization() {
// Some raw data (not necessarily accurate)
var data = google.visualization.arrayToDataTable([
['Month', 'Ax1 line', 'Stack1 1', 'Stack1 2', 'Ax2 line', 'Stack2 1', 'Stack2 2'],
['2004/05', 10, 110, 210, 710, 810, 910],
['2005/06', 20, 120, 220, 720, 820, 920],
['2006/07', 30, 130, 230, 730, 830, 930],
['2007/08', 40, 140, 240, 740, 840, 940],
['2008/09', 50, 150, 250, 750, 850, 950]
]);
var options = {
title: 'Monthly Coffee Production by Country',
vAxis: {
title: 'Cups'
},
hAxis: {
title: 'Month'
},
seriesType: 'bars',
series: {
0: { type: 'line', targetAxisIndex: 1},
1: { type: 'bars', targetAxisIndex: 1},
2: { type: 'bars', targetAxisIndex: 1},
3: { type: 'line', targetAxisIndex: 2},
4: { type: 'bars', targetAxisIndex: 2},
5: { type: 'bars', targetAxisIndex: 2}
},
isStacked: true
};
var chart = new google.visualization.ComboChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div" style="width: 900px; height: 500px;"></div>
回答1:
the problem here, material charts do not support combo charts (adding the line).
and classic charts do not support multiple stacks.
however, by adding a line series in between the columns series,
and moving one to another y-axis,
the stacks will become separated, though on top of one another.
on the chart's 'ready'
event, we can reduce the size of both stacks by half,
then move the second stack to the right.
the biggest problem here is on interactivity.
the chart will reset all the columns back to their original position on interactivity, such as hover.
as such, we must use a MutationObserver
to prevent the reset.
however, there is still a couple minor issues I haven't worked out.
when you hover the first stack, the tooltip appears above the original position.
and there is a highlight surrounding the original position.
here is what I have so far...
google.charts.load('current', {
packages: ['corechart']
}).then(function () {
var data = google.visualization.arrayToDataTable([
['Month', 'Ax1 line', 'Stack1 1', 'Stack1 2', 'Ax2 line', 'Stack2 1', 'Stack2 2'],
['2004/05', 10, 110, 210, 710, 810, 910],
['2005/06', 20, 120, 220, 720, 820, 920],
['2006/07', 30, 130, 230, 730, 830, 930],
['2007/08', 40, 140, 240, 740, 840, 940],
['2008/09', 50, 150, 250, 750, 850, 950]
]);
var options = {
title: 'Monthly Coffee Production by Country',
vAxis: {
title: 'Cups'
},
hAxis: {
title: 'Month'
},
seriesType: 'bars',
series: {
0: { type: 'line', targetAxisIndex: 0},
1: { type: 'bars', targetAxisIndex: 0},
2: { type: 'bars', targetAxisIndex: 0},
3: { type: 'line', targetAxisIndex: 1},
4: { type: 'bars', targetAxisIndex: 1},
5: { type: 'bars', targetAxisIndex: 1}
},
isStacked: true,
legend: {
maxLines: 2,
position: 'top'
},
tooltip: {
target: 'both'
}
};
var chart = new google.visualization.ComboChart(document.getElementById('chart_div'));
var chartLayout;
var chartBounds;
var legendColors = [];
var saveCoords = {};
google.visualization.events.addListener(chart, 'ready', function () {
// init chart elements
chartLayout = chart.getChartLayoutInterface();
chartBounds = chartLayout.getChartAreaBoundingBox();
var bars = chart.getContainer().getElementsByTagName('rect');
// find legend markers
Array.prototype.forEach.call(bars, function (bar) {
var color = bar.getAttribute('fill');
var xCoord = parseFloat(bar.getAttribute('x'));
var yCoord = parseFloat(bar.getAttribute('y'));
if ((xCoord >= chartBounds.left) && (yCoord < chartBounds.top) && (color !== '#ffffff')) {
legendColors.push(color);
}
});
// find bars
Array.prototype.forEach.call(bars, function (bar, index) {
var xCoord = parseFloat(bar.getAttribute('x'));
var yCoord = parseFloat(bar.getAttribute('y'));
if ((xCoord > chartBounds.left) && (yCoord > chartBounds.top)) {
var color = bar.getAttribute('fill');
var width = parseFloat(bar.getAttribute('width')) / 2;
bar.setAttribute('width', width);
saveCoords[xCoord + yCoord] = {
width: width,
x: xCoord
};
if (legendColors.indexOf(color) > 1) {
bar.setAttribute('x', xCoord + width);
saveCoords[xCoord + yCoord].x = xCoord + width;
saveCoords[xCoord + width + yCoord] = saveCoords[xCoord + yCoord];
}
}
});
var observer = new MutationObserver(function () {
// reset bars
var bars = chart.getContainer().getElementsByTagName('rect');
Array.prototype.forEach.call(bars, function (bar, index) {
var xCoord = parseFloat(bar.getAttribute('x'));
var yCoord = parseFloat(bar.getAttribute('y'));
if ((xCoord > chartBounds.left) && (yCoord > chartBounds.top)) {
var color = bar.getAttribute('fill');
if (saveCoords.hasOwnProperty(xCoord + yCoord)) {
bar.setAttribute('width', saveCoords[xCoord + yCoord].width);
bar.setAttribute('x', saveCoords[xCoord + yCoord].x);
}
}
});
});
observer.observe(chart.getContainer(), {
childList: true,
subtree: true
});
});
chart.draw(data, options);
window.addEventListener('resize', function () {
chart.draw(data, options);
});
});
#chart_div {
height: 400px;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
Note: I also found an issue once posted here. For now, the legend must be fully displayed on one line. To run the example here, you must run the snippet, then click Full page.
来源:https://stackoverflow.com/questions/62284910/google-visualization-cluster-multiple-column-stacks-having-goal-lines