How do I move the x axis like a window in google chart while x increases - google-visualization

I would like to draw a chart to my html, the chart I am using is google chart. However, while my x value is increasing, the chart is getting bigger. But I just want a fixed size window which increase both minimum x value and maximum x value. Like sliding window.
The attachment below is my code. This is the js code which updates the gets the value and updates the chart
// load google charts library
google.load("visualization", "1", {packages:["corechart"]});
// for rest, walk, fast_walk data
var data, options, chart;
var xMin = 0;
var xMax = 10;
var i = 0;
/* initialize chart1 - rest, walk, fast_walk data */
function drawChart(data, options) {
var chart = new
google.visualization.LineChart(document.getElementById('data-container'));
chart.draw(data, options);
return(chart);
}
/* update the chart1 - rest, walk, fast_walk data */
function updateChart(percentage) {
i = (i + 1);
data.addRow([
""+i,
percentage
]);
if(xMax >= 9) {
xMin + 1;
}
xMax + 1;
chart.draw(data, options);
}
$(function() {
data = google.visualization.arrayToDataTable([
['Time', 'percentage'],
['0', 0],
]);
options = {
title: 'Energy data',
"curveType": "function",
vAxis: {
min: xMin,
max: xMax
}
};
chart = drawChart(data, options);
});
/* reset charts */
function reset(){
i = 0;
data = google.visualization.arrayToDataTable([
['Time', 'percentage'],
['0', 0],
]);
options = {
title: 'Energy data',
"curveType": "function",
hAxis: {
viewWindow: {
min: 0,
max: 10
}
}
};
chart = drawChart(data, options);
}
I am wondering if it can be designed into a sliding window, so that the x value doesn't stick with the minimum value 0. instead if we want to see the earliest value, we can just scroll left.

