I have created a line chart in Google Sheets using data pulled from an API. The data changes every hour, so the min and max values of the data to chart also changes.
I would like to know if there is a script that can lookup up min and max from a column of data and use those values to dynamically set min/max for the Y axis.
Currently the only way I can see to change Y axis min max is manually in the chart editor, but the inputs only accept numbers, not formulas, so the min max is rigid.
// Only applies to first y axis
// Only applies to charts on active spreadsheet
// Allows for multiple series
// Takes approx 3 seconds per 10 charts to run
// Change Sheet1 for your sheet name
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1')
var config = {
// y-axis max is this factor higher than your chart's max value
// For example if your max value is 100 and scaleUpBuffer is 0.1,
// the y-axis max value will be set to 100 * (1 + 0.1) = 110
scaleUpBuffer: 0.0001,
// y-axis min is this factor lower than your chart's min value
scaleDownBuffer: 0.0001,
// If / when google fix goes through,
// set this to true and run fixCharts() to get default scaling back
// This sets min and max y axis values to null (the default)
restoreDefault: false,
}
function fixCharts() {
var minMaxY1;
sheet.getCharts().forEach(function (chart) {
minMaxY1 = config.restoreDefault ? [null, null] : getChartMinMaxY(chart, config);
updatedChart = chart
.modify()
.setOption('vAxes', {0: {viewWindow: {min: minMaxY1[0], max: minMaxY1[1]}}})
.build();
sheet.updateChart(updatedChart);
})
}
function getChartMinMaxY(chart, config) {
var chartRangeArray,
y1 = [];
chart.getRanges().forEach(function(chartRange) {
chartRangeArray = chartRange.getValues();
chartRangeArray.forEach(function(row) {
for (var i=1;i<row.length;i++) {
// loops all series, assumes all on 1st y axis
if (typeof row[i] == 'number') {
// ignores blanks, headers, junk strings etc.
y1.push(row[i]);
}
}
});
});
return [
Math.min.apply(null, y1) * (1 - config.scaleDownBuffer),
Math.max.apply(null, y1) * (1 + config.scaleUpBuffer)
];
}
Related
I am trying to create a reference line that runs through the origin and passes from negative to positive. See an example of what i am trying to achieve - see the threshold line. This threshold line must run through all three x, y coordinates (-1,-45,000), (0.0), (1, 45,000).
enter image description here
Below is my work so far.
http://jsfiddle.net/catio6/rhf6yre5/1/
I've looked at this for reference but have had had no luck after several hours of attempts of replicating this with all three x, y coordinates (-1,-45,000), (0.0), (1, 45,000): http://jsfiddle.net/phpdeveloperrahul/XvjfL/
$(function () {
$('#container').highcharts({
xAxis: {
categories: ['Jan', 'Feb', 'Mar']
},
series: [{
data: [29.9, 71.5, 256]
}]
}, function(chart) { // on complete
console.log("chart = ");
console.log(chart);
//chart.renderer.path(['M', 0, 0, 'L', 100, 100, 200, 50, 300, 100])
chart.renderer.path(['M', 75, 223.5,'L', 259, 47])//M 75 223.5 L 593 223.5
.attr({
'stroke-width': 2,
stroke: 'red'
})
.add();
});
});
So Highcharts doesn't have, as far as I know, a way to define a line that goes from/to infinity.
One idea I had to solve this issue for you is dynamically calculate the values for the line series based on your data. The idea is simple: Given the maximum values for X and Y you want to plot, we just need to limit the axis to a certain value that makes sense and calculate the values for the asymptote series in order to make it seems infinite. My algorithm looks like this:
// Get all your other data in a well formated way
let yourData = [
{x: 0.57, y: 72484},
{x: 0.57, y: 10000}
];
// Find which are the maximum x and y values
let maxX = yourData.reduce((max, v) => max > v.x ? max : v.x, -999999999);
let maxY = yourData.reduce((max, v) => max > v.y ? max : v.y, -999999999);
// Now you will limit you X and Y axis to a value that makes sense due the maximum values
// Here I will limit it to 10K above or lower on Y and 2 above or lower on X
let maxXAxis = maxX + 2;
let minXAxis = - (maxX + 2);
let maxYAxis = maxY + 10000;
let minYAxis = -(maxY + 10000);
// Now you need to calculate the values for the Willingness to pay series
let fn = (x) => 45000 * x; // This is the function that defines the Willingness to pay line
// Calculate the series values
let willingnessSeries = [];
for(let i = Math.floor(minXAxis); i <= Math.ceil(maxXAxis); i++) {
willingnessSeries.push([i, fn(i)]);
}
Here is a working fiddle: http://jsfiddle.net/n5xg1970/
I tested with several values for your data and all of them seem to be working ok.
Hope it helps
Regards
To preface, I am using the extended logarithm functions for negative and small numbers here:
/**
* Custom Axis extension to allow emulation of negative values on a
logarithmic
* Y axis. Note that the scale is not mathematically correct, as a true
* logarithmic axis never reaches or crosses zero.
*/
(function (H) {
// Pass error messages
H.Axis.prototype.allowNegativeLog = true;
// Override conversions
H.Axis.prototype.log2lin = function (num) {
var isNegative = num < 0,
adjustedNum = Math.abs(num),
result;
if (adjustedNum < 10) {
console.log('adjustedNum: ', adjustedNum);
adjustedNum += (10 - adjustedNum) / 10;
}
result = Math.log(adjustedNum) / Math.LN10;
if (adjustedNum < 10) console.log('result: ', result);
return isNegative ? -result : result;
};
H.Axis.prototype.lin2log = function (num) {
var isNegative = num < 0,
absNum = Math.abs(num),
result = Math.pow(10, absNum);
if (result < 10) {
result = (10 * (result - 1)) / (10 - 1);
}
return isNegative ? -result : result;
};
}(Highcharts));
So if I have a y-axis with a dataMin of 0.22 and a dataMax of 2.34 using a log scale, the only ticks I get back are [0,1] designating 0 and 10, the bottom and top of the chart, with no tick marks in between.
1) Is there a way I can specify how many ticks I want in a log chart (tickAmount: 5 would not work)?
2) Is there a way I can tighten the range over which ticks fall? (like in this case, it sets the axis with data values between 0 and 10 even though my data series only falls between 0.22 and 2.34).
Thanks!
1.
tickPositions and tickPositioner options allow you to control where exactly the ticks will fall.
2.
You can try adjusting the tickInterval option: http://jsfiddle.net/BlackLabel/8v3xuyot/
yAxis: {
type: 'logarithmic',
tickInterval: 0.2
}
API references:
https://api.highcharts.com/highcharts/yAxis.tickInterval
https://api.highcharts.com/highcharts/yAxis.tickPositioner
https://api.highcharts.com/highcharts/yAxis.tickPositions
Let's say I am doing temperature graph for 2 cities. I want to have minimum and maximum temperature for each month and average month temperature in one bar.
I have found (and changed a bit) an example which works perfectly for one city (jsfiddle here). That's the result I want.
Stackoverflow wants me to put some code here because of jsfiddle,
but I really don't know if it is good idea to paste here allt that
long JS code
However I need it for two (or more) cities for each month. For now, I have this (jsfiddle here), but the average points are not placed inside of bars, instead the appear somewhere in the middle.
Is there a way how to have multiple cities with average points placed inside of each bar ? I don't know what I am missing.
Thanks in advance.
Not a perfect solution, but should be good starting point:
(function(H) {
H.wrap(H.seriesTypes.columnrange.prototype, 'drawPoints', function(p) {
var s = this,
chart = s.chart,
xAxis = s.xAxis,
yAxis = s.yAxis,
path, x, y,
r = chart.renderer,
radius = s.barW / 2;
p.call(this);
H.each(this.points, function(p) {
if(p.options.avg) {
y = yAxis.toPixels(p.options.avg * (-1), true);
x = p.plotX + s.columnMetrics.offset + radius;
path = [
'M', x - radius, y - radius,
'L', x + radius, y - radius,
'L', x + radius, y + radius,
'L', x - radius, y + radius,
'Z'
];
if(p.avgShape) {
p.avgShape.attr({
d: path
});
} else {
p.avgShape = r.path(path).attr({
fill: s.options.avgColor
}).add(s.group);
}
}
});
});
})(Highcharts)
Demo: http://jsfiddle.net/r6fha9he/3/
One hacky thing to mention - values are multiplied by *(-1), because toPixels() won't recognize inverted chart, and I would like to avoid using axis.translate(). The same is not necessary for non-inverted chart. In case you are more interested in translate, search source code for:
/**
* Translate from axis value to pixel position on the chart, or back
*
*/
translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) {
I want to enforce that the axis of my bubble chart to starts at 0. In any other chart type I would set
yAxis: {
min:0
}
But this can cause bubbles with an y value near zero to be clipped.
All I could come up with so far is to add an invisible dummy series to force the axis to start at 0.
See http://jsfiddle.net/kzoon/jd3c9/ for my problem and workaround.
Has anyone a better solution to this problem?
How about using tick positioner? It allows you to programmatically set tick positions. Take a look into the docs: http://api.highcharts.com/highcharts#yAxis.tickPositioner
Sample function can work like this:
Find axis min and max values
If min is greater than 0, use 0 as min:
Add ticks every "20" unless we achieve max value
Here you can find the code:
yAxis: {
tickPositioner: function () {
var positions = [],
min = Math.floor(this.min / 10) * 10,
max = Math.ceil(this.max / 10) * 10,
step = 20,
tick;
if (min > 0)
min = 0;
tick = min;
while (tick < max + step) {
positions.push(tick);
tick += step;
}
return positions;
}
},
and working demo on jsfiddle: http://jsfiddle.net/2ABFW/
You can use startWithTick: http://jsfiddle.net/jd3c9/8/
yAxis: {
startWithTick: true
}
Now your y-axis will display the next tick under 0 (-20 in this case) and no bubble will be clipped.
The issue I have is when using two Y-axes (y1 and y2), wherein the y1 value is: (min,max) = (zero,positive) and the y2 value (min, max) = (negative, positive), in such case, the zero marking of y1 coincides with the max (negative) value of the y2 axis (through the x-axis), that is the problem since I want zero point of both y-axis to flush together.
If I knew the value of min and max for both y-axes then this problem could be fixed easily, but I only know whether the range starts from positive or negative value, not the value itself.
Note that this problem is not there when both y-axes have values (data points) above zero. They automatically align such that both their zero points passes through the x-axis.
I managed to do so by fixing the proportion between axis:
public void SetY1Y2CommonZero()
{
AxisChange();
ZedGraph.Scale source, dest;
if (GraphPane.YAxis.Scale.Min != 0)
{
source = GraphPane.YAxis.Scale;
dest = GraphPane.Y2Axis.Scale;
}
else if (GraphPane.Y2Axis.Scale.Min != 0)
{
source = GraphPane.Y2Axis.Scale;
dest = GraphPane.YAxis.Scale;
}
else
{
return;
// do nothing - both axis have 0 on min...
}
double proportion = source.Max / source.Min;
// we want to ENLARGE the other axis accordingly:
if (proportion * dest.Min > dest.Max)
dest.Max = proportion * dest.Min;
else
dest.Min = dest.Max / proportion;
}