I've found numerous posts on how to gradient fill the area beneath the chart, but I'd like to do this:
Is that doable with ChartJS?
It is somehow doable. A simple approach presented below assumes one dataset only (it should be easy to extend the approach for handling more datasets, though). The idea is as follows. We will create a plugin that will override the beforeUpdate method (which is called at the start of every update). At the start of every update, the exact Y pixels of the min and max values of the dataset are calculated. A vertical linear gradient is then created from the context of the canvas using createLinearGradient, with a kind of red for the Y pixel that corresponds to the min value of the dataset and a jazzy kind of blue for the Y pixel that corresponds to the max value of the dataset. Look at the commented code for more information. There may be some glitches regarding hovering over points and legend coloring, which I am not very keen on looking into. A working fiddle is here and the code is also available below.
var gradientLinePlugin = {
// Called at start of update.
beforeUpdate: function(chartInstance) {
if (chartInstance.options.linearGradientLine) {
// The context, needed for the creation of the linear gradient.
var ctx = chartInstance.chart.ctx;
// The first (and, assuming, only) dataset.
var dataset = chartInstance.data.datasets[0];
// Calculate min and max values of the dataset.
var minValue = Number.MAX_VALUE;
var maxValue = Number.MIN_VALUE;
for (var i = 0; i < dataset.data.length; ++i) {
if (minValue > dataset.data[i])
minValue = dataset.data[i];
if (maxValue < dataset.data[i])
maxValue = dataset.data[i];
}
// Calculate Y pixels for min and max values.
var yAxis = chartInstance.scales['y-axis-0'];
var minValueYPixel = yAxis.getPixelForValue(minValue);
var maxValueYPixel = yAxis.getPixelForValue(maxValue);
// Create the gradient.
var gradient = ctx.createLinearGradient(0, minValueYPixel, 0, maxValueYPixel);
// A kind of red for min.
gradient.addColorStop(0, 'rgba(231, 18, 143, 1.0)');
// A kind of blue for max.
gradient.addColorStop(1, 'rgba(0, 173, 238, 1.0)');
// Assign the gradient to the dataset's border color.
dataset.borderColor = gradient;
// Uncomment this for some effects, especially together with commenting the `fill: false` option below.
// dataset.backgroundColor = gradient;
}
}
};
Chart.pluginService.register(gradientLinePlugin);
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["First", "Second", "Third", "Fourth", "Fifth"],
datasets: [{
label: 'My Sample Dataset',
data: [20, 30, 50, 10, 40],
// No curves.
tension: 0,
// No fill under the line.
fill: false
}],
},
options: {
// Option for coloring the line with a gradient.
linearGradientLine: true,
scales: {
yAxes: [{
ticks: {
min: 0,
max: 100,
stepSize: 20
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"></script>
<canvas id="myChart" width="400" height="200"></canvas>
There is also a pluginless method, mentioned here, but that method is lacking. According to that method, one would have to set the borderColor to a gradient that should have been created before the creation of the chart. The gradient is calculated statically and will never fit an arbitrary range or respond to resizing as is.
I have a google visualization bar chart sample here where the data format is as given below.
var data = google.visualization.arrayToDataTable([
['Year', 'Sales', 'Expenses', 'Profit'],
['2014', 1000, 400, 200],
['2015', 1170, 460, 250],
['2016', 660, 1120, 300],
['2017', 1030, 540, 350]
]);
How to make expenses and profits bar stacked where as sales is a separate bar?
At the moment the Google Charts API doesn't have this in built feature, but with some remodeling of the DataTable and Chart Options you can still achieve this.
My Solution
The Stacked Bar should contain values of only Expenses and Profit, to avoid a Stacked bar with Sales the value in the data column for Sales is represented as zero. A separate Sales bar is created by having a similar data row, but with the value of Sales present, and the rest zero. The Date data type needs to be specified in order to group all bar charts with the same date, if this isn't implemented, then there will be a gap between each bar chart with the same year.
More information on Date representation of Columns is available here.
var data = google.visualization.arrayToDataTable([
[ {label: 'Year', id: 'year', type: 'date'},
{label: 'Sales', id: 'Sales', type: 'number'},
{label: 'Expenses', id: 'Expenses', type: 'number'},
{label: 'Profit', id: 'Profit', type: 'number'}],
[{v:new Date('2014'), f:'2014'}, 0, 400, 200],
[{v:new Date('2014'), f:'2014'}, 1000, 0, 0],
[{v:new Date('2015'), f:'2015'}, 0, 460, 250],
[{v:new Date('2015'), f:'2015'}, 1170, 0, 0],
[{v:new Date('2016'), f:'2016'}, 0, 1120, 300],
[{v:new Date('2016'), f:'2016'}, 600, 0, 0],
[{v:new Date('2017'), f:'2017'}, 0, 540, 350],
[{v:new Date('2017'), f:'2017'}, 1030, 0, 0]
]);
To achieve the stacked bar, the google charts configuration option isStacked: true is used. To avoid the vertical axis acting like a timeline with months and days, the vertical axis is formatted to display the Year using vAxis: {format: 'yyyy'}. More information on formatting is available here. To avoid spacing between different Year Bars, the feature bar: {groupWidth: '90%'} is used.
var options = {
chart: {
title: 'Company Performance',
subtitle: 'Sales, Expenses, and Profit: 2014-2017',
},
bars: 'horizontal', // Required for Material Bar Charts.
hAxis: {format: 'decimal'},
vAxis: {
format: 'yyyy'
},
height: 400,
colors: ['#1b9e77', '#d95f02', '#7570b3'],
isStacked: true,
bar: {groupWidth: '90%'}
};
I am working with Google's Charting API and I have a problem where the graph will sometimes have 0 in the middle of the y axis and underneath show negative numbers.
I want to set the chart to be a minimum of 0 and found on Google that all I need to do is add vAxis:{viewWindow: {min: 0}}.
I'm drawing my chart like the below
function (result)
{
alert(result);
var obj = $.parseJSON(result);
var resultData = obj.DATA;
var data = new google.visualization.DataTable(resultData);
var options = {
title: "Crash counts for ",
pointSize: 6,
hAxis: {showTextEvery: 2, slantedText: true, slantedTextAngle: 30},
animation: {
duration: 1000,
easing: 'out'
}
vAxis:{viewWindow: {min: 0}}
};
var chart = new google.visualization.LineChart(document.getElementById("lineChart"));
chart.draw(data, options);
}
), "json";
}
Chrome says that the line vAxis has an error which is unexpected identifier.
Well, really simple mistake, you just forgot to add a , after animation :
var options = {
title: "Crash counts for ",
pointSize: 6,
hAxis: {showTextEvery: 2, slantedText: true, slantedTextAngle: 30},
animation: {
duration: 1000,
easing: 'out'
}, // here is the change
vAxis:{viewWindow: {min: 0}}
};
I'm trying to create a Google chart that looks like the following:
http://chart.googleapis.com/chart?cht=bvs&chs=200x125&chd=t2:10,50,60,80,40%7C50,60,100,40,20%7C30,70,90,95,45&chco=4d89f900,c6d9fd&chbh=20&chds=0,160&chm=H,336699,2,-1,1:22
Basically, I just want to represent the max, min, and average all on one chart, but I can't seem to figure out how to do this. I know it's possible using markers with the old URL-based charts, but they're being deprecated and it doesn't look like the new API supports markers yet.
I tried using candlesticks, but the only way I got it working was with a skinny line and a horizontal line in the middle, so it looked like a bunch of plus signs rather than floating columns with line markers. I know I could also technically stack a column chart with a stepped area chart, but then the line is continuous across all entries, which I don't want.
Thanks.
EDIT: Using jmac's method and intervals, I came up with this:
function drawVisualization() {
// Create and populate the data table.
var data = new google.visualization.DataTable();
data.addColumn('string', 'label');
data.addColumn('number', 'filler');
data.addColumn('number', 'range');
data.addColumn({type:'number', role:'interval'});
data.addRows([
['A', 3, 4, 2],
['B', 2, 5, 4],
['C', 4, 4, 1],
['D', 5, 2, 1],
['E', 1, 8, 4],
]);
// Create and draw the visualization.
var ac = new google.visualization.ColumnChart(document.getElementById('visualization'));
ac.draw(data, {
width: 600,
isStacked: true,
series: [{color:'transparent'},{color:'silver'},{color:'silver'}],
vAxis: {gridlines: {color: 'transparent'}, textPosition: 'none'},
focusTarget: 'category',
intervals: { 'style': 'bars', 'barWidth': 1.3, 'lineWidth': 2 },
});
}
I don't have enough reputation to post an image of what it looks like yet, but if you paste it in here you can see it: https://code.google.com/apis/ajax/playground/?type=visualization#column_chart
Also, since it still highlights the filler area when you mouse over it, I found a css hack to hide the highlighting on mouse over:
#chart-div {
svg g g g g rect {
stroke-width:0px;
}
}
You can use "box" style intervals to accomplish what you want:
function drawChart () {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Category');
data.addColumn('number', 'Min');
data.addColumn('number', 'Average');
data.addColumn('number', 'Max');
data.addRows([
['Foo', 3, 5, 7],
['Bar', 5, 8, 10],
['Baz', 0, 2, 6],
['Bat', 1, 2, 4]
]);
var view = new google.visualization.DataView(data);
// duplicate 1 column as a dummy data series, and add intervals to it
view.setColumns([0, 1, {
id: 'min',
type: 'number',
role: 'interval',
calc: function (dt, row) {
return dt.getValue(row, 1);
}
}, {
id: 'avg',
type: 'number',
role: 'interval',
calc: function (dt, row) {
return dt.getValue(row, 2);
}
}, {
id: 'max',
type: 'number',
role: 'interval',
calc: function (dt, row) {
return dt.getValue(row, 3);
}
}, 1, 2, 3]);
var chart = new google.visualization.LineChart(document.querySelector('#chart_div'));
chart.draw(view, {
height: 400,
width: 600,
lineWidth: 0,
intervals: {
style: 'boxes'
},
legend: {
position: 'none'
},
series: {
0: {
// dummy data series, controls color of intervals
visibleInLegend: false,
color: 'blue',
enableInteractivity: false
},
1: {
// min series options
},
2: {
// average series options
},
3: {
// max series options
}
}
});
}
google.load('visualization', '1', {packages:['corechart'], callback: drawChart});
See working example: http://jsfiddle.net/asgallant/pvJpx/
If all you care about is how it looks visually, you can recreate this with a bit of finagling to have it look like this:
This is the code:
function drawVisualization() {
// Create and populate the data table.
var data = google.visualization.arrayToDataTable([
['label', 'filler', 'bot half', 'top half'],
['A', 3, 2, 2],
['B', 2, 4, 1],
['C', 4, 1, 3],
['D', 5, 1, 1],
['E', 1, 4, 4],
]);
// Create and draw the visualization.
var ac = new google.visualization.ColumnChart(document.getElementById('visualization'));
ac.draw(data, {
width: 600,
isStacked: true,
series: [{color:'transparent'},{color:'silver'},{color:'silver'}],
vAxis: {gridlines: {color: 'transparent'}, textPosition: 'none'},
focusTarget: 'category',
});
}
This is a dumb workaround, but here are the steps given a min value, a max value, and an avg value:
Create a dummy (transparent) series equal to min
Create a second series for the bottom half of the bar equal to avg - min
Create a third series for the top half of the bar equal to max - avg
Although it looks right, the issue is that interaction with the chart will be real funky, in the sense that it won't show you what you would expect from the chart (you would have separate values that aren't showing min, max, and average, but only two values for the size of points 2) and 3) above). You can get around this with creative use of focusTarget, but that will still get you odd stuff like this:
Now you could theoretically rename your series, and use the {v:, f:} trick to make it look nicer, and that may be a good workaround, but it is very kludgy depending on your application. If you finagle it all nice and right, you would get something like this:
This is done with the following code:
function drawVisualization() {
// Create and populate the data table.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Series Name');
data.addColumn('number', 'Average');
data.addColumn('number', 'Minimum');
data.addColumn('number', 'Maximum');
data.addRows([
['A', {v:3, f:'5'}, {v:2, f:'3'}, {v:2, f:'7'}],
['B', {v:2, f:'6'}, {v:4, f:'2'}, {v:1, f:'7'}],
['C', {v:4, f:'5'}, {v:1, f:'4'}, {v:3, f:'8'}],
['D', {v:5, f:'6'}, {v:1, f:'5'}, {v:1, f:'8'}],
['E', {v:1, f:'5'}, {v:4, f:'1'}, {v:4, f:'9'}],
]);
// Create and draw the visualization.
var ac = new google.visualization.ColumnChart(document.getElementById('visualization'));
ac.draw(data, {
width: 600,
isStacked: true,
series: [{color:'transparent'},{color:'silver'},{color:'silver'}],
vAxis: {gridlines: {color: 'transparent'}, textPosition: 'none'},
focusTarget: 'category',
});
}
Again, this is kludgy and not perfect (see the grey box around the filler series, that can't be helped), but it will display the info, and it can be automated using some fancy javascript and/or formatters with dataviews depending on how often the charts need to be changed and what format you get your data in.