According to the documentation at [https://developers.google.com/chart/interactive/docs/gallery/linechart]
/* copied from site*/
var options = {
chart: {
title: 'Box Office Earnings in First Two Weeks of Opening',
subtitle: 'in millions of dollars (USD)'
},
width: 900, //<--- set fixed width like so
height: 500
};
To get a scrollable div,you can wrap your chart inside another div
<div class='h-scrollable' > <!---- chart code here ----> </div>
and for css
.h-scrollable {
width: 100%;
overflow: hidden; // only if you don't want y axis to be scrollable
overflow-x: auto;
}
Now, for the chart give a width depending on the number of x values you have. You could do a mathematical computation by taking the width of the h-scrollbale div in javascript and dividing it by number of x-points you want in a window and then multiplying it with total x values you have and setting it as chart width.
Update:
inititally get the width of h-scrollable as let viewWidth = document.querySelector(".h-scrollable").offsetWidth
[refer : How to find the width of a div using raw JavaScript?
Then if you want to show 10 x values in a view, divide viewWidth by 10 to get one xWidth. Now you can re-render the chart each time by setting width as no.of X values * xWidth so that it scrolls accordingly

Upon each update, you can just remove the first raw.

Related

How to show the complete number on the Y-axis in Google Chart?

I am using Google Chart to display a bar chart. Please see the screen shot below. Problem is, the highest number on the y-axis is always getting chopped off. In the screenshot, the highest number is supposed to be 55; but, as you can see, only the lower portion of 55 is showing. I have tried changing the height of my div and changing the height of the chart. They didn't solve the problem. I have also tried changing the max value of the y-axis but that does not solve the problem in the sense that the highest number would get chopped off (for example, for the chart in my screen shot, if I were to change the max value to 60, it is true that 55 would show completely but 60 would be chopped off).
How can I show the complete number on the highest value on y-axis in Google Charts?
google.charts.load('current', {packages: ['bar']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.arrayToDataTable([
["", ""],
["Col_1", <?php echo $col1Count; ?>],
["Col_2", <?php echo $col2Count; ?>],
["Col_3", <?php echo $col3Count; ?>],
["Col_4", <?php echo $col4Count; ?>]
]);
var options = {
width: 720,
height: 550,
legend: { position: 'none' },
vAxis: {
viewWindow: {
min: 5,
max: 55
},
ticks: [5,10,15,20,25,30,35,40,45,50,55]
}
};
// Instantiate and draw the chart.
var chart = new google.charts.Bar(document.getElementById('myChart'));
chart.draw(data, google.charts.Bar.convertOptions(options));
google.visualization.events.addListener(chart, 'ready', function () {
html2canvas(document.getElementById('myChart')).then(function(canvas) {
// console.log(canvas.toDataURL('image/png'));
//document.getElementById('myChart').appendChild(canvas);
jQuery.ajax({
type: 'POST',
url: 'https://myURL.com/saveimage.php',
data: {
// send image string as data
imgstr: canvas.toDataURL('image/png')
},
success: function(response) {
alert(response);
}
});
});
});
}
The material Bar chart has far fewer options than the classic charts. I suspect that if you add a title, even if it is just a space character, that will reserve enough space above the chart to show the topmost axis tick label without truncation.

ChartJs: Is there a way to control the font options per line for a multiline axis label

I am open to learning that there is already a way (via configuration, or developing a plugin) to hook into the rendering of the label of an axis, such that I could control aspects of the font used to render each line of a multiline label (e.g., what I need to render would be similar visually to a label and sub-label below it, with the primary label being bolded and a larger font size, while the sub-label directly beneath it would be normal font weight and a smaller size).
I am using ChartJs version 3.5.1 to render a horizontal barchart (meaning that the dataset labels on the left are really configured under the y axis), and have tried a few different things already:
Hooking into the tick callback - but I can't even use this function to duplicate default functionality (the value coming into that function isn't the label text; instead it is the index/ordinal of the data row?). Even if I could get this to work as shown in examples, it appears like this would be more for the content of the label than any of the configuration options themselves.
Setting the font configuration for ticks to be an array - but this only serves to allow me to change the font between data rows (e.g., I can make the label of the top row in my horizontal bar chart be size 22, the second label 10, etc. - but not change font attributes within lines of a given label)
Using a plugin like afterDraw to try to go tweak things - but again, the configuration at that point seems to only consider all of the lines together as one label.
Tried looking through past PRs to the project (mostly centered around adding multiline label support, as well as bug fixes specific to that area) to get any additional insight
If there isn't a way currently (via plugins or existing configuration), does anyone have a good feel for where to start attacking this sort of a change as a new PR?
UPDATE
As was shared as a response to my corresponding ChartJs feature request and as the accepted answer below, a custom plugin seems to be the only way currently to accomplish what I wanted for now.
Here are the key bits from my configuration (admittedly much more "one time use only" than the accepted answer, as I moved some of the configuration inside of the plugin as hard-coded values given my relatively narrow use case):
// this will be passed into the chart constructor...
const options = {
//...
scales: {
//...
// I wanted to impact the lefthand side of a horizontal bar chart
y: {
ticks: {
// make the original labels white for later painting over with custom sub-labels
color: "white",
// we still want this here to be able to take up the same space as the eventual label we will stick here
font: {
size: 22,
weight: "bold"
}
}
},
//...
}
};
// This is my plugin, also later passed into the chart constructor
const customSubLabelsPlugin = {
id: "customSubLabels",
afterDraw: (chart, args, opts) => {
// Set all variables needed
const {
ctx,
// I only cared about altering one specific axis
scales: { y }
} = chart;
const labelItems = y._labelItems;
const fontStringSubTitle = "16px Helvetica,Arial,sans-serif";
const fontStringMain = "bold 22px Helvetica,Arial,sans-serif";
// loop over each dataset label
for (let i = 0; i < labelItems.length; i++) {
let labelItem = labelItems[i];
// For purposes of redrawing, we are going to always assume that each label is an array - because we make it that way if we need to
const label = Array.isArray(labelItem.label)
? labelItem.label
: [labelItem.label];
// Draw new text on canvas
let offset = 0;
label.forEach((el) => {
let elTextMetrics = ctx.measureText(el);
if (labelItem.label.indexOf(el) === 0) {
ctx.font = fontStringMain;
} else {
ctx.font = fontStringSubTitle;
}
ctx.save();
ctx.fillStyle = "#546a6f";
ctx.fillText(
el,
labelItem.translation[0],
labelItem.translation[1] + labelItem.textOffset + offset
);
ctx.restore();
offset +=
elTextMetrics.fontBoundingBoxAscent +
elTextMetrics.fontBoundingBoxDescent;
});
}
}
};
You can use a plugin to redraw the ticks for you, might need some finetuning for your specific needs:
var options = {
type: 'line',
data: {
labels: [
["Red", "subTitle"],
["Blue", "subTitle"],
["Yellow", "subTitle"],
["Green", "subTitle"],
["Purple", "subTitle"],
["Orange", "subTitle"]
],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'red',
backgroundColor: 'red'
}]
},
options: {
plugins: {
customTextColor: {
color: 'blue',
boxColor: 'white',
fontStringSubTitle: 'italic 12px Comic Sans MS',
fontStringMain: ''
}
}
},
plugins: [{
id: 'customTextColor',
afterDraw: (chart, args, opts) => {
// Set all variables needed
const {
ctx,
scales: {
y,
x
}
} = chart;
const labelItems = x._labelItems;
const {
color,
boxColor,
fontStringMain,
fontStringSubTitle
} = opts;
const defaultFontString = '12px "Helvetica Neue", Helvetica, Arial, sans-serif';
for (let i = 0; i < labelItems.length; i++) {
let labelItem = labelItems[i];
if (!Array.isArray(labelItem.label)) {
continue;
}
let metrics = ctx.measureText(labelItem.label);
let labelWidth = metrics.width;
let labelHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
//Draw box over old labels so they are inviseble
ctx.save();
ctx.fillStyle = boxColor || '#FFFFFF';
ctx.fillRect((labelItem.translation[0] - labelWidth / 2), labelItem.translation[1], labelWidth, labelHeight * labelItem.label.length);
ctx.restore();
// Draw new text on canvas
let offset = 0;
labelItem.label.forEach(el => {
let elTextMetrics = ctx.measureText(el);
let elWidth = elTextMetrics.width;
if (labelItem.label.indexOf(el) === 0) {
ctx.font = fontStringMain || defaultFontString;
} else {
ctx.font = fontStringSubTitle || defaultFontString;
}
ctx.save();
ctx.fillStyle = color || Chart.defaults.color
ctx.fillText(el, (labelItem.translation[0] - elWidth / 2), labelItem.translation[1] + labelItem.textOffset + offset);
ctx.restore();
offset += elTextMetrics.fontBoundingBoxAscent + elTextMetrics.fontBoundingBoxDescent;
});
}
// Draw white box over old label
}
}]
}
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.5.1/chart.js"></script>
</body>

