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
Related
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)
];
}
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.
We are comparing multiple series using logarithmic scale. Most of the time the chart looks fine and scales correctly to min/max of the data. In the example below the top series is cut off. The only thing that changes is the data. If we change the time period to any of the other options, it will re-scale correctly, going back to 1Y has the same problem. Is there a way to resolve this issue?
The second question I have is about the logic of the grid lines/labels when in log scale. Shouldn't by default the steps be based on Log(10) for example [0.10%, 1%, 10%, 100%, 1000%]? Is there a config option to set this? In the example below the default [0.10, 0.20, 0.40, 1.00] doesn't make much sense.
This is what the labels/ticks should always look like: jsfiddle.net/TeTMw/1
EDIT:
Here is the highcharts code that looks like is causing this bug. Would be great if you could help fix this issue.
// Second case: We need intermediary ticks. For example
// 1, 2, 4, 6, 8, 10, 20, 40 etc.
} else if (interval >= 0.08) {
var roundedMin = mathFloor(min),
intermediate,
i,
j,
len,
pos,
lastPos,
break2;
if (interval > 0.3) {
intermediate = [1, 2, 4];
} else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
intermediate = [1, 2, 4, 6, 8];
} else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
}
for (i = roundedMin; i < max + 1 && !break2; i++) {
len = intermediate.length;
for (j = 0; j < len && !break2; j++) {
pos = log2lin(lin2log(i) * intermediate[j]);
if (pos > min && lastPos <= max) {
positions.push(lastPos);
}
if (lastPos > max) {
break2 = true;
}
lastPos = pos;
}
}
There's a known issue that log axes may be cut off in Highcharts 1.3.0. This will be fixed with 1.3.1, released later this week. See https://github.com/highslide-software/highcharts.com/issues/1666 .
For the Y axis tick interval, see http://api.highcharts.com/highcharts#yAxis.tickInterval. By default, the tickInterval is null, so the layout of the ticks is determined by tickPixelInterval. In your specific chart for example, if we skipped 0.20 and 0.40, the ticks would become too widespread for the default tickPixelInterval of 72.
I need to know how to calculate the positions of the QR Code alignment patterns as defined in the table of ISO/IEC 18004:2000 Annex E.
I don't understand how it's calculated. If you take the Version 16, for example, the positions are calculated using {6,26,50,74} and distance between the points are {20,24,24}. Why isn't it {6,28,52,74}, if the distances between the points, {22,24,22}, is distributed more equally?
I would like to know how this can be generated procedurally.
While the specification does provide a table of the alignment, this is a reasonable question (and one I found myself with :-)) - the possibility of generating the positions procedurally has its merits (less typo-prone code, smaller code footprint, knowing pattern/properties of the positions).
I'm happy to report that, yes, a procedure exists (and it is even fairly simple).
The specification itself says most of it:
[The alignment patterns] are spaced as evenly as possible between the Timing Pattern and the opposite side of the symbol, any uneven spacing being accommodated between the timing pattern and the first alignment pattern in the symbol interior.
That is, only the interval between the first and second coordinate may differ from the rest of the intervals. The rest must be equal.
Another important bit is of course that, for the APs to agree with the timing patterns, the intervals must be even.
The remaining tricky bit is just getting the rounding right.
Anyway - here's code printing the alignment position table:
def size_for_version(version):
return 17 + 4 * version
def alignment_coord_list(version):
if version == 1:
return []
divs = 2 + version // 7
size = size_for_version(version)
total_dist = size - 7 - 6
divisor = 2 * (divs - 1)
# Step must be even, for alignment patterns to agree with timing patterns
step = (total_dist + divisor // 2 + 1) // divisor * 2 # Get the rounding right
coords = [6]
for i in range(divs - 2, -1, -1): # divs-2 down to 0, inclusive
coords.append(size - 7 - i * step)
return coords
for version in range(1, 40 + 1): # 1 to 40 inclusive
print("V%d: %s" % (version, alignment_coord_list(version)))
Here's a Python solution which is basically equivalent to the C# solution posted by #jgosar, except that it corrects a deviation from the thonky.com table for version 32 (that other solution reports 110 for the second last position, whereas the linked table says 112):
def get_alignment_positions(version):
positions = []
if version > 1:
n_patterns = version // 7 + 2
first_pos = 6
positions.append(first_pos)
matrix_width = 17 + 4 * version
last_pos = matrix_width - 1 - first_pos
second_last_pos = (
(first_pos + last_pos * (n_patterns - 2) # Interpolate end points to get point
+ (n_patterns - 1) // 2) # Round to nearest int by adding half
# of divisor before division
// (n_patterns - 1) # Floor-divide by number of intervals
# to complete interpolation
) & -2 # Round down to even integer
pos_step = last_pos - second_last_pos
second_pos = last_pos - (n_patterns - 2) * pos_step
positions.extend(range(second_pos, last_pos + 1, pos_step))
return positions
The correction consists of first rounding the second last position (up or down) to the nearest integer and then rounding down to the nearest even integer (instead of directly rounding down to the nearest even integer).
Disclaimer: Like #jgosar, I don't know whether the thonky.com table is correct (I'm not going to buy the spec to find out). I've simply verified (by pasting the table into a suitable wrapper around the above function) that my solution matches that table in its current version.
sorry about my English.
I hope this can help you, and not to later reply.
first things, the standard forget a important thing is that the top left is define with (0,0).
the { 6, 26, 50, 74 } means the alignment points row coordinate and col coordinate, and I don't
know why they do like this, maybe for save space. but we combine all the values for example the:
{ 6, 26, 50, 74 }
and we get :
{ 6 , 6 } ---> ( the x coordinate is 6, and the y is 6, from top/left )
{ 6 , 26 }
{ 6 , 50 }
{ 6 , 74 }
{ 26, 26 }
{ 26, 50 }
{ 26, 74 }
{ 50, 50 }
{ 50, 74 }
{ 74, 74 }
those point's are the actual coordinate of alignment patterns center.
Ps: if a position has the position detection patterns, we ignore output alignment, like the position
(6, 6).
I also have this question before, but now, I solve it, so I hope you can solve it too.
good luck~
There are some comments on the top rated answer that suggest it isn't 100% accurate, so i'm contributing my solution as well.
My solution is written in C#. It should be easy to translate it to a language of your choice.
private static int[] getAlignmentCoords(int version)
{
if (version <= 1)
{
return new int[0];
}
int num = (version / 7) + 2;//number of coordinates to return
int[] result = new int[num];
result[0] = 6;
if (num == 1)
{
return result;
}
result[num - 1] = 4 * version + 10;
if (num == 2)
{
return result;
}
result[num - 2] = 2 * ((result[0] + result[num - 1] * (num - 2)) / ((num - 1) * 2)); //leave these brackets alone, because of integer division they ensure you get a number that's divisible by 2
if (num == 3)
{
return result;
}
int step = result[num - 1] - result[num - 2];
for (int i = num - 3; i > 0; i--)
{
result[i] = result[i + 1] - step;
}
return result;
}
The values i get with it are the same as shown here: http://www.thonky.com/qr-code-tutorial/alignment-pattern-locations/
To sum it up, the first coordinate is always 6.
The last coordinate is always 7 less than the image size. The image size is calculated as 4*version+17, therefore the last coordinate is 4*version+10.
If the coordinates were precisely evenly spaced, the position of one coordinate before the last would be (first_coordinate+(num-2) * last_coordinate)/(num-1), where num is the number of all coordinates.
But the coordinates are not evenly spaced, so this position has to be reduced to an even number.
Each of the remaining coordinates is spaced the same distance from the next one as the last two are from each other.
Disclaimer: I didn't read any of the documentation, i just wrote some code that generates a sequence of numbers that's the same as in the table i linked to.
Starting with #ericsoe's answer, and noting it's incorrect for v36 and v39 (thanks to #Ana's remarks), I've developed a function that returns the correct sequences. Pardon the JavaScript (fairly easy to translate to other languages, though):
function getAlignmentCoordinates(version) {
if (version === 1) {
return [];
}
const intervals = Math.floor(version / 7) + 1;
const distance = 4 * version + 4; // between first and last alignment pattern
const step = Math.ceil(distance / intervals / 2) * 2; // To get the next even number
return [6].concat(Array.from(
{ length: intervals },
(_, index) => distance + 6 - (intervals - 1 - index) * step)
);
}
I don't know if this is a useful question to ask. It just is the way it is, and it doesn't really matter much if it were {22,24,22}. Why are you asking?
My guess it that the spacing should be multiples of 4 modules.
It seems like most answers aren't correct for all versions (especially v32, v36 and v39) and/or are quite convoluted.
Based on #MaxArt's great solution (which produces wrong coordinates for v32), here's a C function which calculates the correct coordinates for all versions:
#include <math.h>
int getAlignmentCoordinates(int version, int *coordinates) {
if (version <= 1) return 0;
int intervals = (version / 7) + 1; // Number of gaps between alignment patterns
int distance = 4 * version + 4; // Distance between first and last alignment pattern
int step = lround((double)distance / (double)intervals); // Round equal spacing to nearest integer
step += step & 0b1; // Round step to next even number
coordinates[0] = 6; // First coordinate is always 6 (can't be calculated with step)
for (int i = 1; i <= intervals; i++) {
coordinates[i] = 6 + distance - step * (intervals - i); // Start right/bottom and go left/up by step*k
}
return intervals+1;
}
The key is to first round the division to the nearest integer (instead of up) and then round it to the next largest even number.
The C program below uses this function to generate the same values as in the table of ISO/IEC 18004:2000 Annex E linked by OP and the (updated) list found on thonky.com:
#include <stdio.h>
void main() {
for (int version = 2; version <= 40; version++) {
int coordinates[7];
int n = getAlignmentCoordinates(version, coordinates);
printf("%d:", version);
for (int i = 0; i < n; i++) {
printf(" %d", coordinates[i]);
}
printf("\n");
}
}
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;
}