Related
I am working on project that requires me to design a chart as the following diagram:
I am using chart.js and react to make the distribution plot. But I am not able to figure out how to add fill color of the area chart between a particular range of values of the x-variable. So far, I am able to achieve this:
I have used the following code to make the area chart as a react component:
const data = {
labels: {DataLabel},
datasets: [
{
label: 'Blood Sugar Measure',
data: {Data},
fill: true,
backgroundColor: ["#E5E5E5", "#E5E5E5", "#B4EDB3", "#B4EDB3", "#E5E5E5"],
pointBorderColor: "#8884d8",
pointBorderWidth: 2,
pointRadius: 3,
tension: 0.4
},
],
};
const options = {
plugins: { legend: { display: false } },
layout: { padding: { bottom: 0.1 } },
scales: {
y: {
display : false,
beginAtZero: true,
grid: {
display: false
},
ticks: {
color: "#000000",
font: {
size: 18
}
}
},
x: {
beginAtZero: true,
grid: {
display: false
},
ticks: {
color: "#000000",
font: {
size: 10
},
min: 0
}
}
},
};
export const DistChart = () => {
return (<div className="App">
<Line data={data} options={options} />
</div>);
};
I would need some help to apply the conditional fill color based on the x-axis variable.
You can use multiple datasets with object data so you can specify starting and end points. After this you can manipulate the legend so it looks like its only a single dataset:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: 'Blood Sugar Measure extra',
data: [{
x: "Red",
y: 5
}, {
x: "Blue",
y: 8
}],
backgroundColor: 'lightGray',
fill: true
},
{
label: 'Blood Sugar Measure',
data: [{
x: "Blue",
y: 8
}, {
x: "Yellow",
y: 12
}, {
x: "Green",
y: 10
}],
backgroundColor: 'pink',
fill: true
},
{
label: 'Blood Sugar Measure extra',
data: [{
x: "Green",
y: 10
}, {
x: "Purple",
y: 12
}, {
x: "Orange",
y: 10
}],
backgroundColor: 'lightGray',
fill: true
}
]
},
options: {
plugins: {
legend: {
onClick: (e, legendItem, legend) => {
const ci = legend.chart;
const currentlyHidden = ci.getDatasetMeta(0).hidden;
for (let i = 0; i < ci.data.datasets.length; i++) {
ci.setDatasetVisibility(i, currentlyHidden)
}
ci.update();
},
labels: {
filter: (e) => (!e.text.includes('extra'))
}
}
}
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.js"></script>
</body>
I was able to make some progress on the design that I wanted, so thought of sharing my answer to benefit others. I was able to fill based on x-axis data values and get the following chart:
I had to use the segment property inside data configs to achieve this, with the help of a function. This is the modified code:
const highlightRegion = (ctx, value) => {
if (ctx.p0DataIndex > boundary_val1 && ctx.p0DataIndex < boundary_val2) {
return "#B4EDB3";
}
return "#E5E5E5";
};
const bgColor = ctx => highlightRegion(ctx, "#B4EDB3");
const data = {
labels: x_values,
datasets: [
{
label: 'Count',
data: [0, 20, 40, 80, 150, 80, 30, 0],
pointRadius: 0,
fill: true,
tension: 0.4,
segment: {
backgroundColor: bgColor,
borderColor: bgColor,
},
},
],
};
Special thanks goes to this youtube series from where I was able to find my answer: https://www.youtube.com/watch?v=st2O-pvhWM4.
I will keep this post open, in case if there is a better solution as I think my solution is not absolutely correct.
Is there any way to get the visible values on a chart after zooming or panning, using react-chartjs-2? I found a solution without the react version, but I'm not sure how to reference the "chart object" in react. I tried the solution as is, but it doesn't display the visible values, but rather keeps increasing the height of my charts (stretches downwards).
So what would the react solution look like?
Here's my attempt with the linked solution
options:
const chartStyle = {
options: {
animation: false,
maintainAspectRatio: true,
responsive: true,
plugins: {
zoom: {
zoom: {
wheel: {
enabled: true,
modifierKey: "shift",
},
pinch: {
enabled: true,
},
enabled: true,
drag: true,
mode: "x",
onZoomComplete: getVisibleValues,
},
pan: {
enabled: true,
mode: "x",
speed: 2,
onPanComplete: getVisibleValues,
},
mode: "xy",
},
legend: {
display: false,
},
},
scales: {
y: {
type: "linear",
display: "true",
position: "left",
grid: {
drawBorder: true,
color: "#000000",
},
ticks: {
beginAtZero: true,
color: "#000000",
},
},
x: {
max: 9,
grid: {
drawBorder: true,
color: "#00000",
},
ticks: {
beginAtZero: false,
color: "#000000",
},
},
},
},
};
function getVisibleValues({ chart }) {
let x = chart.scales["x-axis-0"];
document.getElementById("vv").innerText = JSON.stringify(
chart.data.datasets[0].data.slice(x.minIndex, x.maxIndex + 1)
);
}
export default chartStyle;
Here's my attempt at accessing the chart instance with useRef(), but ref.scales is undefined:
const SingleChart = ({ chosenDevice, variable }) => {
const [chartData, setChartData] = useState({
labels: [],
datasets: [
{
label: variable,
data: [],
fill: false,
backgroundColor: "rgb(0,0,0)",
borderColor: "rgba(0,0,0, 0.8)",
color: "rgb(0,0,0)",
pointRadius: 4,
yAxisID: "y",
},
],
});
const [apiData, setApiData] = useState({});
const processEnv = process.env;
// USEREF ATTEMPT
const ref = useRef();
console.log(ref.scales);
let x = ref.scales["x"];
console.log("min index", x.minIndex);
console.log("max index", x.maxIndex);
useEffect(() => {
ChartComp.register(zoomPlugin);
var chartDataUrl = processEnv.REACT_APP_CHART_API + chosenDevice;
async function updatePPK() {
if (chosenDevice !== "") {
const dataResponse = await fetch(chartDataUrl);
var dataBody = await dataResponse.json();
for (var key in dataBody) {
dataBody[key].PV_POWER =
dataBody[key].PV_CURR * dataBody[key].PV_CURR;
}
setApiData(dataBody);
}
}
updatePPK();
}, [chosenDevice]);
useEffect(() => {
var chartLabels = [];
var chartValues = [];
console.log(apiData);
console.log("keys", Object.keys(apiData) !== 0);
if (Object.keys(apiData).length !== 0) {
for (var key in apiData) {
if (variable == "GEN_ON") {
chartValues.push(apiData[key][variable] == "ON" ? 1 : 0);
} else {
chartValues.push(apiData[key][variable]); // for every element in the passed in chartData object from hookMqtt, add the variable the graph requests (this.variable) to an array of data to be displayed
}
chartLabels.push(apiData[key]["TIME_COMMITED"].substring(11, 19));
}
setChartData({
labels: chartLabels,
datasets: [
{
label: variable,
data: chartValues,
fill: false,
backgroundColor: "rgb(0,0,0)",
borderColor: "rgba(0,0,0, 0.8)",
color: "rgb(0,0,0)",
pointRadius: 4,
yAxisID: "y",
},
],
});
console.log(chartData);
}
}, [chosenDevice, apiData]);
return (
<div>
<Line
ref={ref}
data={chartData}
options={variable != "GEN_ON" ? chartStyles.options : genStyle.options}
width={20}
height={10}
/>
</div>
);
};
SingleChart.propTypes = {
chartData: PropTypes.any,
chosenDevice: PropTypes.any,
setDate: PropTypes.any,
variable: PropTypes.any,
state: PropTypes.any,
};
export default SingleChart;
I have create a custom series type to display the labels in Y Axis. When the label is large, then text is overlapping the main grid. I need to clip that overlapping text and also need to provide a scrollbar to display the remaining text. See the below image for better understanding of my question.
options = {
.......
grid: {
height: 250
},
yAxis: {
axisTick: { show: false },
splitLine: { show: false },
axisLine: { show: false },
axisLabel: { show: false },
min: 0,
max: this.labelData.length + 1,
},
series: [{
type: 'custom', // This is for rendering the main content (Gantt Chart)
renderItem: this.renderGanttItemClosure(),
itemStyle: {
opacity: 0.8
},
encode: {
x: [1, 2],
y: 0,
},
data: this.data
},
{
type: 'custom', // This is for rendering the label
renderItem: this.renderAxisLabelItem,
itemStyle: {
opacity: 0.8
},
encode: {
x: -1,
y: 0,
},
data: this.labelData,
}]
};
function renderAxisLabelItem(params: any, api: any) {
var categoryLevel = api.value(0);
var start = api.coord([0, categoryLevel]);
var barHeight = api.size([0, 1])[1] * 0.6;
var y = start[1];
if (y < params.coordSys.y + 5) {
return;
}
// M0,0 L0,150 L200,75 Z - Right arrow
// M0,0 L75,200 L150,0 Z - Down arrow
const labelItem = {
type: 'group',
position: [
10,
y
],
children: [] as any[]
};
var isExpanded = api.value(3);
const labelExpandIndicator = {
type: 'path',
shape: {
d: isExpanded ? 'M0,0 L75,200 L150,0 Z' : 'M0,0 L0,150 L200,75 Z',
x: 0,
y: -5,
width: 10,
height: 10,
layout: 'cover'
},
style: {
fill: '#000'
}
};
var hasChildren = api.value(2);
if (hasChildren) {
labelItem.children.push(labelExpandIndicator);
}
const hierarchyLevel = api.value(4);
const labelText = {
type: 'text',
style: {
x: 20 + (hierarchyLevel * 20),
y: 7,
text: api.value(1),
textVerticalAlign: 'bottom',
textAlign: 'left',
textFill: '#000'
}
};
labelItem.children.push(labelText);
return labelItem;
}
Demo code
Below is the full demo code. Just copy paste the below code in eCharts Editor
labelData = [{
name: "Application A to B",
value: [
2, // Category Level. Higher is on top.
"Application A to Application B", // Label Name
true, // Is there children
true, // Is expanded
0 // Hierarchy level
]
}, {
name: "Application B to Cosmos",
value: [
1,
"Application B to Cosmos",
false,
false,
1
]
}];
data = [{
name: "Application A to B",
value: [
2,
100,
1000,
1000
],
itemStyle: {
normal: {
color: '#7b9ce1'
}
}
}, {
name: "Application B processing",
value: [
1,
200,
700,
500
],
itemStyle: {
normal: {
color: '#bd6d6c'
}
}
}];
option = {
title: {
text: 'Dependency',
left: 'center'
},
tooltip: {
confine: true,
formatter: function (params) {
return params.marker + params.name + ': ' + params.value[3] + ' ms';
}
},
dataZoom: [{
type: 'slider',
filterMode: 'weakFilter',
showDataShadow: false,
top: 360,
labelFormatter: ''
}, {
type: 'inside',
filterMode: 'weakFilter'
},
{
type: 'slider',
zoomLock: true,
width: 10,
right: 10,
top: 70,
bottom: 20,
start: 95,
end: 100,
handleSize: 0,
showDetail: false,
}],
grid: {
height: 250
},
xAxis: {
min: 100,
scale: true,
axisLabel: {
formatter: function (val) {
return val + ' ms';
}
}
},
yAxis: {
axisTick: { show: false },
splitLine: { show: false },
axisLine: { show: false },
axisLabel: { show: false },
min: 0,
max: labelData.length + 1,
},
series: [{
type: 'custom',
renderItem: renderGanttItem,
itemStyle: {
opacity: 0.8
},
encode: {
x: [1, 2],
y: 0,
},
data: this.data,
zlevel: 10
},
{
type: 'custom',
renderItem: renderAxisLabelItem,
itemStyle: {
opacity: 0.8
},
encode: {
x: -1,
y: 0,
},
data: this.labelData,
zlevel: 5
}]
};
merge = {};
mergeData = {};
function renderAxisLabelItem(params, api) {
var categoryLevel = api.value(0);
var start = api.coord([0, categoryLevel]);
var barHeight = api.size([0, 1])[1] * 0.6;
var y = start[1];
if (y < params.coordSys.y + 5) {
return;
}
// M0,0 L0,150 L200,75 Z - Right arrow
// M0,0 L75,200 L150,0 Z - Down arrow
const labelItem = {
type: 'group',
position: [
10,
y
],
children: []
};
var isExpanded = api.value(3);
const labelExpandIndicator = {
type: 'path',
shape: {
d: isExpanded ? 'M0,0 L75,200 L150,0 Z' : 'M0,0 L0,150 L200,75 Z',
x: 0,
y: -5,
width: 10,
height: 10,
layout: 'cover'
},
style: {
fill: '#000'
}
};
var hasChildren = api.value(2);
if (hasChildren) {
labelItem.children.push(labelExpandIndicator);
}
const hierarchyLevel = api.value(4);
const labelText = {
type: 'text',
style: {
x: 20 + (hierarchyLevel * 20),
y: 7,
text: api.value(1),
textVerticalAlign: 'bottom',
textAlign: 'left',
textFill: '#000'
}
};
labelItem.children.push(labelText);
return labelItem;
}
function renderGanttItem(params, api) {
var categoryIndex = api.value(0);
var start = api.coord([api.value(1), categoryIndex]);
var end = api.coord([api.value(2), categoryIndex]);
var coordSys = params.coordSys;
var barLength = end[0] - start[0];
// Get the heigth corresponds to length 1 on y axis.
var barHeight = api.size([0, 1])[1] * 0.6;
var x = start[0];
var y = start[1] - barHeight / 2;
var rectNormal = echarts.graphic.clipRectByRect({
x: x, y: y, width: barLength, height: barHeight
}, {
x: params.coordSys.x,
y: params.coordSys.y,
width: params.coordSys.width,
height: params.coordSys.height
});
return {
type: 'group',
children: [{
type: 'rect',
shape: rectNormal,
style: api.style()
}]
};
}
I want to set intervals of negative and positive axes differently like my data in positive have values around 4000000 and in negative I have -2, -5 , -10 ..etc such values and they all are dynamic .
What's the best way to do that except Tick positioner? or with tick positioner?
Using Highcharts in Angular
You can use two y-axes and assign series to the appropriate one based on values.
yAxis: [{
height: '50%',
min: 0
}, {
top: '50%',
height: '50%',
offset: 0,
max: 0
}],
series: [{
data: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
}, {
data: [0, -1, 0, -2, -2, -3, -2, -5, 0, -2],
yAxis: 1,
}]
Live demo: http://jsfiddle.net/BlackLabel/0zsnpgou/
API Reference: https://api.highcharts.com/highcharts/yAxis
I'm using combination chart with four different data arrays in series and each one them is expected to have negative values
this is my chart
the code here
export const getAirlinesChartOption = (data) => {
let val;
console.log('data',data)
let tpSegments = data.map((x) => x.tpSegments);
let amadeusSegments = data.map((x) => x.amadeusSegments);
let sabreSegments = data.map((x) => x.sabreSegments);
let lytpSegments = data.map((x) => x.lytpSegments);
console.log('tpSegments',tpSegments)
console.log('amadeusSegments',amadeusSegments)
console.log('sabreSegments',sabreSegments)
console.log('lytpSegments',lytpSegments)
const allValues =[]
tpSegments.map((x,index)=>{
allValues.push(tpSegments[index])
allValues.push(amadeusSegments[index])
allValues.push(sabreSegments[index])
allValues.push(lytpSegments[index])
})
console.log('allValues',allValues)
const neg = allValues.filter(function (v) {
return v < 0;
}),
pos = allValues.filter(function (v) {
return v > 0;
});
let positiveCount = pos.length;
let negativeCount = neg.length;
let posMax = Math.max(...pos)
let negMax = Math.max(...neg)
console.log('pos',pos)
console.log('neg',neg)
console.log('posMax',posMax)
console.log('negMax',negMax)
let sortedPosArray = pos.sort(function(a, b) {
return a - b;
});
let sortedNegArray = neg.sort(function(a, b) {
return a - b;
});
let tickArray = sortedNegArray.concat(sortedPosArray)
console.log('sortedPosArray',sortedPosArray)
console.log('sortedNegArray',sortedNegArray)
console.log('tickArray',tickArray)
console.log('positiveCount',positiveCount)
console.log('negativeCount',negativeCount)
let obj: Highcharts.Options = {
credits: {
enabled: false,
},
chart: {
type: "column",
height: 180,
reflow: false,
},
title: {
text: null,
},
legend: {
padding: 0,
itemMarginTop: -15,
itemMarginBottom: -15,
itemHoverStyle: {
color: "#83858e",
},
itemStyle: {
fontSize: "10px",
color: "#83858e",
fontWeight: "light",
},
},
xAxis: {
categories: data.map(x=>x.airline),
labels: {
style: {
color: "#b6bbc0",
fontSize: "10px",
},
},
},
yAxis: {
gridLineDashStyle: "Dash",
labels: {
formatter: function () {
if (this.value >= 1000 || this.value <= -1000) {
val = Highcharts.numberFormat(this.value / 1000, 0) + "K"
return val;
}
else {
val = this.value
return val;
}
},
style: {
color: "#b6bbc0",
fontSize: "10px",
},
},
title: {
text: "",
},
// tickInterval:1000,
// tickPositions: tickArray,
min: negMax<0 && negMax !== -Infinity ?negMax:0,
max: posMax>0 && posMax !== -Infinity?posMax:0,
tickPositioner: function () {
var positions = [],
tick = Math.floor(this.min),
increment = Math.ceil((Math.abs(this.max) - Math.abs(this.min)) / 10);
console.log('increment',increment)
if (this.max !== null && this.min !== null) {
console.log('min',this.min);
for (tick; tick - increment <= this.max; tick += increment) {
positions.push(tick);
}
}
return positions;
}
},
plotOptions: {
series: {
events: {
legendItemClick: function (e) {
e.preventDefault();
},
},
},
},
tooltip: {
pointFormatter: function(){ return '' +
'<span style="color:' + this.color + '">' + this.series.name + '</span>: <b>' + this.y.toLocaleString() +'</b>'
},
//headerFormat: '<span style="font-size:11px">{category}</span><br>',
},
series: [
{
name: "TP",
type: "column",
color: "#01DFA5",
data: data.map(x=>Number(x.tpSegments)),
pointWidth: 5,
groupPadding:0.28,
borderRadius: 5,
},
{
name: "1S",
type: "column",
color: "#5858FA",
data:data.map(x=>Number(x.sabreSegments)),
pointWidth: 5,
groupPadding:0.28,
borderRadius: 5,
},
{
name: "1A",
type: "column",
color: "#11cdef",
data: data.map(x=>Number(x.amadeusSegments)),
pointWidth: 5,
groupPadding:0.28,
borderRadius: 5,
},
{
type: "line",
name: "LYTP",
grouping: false,
color: "#000000",
data: data.map(x=>Number(x.lytpSegments)),
borderRadius: 5,
pointRange:1,
marker: {
symbol: "triangle",
},
},
],
};
return obj;
};
is there any way to do a stacked and grouped bar chart with Chart.js library?
It should look something like this http://www.highcharts.com/demo/column-stacked-and-grouped
OK, I found the solution. It's described in this GitHub issue and solution is in this JSFiddle
Chart.defaults.groupableBar = Chart.helpers.clone(Chart.defaults.bar);
var helpers = Chart.helpers;
Chart.controllers.groupableBar = Chart.controllers.bar.extend({
calculateBarX: function (index, datasetIndex) {
// position the bars based on the stack index
var stackIndex = this.getMeta().stackIndex;
return Chart.controllers.bar.prototype.calculateBarX.apply(this, [index, stackIndex]);
},
hideOtherStacks: function (datasetIndex) {
var meta = this.getMeta();
var stackIndex = meta.stackIndex;
this.hiddens = [];
for (var i = 0; i < datasetIndex; i++) {
var dsMeta = this.chart.getDatasetMeta(i);
if (dsMeta.stackIndex !== stackIndex) {
this.hiddens.push(dsMeta.hidden);
dsMeta.hidden = true;
}
}
},
unhideOtherStacks: function (datasetIndex) {
var meta = this.getMeta();
var stackIndex = meta.stackIndex;
for (var i = 0; i < datasetIndex; i++) {
var dsMeta = this.chart.getDatasetMeta(i);
if (dsMeta.stackIndex !== stackIndex) {
dsMeta.hidden = this.hiddens.unshift();
}
}
},
calculateBarY: function (index, datasetIndex) {
this.hideOtherStacks(datasetIndex);
var barY = Chart.controllers.bar.prototype.calculateBarY.apply(this, [index, datasetIndex]);
this.unhideOtherStacks(datasetIndex);
return barY;
},
calculateBarBase: function (datasetIndex, index) {
this.hideOtherStacks(datasetIndex);
var barBase = Chart.controllers.bar.prototype.calculateBarBase.apply(this, [datasetIndex, index]);
this.unhideOtherStacks(datasetIndex);
return barBase;
},
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: "Apples",
backgroundColor: "rgba(99,255,132,0.2)",
data: [20, 10, 30],
stack: 1
},
{
label: "Bananas",
backgroundColor: "rgba(99,132,255,0.2)",
data: [40, 50, 20],
stack: 1
},
{
label: "Cookies",
backgroundColor: "rgba(255,99,132,0.2)",
data: [60, 20, 20],
stack: 1
},
{
label: "Apples",
backgroundColor: "rgba(99,255,132,0.2)",
data: [20, 10, 30],
stack: 2
},
{
label: "Bananas",
backgroundColor: "rgba(99,132,255,0.2)",
data: [40, 50, 20],
stack: 2
},
{
label: "Cookies",
backgroundColor: "rgba(255,99,132,0.2)",
data: [60, 20, 20],
stack: 2
},
{
label: "Apples",
backgroundColor: "rgba(99,255,132,0.2)",
data: [20, 10, 30],
stack: 3
},
{
label: "Bananas",
backgroundColor: "rgba(99,132,255,0.2)",
data: [40, 50, 20],
stack: 3
},
{
label: "Cookies",
backgroundColor: "rgba(255,99,132,0.2)",
data: [60, 20, 20],
stack: 3
},
]
};
var ctx = document.getElementById("myChart").getContext("2d");
new Chart(ctx, {
type: 'groupableBar',
data: data,
options: {
legend: {
labels: {
generateLabels: function(chart) {
return Chart.defaults.global.legend.labels.generateLabels.apply(this, [chart]).filter(function(item, i){
return i <= 2;
});
}
}
},
scales: {
yAxes: [{
ticks: {
max: 160,
},
stacked: true,
}]
}
}
});
You should use the stack property of the dataset object for each dataset.
As you can see in Chart.js Documentation, stack is defined as:
"The ID of the group to which this dataset belongs to (when stacked, each group will be a separate stack)"
I believe this functionality has been introduced recently and in 2016 Chart.js did not have this because of this post
You can acheive stacked bar chart with below data
this.dataStackedBarChart = {
type: 'horizontalBar',
labels: this.stackedBarChartLabel,
datasets: [
{
label: 'Success Count',
stack: 'Stack 0',
data: this.successCount,
backgroundColor: 'green'
},
{
label: 'FailureCount',
stack: 'Stack 0',
data: this.failureCount,
backgroundColor: 'red'
},
{
label: 'AvgDuration',
stack: 'Stack 1',
data: this.avgDuration,
backgroundColor: 'black'
},
{
label: 'MaxDuration',
stack: 'Stack 2',
data: this.maxDuration,
backgroundColor: 'orange'
},
{
label: 'MinDuration',
stack: 'Stack 3',
data: this.minDuration,
backgroundColor: 'pink'
}
],
borderWidth: 2
}
this.optionsStackedBarChart = {
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
var label = this.uniqueApiPath[tooltipItem.index];
return label;
}
}
},
scales: {
xAxes: [
{
stacked: true,
display: true,
ticks: {
beginAtZero: true,
min: 0,
suggestedMin: 0
}
}
],
yAxes: [
{
stacked: true,
display: true,
ticks: {
beginAtZero: true,
min: 0,
suggestedMin: 0
}
}
]
}