Gradient line chart with ChartJS

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.

merge total of Stacked Bar Chart

I have two unsigned integers per bar: X and Y.
X is always less than Y because it is a subset of it.
I want to combine these two values into a single bar. However, the Stacking Bar Chart adds up the values instead of "overlapping" them.
This is what isStacked: true results in:
XXXYYYYY
(3x + 5y, Axis goes up to 8)
And here is what my goal is:
XXXYY
(3x within 5y, Axis goes up to 5)
How can I "merge" the values into one bar that is based on Y only and thus doesn't impact the axis' maximum value?
the data will need to be adjusted, setting chart options won't get it.
you could create a view, add a calculated column, and exclude the original Y...
google.charts.load('current', {
packages: ['corechart'],
callback: drawChart
});
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Year', 'X', 'Y'],
['2016', 3, 5]
]);
var view = new google.visualization.DataView(data);
view.setColumns([0, 1, {
calc: function (data, row) {
return {
v: data.getValue(row, 2) - data.getValue(row, 1),
f: data.getValue(row, 2).toString()
};
},
type: 'number',
label: 'Y'
}]);
var chart = new google.visualization.BarChart(document.getElementById('chart_div'));
chart.draw(view, {isStacked: true});
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

Google Chart API - Change value with mouse click (setValue)

I have created a donut chart from Google Charts API. When clicking on each slice, it should increase by 10 units and decrease the adjacent slice (clockwise) by 10 units. What I have thus far is a alert popup that explains this, but I would like to redraw the chart with the new values.
Here is my code:
<html>
<head>
<!--Load the AJAX API-->
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
// Load the Visualization API and the piechart package.
google.load('visualization', '1.0', {'packages':['corechart']});
// Set a callback to run when the Google Visualization API is loaded.
google.setOnLoadCallback(drawChart);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart() {
// Create the data table.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Option');
data.addColumn('number', 'Value');
data.addRows([
['Option A', 40],
['Option B', 30],
['Option C', 30]
]);
// Set chart options
var options = {
height: 300,
fontName: 'Lato, sans-serif',
title: 'Values per option',
titleTextStyle: {
color: '#5a5a5a',
fontSize: 20,
bold: true,
align: 'center'
},
pieHole: 0.6,
slices: {
0: {color: 'red'},
1: {color: 'blue'},
2: {color: 'green'}
},
legend: {
position: 'bottom',
textStyle: {
color: '#5a5a5a',
fontSize: 14
}
},
enableInteractivity: true,
pieSliceText: 'none'
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.PieChart(document.getElementById('chart_div'));
function selectHandler() {
var selectedItem = chart.getSelection()[0];
if (selectedItem && selectedItem.row <2) {
var activeTrait = data.getValue(selectedItem.row, 0);
activePerc = data.getValue(selectedItem.row, 1);
activePercNew = parseInt(activePerc)+10
adjaceTrait = data.getValue(selectedItem.row+1, 0);
adjacePerc = data.getValue(selectedItem.row+1, 1);
adjacePercNew = parseInt(adjacePerc)-10
alert(activeTrait + ' has a value of ' + activePerc + '%. The new value will now be set to ' + activePercNew + '% and ' + adjaceTrait + ' will be corrected to ' + adjacePercNew + '%.');
}
if (selectedItem && selectedItem.row == 2) {
var activeTrait = data.getValue(selectedItem.row, 0);
activePerc = data.getValue(selectedItem.row, 1);
activePercNew = parseInt(activePerc)+10
adjaceTrait = data.getValue(selectedItem.row-2, 0);
adjacePerc = data.getValue(selectedItem.row-2, 1);
adjacePercNew = parseInt(adjacePerc)-10
alert(activeTrait + ' has a value of ' + activePerc + '%. The new value will now be set to ' + activePercNew + '% and ' + adjaceTrait + ' will be corrected to ' + adjacePercNew + '%.');
}
}
google.visualization.events.addListener(chart, 'select', selectHandler);
chart.draw(data, options);
}
</script>
</head>
<body>
<!--Div that will hold the pie chart-->
<div id="chart_div" style="width:800; height:300"></div>
</body>
</html>
I would just like to resize the selected and adjacent slices by clicking on a single slice. Not sure if I should create a var newdata with the changed values and use chart.draw(newdata, option)?
Yeah you're pretty much there, and you're definitely on the right track with your answer. You can use data.setValue() to adjust your values then you would have something like this in your second "if" statement:
data.setValue(selectedItem.row,1, activePercNew);
data.setValue(selectedItem.row-2,1, adjacePercNew);
chart.draw(data, options);
// the thing we just clicked on was redrawn so it lost its handler, reinstate it:
google.visualization.events.addListener(chart, 'select', selectHandler);
And the same in the first but with selectedItem.row+1 instead of selectedItem.row-2. Or, ideally, tidy that section up a little so the two if statements figure out which things you're referring to and then one bit of code does the redraw. For example, here's an adjusted handler function which also doesn't rely on there being 3 sections:
function selectHandler() {
var selectedItem = chart.getSelection()[0];
var numRows = data.getNumberOfRows();
// verify the selection isn't inexplicibly invalid
if (selectedItem && selectedItem.row < numRows && selectedItem.row >= 0) {
// find the two items we're looking at
var curItem = selectedItem.row;
// we either want selected.row + 1 or we want 0 if the selected item was the last one
var otherItem = selectedItem.row == numRows - 1 ? 0 : selectedItem.row + 1;
// calculate the new values
var activePerc = data.getValue(curItem , 1);
var activePercNew = parseInt(activePerc)+10;
var adjacePerc = data.getValue(otherItem , 1);
var adjacePercNew = parseInt(adjacePerc )-10;
// update the chart
data.setValue(curItem,1, activePercNew);
data.setValue(otherItem,1, adjacePercNew);
chart.draw(data, options);
// the thing we just clicked on was redrawn so it lost its handler, reinstate it:
google.visualization.events.addListener(chart, 'select', selectHandler);
}
}
You might want to then also consider what should happen if a value is forced right to zero - with this solution it'll disappear from the chart, and then the next click will force an invalid negative value.