When creating an angle, how do I control the attributes of the automatically created points? - label

I'm working with a polygon and attempting to create angles with labels but when angles are created, so are the points used to define them. This would be fine but I can't control the labels on the automatically created points (and I don't know what they are called or how to find out).
var points = [
[0, 0],
[0, 5],
[3, 0]
];
for (k = 0; k < showAngle.length; k++) {
if (showAngle[k] == 1) {
var angle = board.create('angle', [points[k], points[((k + 1) % points.length)], points[((k + 2) % points.length)]],{fixed:true});
} else if (showAngle[k] == 2) {
var angle = board.create('angle', [points[k], points[((k + 1) % points.length)], points[((k + 2) % points.length)]], {
fixed: false,
name: function() {
return ((180/Math.PI)*JXG.Math.Geometry.rad(points[k], points[((k + 1) % points.length)], points[((k + 2) % points.length)])).toFixed(1) + '°';
}
});
}
}
https://jsfiddle.net/jscottuq/acyrLxfh/12/ contains what I've got so far.
The arrays showLen and showAngle are setting what labels are shown for each side/angle (0 - no label, 1 - name , 2 - measurement).
These will be set when the jsxgraph is created.

At the time being, the possibility to control the style of the newly created points of an angle is missing. We will add this soon.
However, a solution would be to use the already existing points which are hidden in this example. For this it would be helpful to kee a list of these points, e.g. jxg_points:
var jxg_points = [];
for (i = 0; i < points.length; i++) {
var rise = points[(i + 1) % points.length][1] - points[i][1];
var run = points[(i + 1) % points.length][0] - points[i][0];
var point = board.create('point', [points[i][0], points[i][1]], {
fixed: true,
visible:false
});
jxg_points.push(point); // Store the point
points[i].pop();
len[i] = Math.round((Math.sqrt(rise * rise + run * run) + Number.EPSILON) * 100) / 100;
}
Then the points can be reused for the angles without creating new points:
for (k = 0; k < showAngle.length; k++) {
if (showAngle[k] == 1) {
angle = board.create('angle', [
jxg_points[k],
jxg_points[((k + 1) % jxg_points.length)],
jxg_points[((k + 2) % jxg_points.length)]
],{fixed:true});
} else if (showAngle[k] == 2) {
var angle = board.create('angle', [
jxg_points[k],
jxg_points[((k + 1) % jxg_points.length)],
jxg_points[((k + 2) % jxg_points.length)]], {
fixed: false,
name: function() {
return ((180/Math.PI)*JXG.Math.Geometry.rad(points[k], points[((k + 1) % points.length)], points[((k + 2) % points.length)])).toFixed(1) + '°';
}
});
}
}
See it live at https://jsfiddle.net/d8an0epy/.

Related

Find the combination of a number set so that the total difference between two number sets is minimal

Find the combinations of lots assigned from newLots into oldLots, so that the sum of differences between newLotCombinations and oldLots is optimal.
If a difference is less than 0, it counts as 0.
All lots from newLot must be assigned into newLotCombinations.
/**
*
* e.g.
*
* newLots = [20.16, 9.95, 12.62, 7.44, 11.18, 9.02, 8.21, 8.22, 6.57, 6.63]
* oldLots = [12.03, 14.03, 16.04, 17.8, 18.04, 22.05]
*
* newLotCombinations | oldLot | Difference (newLotCombinations - oldLot)
* 20.16, 9.95 | 12.03 | 18.03 (20.16 + 9.95 - 12.03)
* 12.62, 7.44 | 14.03 | 6.03 (12.62 + 7.44 - 14.03)
* 11.18 | 16.04 | 0
* ...
* ...
* Sum of differences = 18.03 + 6.03 + 0 + ...
*/
I think this should involve memoizing the paths that I have gone through, like a map, and walking backward when a path is not correct (total sum larger than the assumption)
This is what I have so far:
const newLots = [20.16, 9.95, 12.62, 7.44, 11.18, 9.02, 8.21, 8.22, 6.57, 6.63]; // stack
const oldLots = [12.03, 14.03, 16.04, 17.8, 18.04, 22.05];
// newLotCombinations is an array of array [[], [], [], [], [], []] // i and j
const newLotCombinations = oldLots.map(() => []);
// Assuming the max. sum of differences is 5.
const MAX_SUM_OF_DIFFERENCES = 7;
const sum = 0;
// store information about a path?
const paths = {};
const loopLots = (i = 0, j = 0) => {
if (i === -1) {
console.log('it should not come to here');
console.log(
"No possible combination for max sum of differences:",
MAX_SUM_OF_DIFFERENCES
);
} else if (!newLots.length) {
console.log(
"Combination that works with max sum of difference ",
MAX_SUM_OF_DIFFERENCES,
newLotCombinations
);
}
newLotCombinations[i][j] = newLots.pop();
if (getSumOfDifferences() > MAX_SUM_OF_DIFFERENCES) {
// put it back to stack
newLots.push(newLotCombinations[i].pop());
if (i + 1 < newLotCombinations.length) {
loopLots(i + 1, newLotCombinations[i+ 1].length);
} else {
// It keeps popping until an array has more than 1 element.
// But this is incorrect, as it will loop with 3 combinations of numbers
// It should walk backward until last divergence
while (newLotCombinations[i] && (i === newLotCombinations.length - 1 || newLotCombinations[i].length < 2)) {
newLots.push(newLotCombinations[i].pop());
i--;
}
if (newLotCombinations[i]) {
newLots.push(newLotCombinations[i].pop());
newLotCombinations[i + 1][newLotCombinations[i + 1].length] = newLots.pop();
// loopLots(i + 1, newLotCombinations[i + 1].length);
loopLots(0, newLotCombinations[0].length);
} else {
console.log(
"No possible combination for max sum of differences:",
MAX_SUM_OF_DIFFERENCES
);
}
}
} else {
loopLots(0, newLotCombinations[0].length);
}
};
const getSumOfDifferences = () => {
let sumOfDifferences = 0;
newLotCombinations.forEach((lots, i) => {
const lotSum = lots.reduce((sum, lot) => {
sum += lot;
return sum;
}, 0);
const difference = lotSum - oldLots[i];
if (difference > 0) {
sumOfDifferences += difference;
}
});
return sumOfDifferences;
};
loopLots();
The logic of using newLotCombinations[i].length < 2 is incorrect, because it keeps pushing in the same alternating numbers. If I memoize the paths to check whether I should go further, how can I know when I am walking backward or forward if simply saving the paths that I have walked through?
I am thinking I also should not save a subset path. I should save a path that reaches the end (i.e. 6), because a subset of path contains unknown paths ahead.

Understanding Spark correlation algorithm

I was reading Spark correlation algorithm source code and while going through the code, I coulddn't understand this particular peace of code.
This is from the file : org/apache/spark/mllib/linalg/BLAS.scala
def spr(alpha: Double, v: Vector, U: Array[Double]): Unit = {
val n = v.size
v match {
case DenseVector(values) =>
NativeBLAS.dspr("U", n, alpha, values, 1, U)
case SparseVector(size, indices, values) =>
val nnz = indices.length
var colStartIdx = 0
var prevCol = 0
var col = 0
var j = 0
var i = 0
var av = 0.0
while (j < nnz) {
col = indices(j)
// Skip empty columns.
colStartIdx += (col - prevCol) * (col + prevCol + 1) / 2
av = alpha * values(j)
i = 0
while (i <= j) {
U(colStartIdx + indices(i)) += av * values(i)
i += 1
}
j += 1
prevCol = col
}
}
}
I do not know Scala and that could be the reason I could not understand it. Can someone explain what is happening here.
It is being called from Rowmatrix.scala
def computeGramianMatrix(): Matrix = {
val n = numCols().toInt
checkNumColumns(n)
// Computes n*(n+1)/2, avoiding overflow in the multiplication.
// This succeeds when n <= 65535, which is checked above
val nt = if (n % 2 == 0) ((n / 2) * (n + 1)) else (n * ((n + 1) / 2))
// Compute the upper triangular part of the gram matrix.
val GU = rows.treeAggregate(new BDV[Double](nt))(
seqOp = (U, v) => {
BLAS.spr(1.0, v, U.data)
U
}, combOp = (U1, U2) => U1 += U2)
RowMatrix.triuToFull(n, GU.data)
}
The correlation is defined here:
https://en.wikipedia.org/wiki/Pearson_correlation_coefficient
The final goal is to understand the Spark correlation algorithm.
Update 1: Relevent paper https://stanford.edu/~rezab/papers/linalg.pdf

Can someone help me vectorize / speed up this Matlab Loop?

correlation = zeros(length(s1), 1);
sizeNum = 0;
for i = 1 : length(s1) - windowSize - delta
s1Dat = s1(i : i + windowSize);
s2Dat = s2(i + delta : i + delta + windowSize);
if length(find(isnan(s1Dat))) == 0 && length(find(isnan(s2Dat))) == 0
if(var(s1Dat) ~= 0 || var(s2Dat) ~= 0)
sizeNum = sizeNum + 1;
correlation(i) = abs(corr(s1Dat, s2Dat)) ^ 2;
end
end
end
What's happening here:
Run through every values in s1. For every value, get a slice for s1
till s1 + windowSize.
Do the same for s2, only get the slice after an intermediate delta.
If there are no NaN's in any of the two slices and they aren't flat,
then get the correlaton between them and add that to the
correlation matrix.
This is not an answer, I am trying to understand what is being asked.
Take some data:
N = 1e4;
s1 = cumsum(randn(N, 1)); s2 = cumsum(randn(N, 1));
s1(randi(N, 50, 1)) = NaN; s2(randi(N, 50, 1)) = NaN;
windowSize = 200; delta = 100;
Compute correlations:
tic
corr_s = zeros(N - windowSize - delta, 1);
for i = 1:(N - windowSize - delta)
s1Dat = s1(i:(i + windowSize));
s2Dat = s2((i + delta):(i + delta + windowSize));
corr_s(i) = corr(s1Dat, s2Dat);
end
inds = isnan(corr_s);
corr_s(inds) = 0;
corr_s = corr_s .^ 2; % square of correlation coefficient??? Why?
sizeNum = sum(~inds);
toc
This is what you want to do, right? A moving window correlation function? This is a very interesting question indeed …

Algorithm to divide text into 3 evenly-sized groups

I'm would like to create an algorithm that will divide text into 3-evenly sized groups (based on text length). Since this will be put to use for line-breaks, the order of the text needs to be maintained.
For instance this string:
Just testing to see how this works.
would sort to:
Just testing // 12 characters
to see how // 10 characters
this works. // 11 characters
Any ideas?
The "minimum raggedness" dynamic program, also from the Wikipedia article on word wrap, can be adapted to your needs. Set LineWidth = len(text)/n - 1 and ignore the comment about infinite penalties for exceeding the line width; use the definition of c(i, j) as is with P = 2.
Code. I took the liberty of modifying the DP always to return exactly n lines, at the cost of increasing the running time from O(#words ** 2) to O(#words ** 2 * n).
def minragged(text, n=3):
"""
>>> minragged('Just testing to see how this works.')
['Just testing', 'to see how', 'this works.']
>>> minragged('Just testing to see how this works.', 10)
['', '', 'Just', 'testing', 'to', 'see', 'how', 'this', 'works.', '']
"""
words = text.split()
cumwordwidth = [0]
# cumwordwidth[-1] is the last element
for word in words:
cumwordwidth.append(cumwordwidth[-1] + len(word))
totalwidth = cumwordwidth[-1] + len(words) - 1 # len(words) - 1 spaces
linewidth = float(totalwidth - (n - 1)) / float(n) # n - 1 line breaks
def cost(i, j):
"""
cost of a line words[i], ..., words[j - 1] (words[i:j])
"""
actuallinewidth = max(j - i - 1, 0) + (cumwordwidth[j] - cumwordwidth[i])
return (linewidth - float(actuallinewidth)) ** 2
# best[l][k][0] is the min total cost for words 0, ..., k - 1 on l lines
# best[l][k][1] is a minimizing index for the start of the last line
best = [[(0.0, None)] + [(float('inf'), None)] * len(words)]
# xrange(upper) is the interval 0, 1, ..., upper - 1
for l in xrange(1, n + 1):
best.append([])
for j in xrange(len(words) + 1):
best[l].append(min((best[l - 1][k][0] + cost(k, j), k) for k in xrange(j + 1)))
lines = []
b = len(words)
# xrange(upper, 0, -1) is the interval upper, upper - 1, ..., 1
for l in xrange(n, 0, -1):
a = best[l][b][1]
lines.append(' '.join(words[a:b]))
b = a
lines.reverse()
return lines
if __name__ == '__main__':
import doctest
doctest.testmod()
You can try the next simple heuristic for starters: Place to iterators in n/3 and 2n/3 and search for the closest space near each of them.
From http://en.wikipedia.org/wiki/Word_wrap:
SpaceLeft := LineWidth
for each Word in Text
if Width(Word) > SpaceLeft
insert line break before Word in Text
SpaceLeft := LineWidth - Width(Word)
else
SpaceLeft := SpaceLeft - (Width(Word) + SpaceWidth)
This method is used by many modern word processors, such as OpenOffice.org Writer and Microsoft Word. This algorithm is optimal in that it always puts the text on the minimum number of lines.
The answer from "someone" works fine. However, I had problems translating this into SWIFT code. Here is my translation for all those that are interested.
import Foundation
class SplitText{
typealias MinRag = (Float, Int) // meaning (cost for line (so far), word index)
// from http://stackoverflow.com/questions/6426017/word-wrap-to-x-lines-instead-of-maximum-width-least-raggedness?lq=1
class func splitText(text:String, numberOfLines:Int)-> [String]{
//preparations
var words = split(text, maxSplit:100, allowEmptySlices: false, isSeparator:{(s:Character)-> Bool in return s == " " || s == "\n"})
var cumwordwidth = [Int](); //cummulative word widths
cumwordwidth.append(0);
for word in words{
cumwordwidth.append(cumwordwidth[cumwordwidth.count - 1] + count(word));
}
var totalwidth = cumwordwidth[cumwordwidth.count - 1] + count(words) - 1;
var linewidth:Float = Float(totalwidth - (numberOfLines - 1)) / Float(numberOfLines)
// cost function for one line for words i .. j
var cost = { (i:Int,j:Int)-> Float in
var actuallinewidth = max(j - i - 1, 0) + (cumwordwidth[j] - cumwordwidth[i]);
var remainingWidth: Float = linewidth - Float(actuallinewidth)
return remainingWidth * remainingWidth
}
var best = [[MinRag]]()
var tmp = [MinRag]();
//ensure that data structure is initialised in a way that we start with adding the first word
tmp.append((0, -1));
for word in words {
tmp.append((Float.infinity , -1));
}
best.append(tmp);
//now we can start. We simply calculate the cost for all possible lines
for l in 1...numberOfLines {
tmp = [MinRag]()
for j in 0...words.count {
var min:MinRag = (best[l - 1][0].0 + cost(0, j), 0);
var k: Int
for k = 0; k < j + 1 ; ++k {
var loc:Float = best[l - 1][k].0 + cost(k, j);
if (loc < min.0 || (loc == min.0 && k < min.1)) {
min=(loc, k);
}
println("l=\(l), j=\(j), k=\(k), min=\(min)")
}
tmp.append(min);
}
best.append(tmp);
}
//now build the answer based on above calculations
var lines = [String]();
var b = words.count;
var o:Int
for o = numberOfLines; o > 0 ; --o {
var a = best[o][b].1;
lines.append(" ".join(words[a...b-1]));
b = a;
}
return reverse(lines);
}
}

Choosing an attractive linear scale for a graph's Y Axis

I'm writing a bit of code to display a bar (or line) graph in our software. Everything's going fine. The thing that's got me stumped is labeling the Y axis.
The caller can tell me how finely they want the Y scale labeled, but I seem to be stuck on exactly what to label them in an "attractive" kind of way. I can't describe "attractive", and probably neither can you, but we know it when we see it, right?
So if the data points are:
15, 234, 140, 65, 90
And the user asks for 10 labels on the Y axis, a little bit of finagling with paper and pencil comes up with:
0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250
So there's 10 there (not including 0), the last one extends just beyond the highest value (234 < 250), and it's a "nice" increment of 25 each. If they asked for 8 labels, an increment of 30 would have looked nice:
0, 30, 60, 90, 120, 150, 180, 210, 240
Nine would have been tricky. Maybe just have used either 8 or 10 and call it close enough would be okay. And what to do when some of the points are negative?
I can see Excel tackles this problem nicely.
Does anyone know a general-purpose algorithm (even some brute force is okay) for solving this? I don't have to do it quickly, but it should look nice.
A long time ago I have written a graph module that covered this nicely. Digging in the grey mass gets the following:
Determine lower and upper bound of the data. (Beware of the special case where lower bound = upper bound!
Divide range into the required amount of ticks.
Round the tick range up into nice amounts.
Adjust the lower and upper bound accordingly.
Lets take your example:
15, 234, 140, 65, 90 with 10 ticks
lower bound = 15
upper bound = 234
range = 234-15 = 219
tick range = 21.9. This should be 25.0
new lower bound = 25 * round(15/25) = 0
new upper bound = 25 * round(1+235/25) = 250
So the range = 0,25,50,...,225,250
You can get the nice tick range with the following steps:
divide by 10^x such that the result lies between 0.1 and 1.0 (including 0.1 excluding 1).
translate accordingly:
0.1 -> 0.1
<= 0.2 -> 0.2
<= 0.25 -> 0.25
<= 0.3 -> 0.3
<= 0.4 -> 0.4
<= 0.5 -> 0.5
<= 0.6 -> 0.6
<= 0.7 -> 0.7
<= 0.75 -> 0.75
<= 0.8 -> 0.8
<= 0.9 -> 0.9
<= 1.0 -> 1.0
multiply by 10^x.
In this case, 21.9 is divided by 10^2 to get 0.219. This is <= 0.25 so we now have 0.25. Multiplied by 10^2 this gives 25.
Lets take a look at the same example with 8 ticks:
15, 234, 140, 65, 90 with 8 ticks
lower bound = 15
upper bound = 234
range = 234-15 = 219
tick range = 27.375
Divide by 10^2 for 0.27375, translates to 0.3, which gives (multiplied by 10^2) 30.
new lower bound = 30 * round(15/30) = 0
new upper bound = 30 * round(1+235/30) = 240
Which give the result you requested ;-).
------ Added by KD ------
Here's code that achieves this algorithm without using lookup tables, etc...:
double range = ...;
int tickCount = ...;
double unroundedTickSize = range/(tickCount-1);
double x = Math.ceil(Math.log10(unroundedTickSize)-1);
double pow10x = Math.pow(10, x);
double roundedTickRange = Math.ceil(unroundedTickSize / pow10x) * pow10x;
return roundedTickRange;
Generally speaking, the number of ticks includes the bottom tick, so the actual y-axis segments are one less than the number of ticks.
Here is a PHP example I am using. This function returns an array of pretty Y axis values that encompass the min and max Y values passed in. Of course, this routine could also be used for X axis values.
It allows you to "suggest" how many ticks you might want, but the routine will return
what looks good. I have added some sample data and shown the results for these.
#!/usr/bin/php -q
<?php
function makeYaxis($yMin, $yMax, $ticks = 10)
{
// This routine creates the Y axis values for a graph.
//
// Calculate Min amd Max graphical labels and graph
// increments. The number of ticks defaults to
// 10 which is the SUGGESTED value. Any tick value
// entered is used as a suggested value which is
// adjusted to be a 'pretty' value.
//
// Output will be an array of the Y axis values that
// encompass the Y values.
$result = array();
// If yMin and yMax are identical, then
// adjust the yMin and yMax values to actually
// make a graph. Also avoids division by zero errors.
if($yMin == $yMax)
{
$yMin = $yMin - 10; // some small value
$yMax = $yMax + 10; // some small value
}
// Determine Range
$range = $yMax - $yMin;
// Adjust ticks if needed
if($ticks < 2)
$ticks = 2;
else if($ticks > 2)
$ticks -= 2;
// Get raw step value
$tempStep = $range/$ticks;
// Calculate pretty step value
$mag = floor(log10($tempStep));
$magPow = pow(10,$mag);
$magMsd = (int)($tempStep/$magPow + 0.5);
$stepSize = $magMsd*$magPow;
// build Y label array.
// Lower and upper bounds calculations
$lb = $stepSize * floor($yMin/$stepSize);
$ub = $stepSize * ceil(($yMax/$stepSize));
// Build array
$val = $lb;
while(1)
{
$result[] = $val;
$val += $stepSize;
if($val > $ub)
break;
}
return $result;
}
// Create some sample data for demonstration purposes
$yMin = 60;
$yMax = 330;
$scale = makeYaxis($yMin, $yMax);
print_r($scale);
$scale = makeYaxis($yMin, $yMax,5);
print_r($scale);
$yMin = 60847326;
$yMax = 73425330;
$scale = makeYaxis($yMin, $yMax);
print_r($scale);
?>
Result output from sample data
# ./test1.php
Array
(
[0] => 60
[1] => 90
[2] => 120
[3] => 150
[4] => 180
[5] => 210
[6] => 240
[7] => 270
[8] => 300
[9] => 330
)
Array
(
[0] => 0
[1] => 90
[2] => 180
[3] => 270
[4] => 360
)
Array
(
[0] => 60000000
[1] => 62000000
[2] => 64000000
[3] => 66000000
[4] => 68000000
[5] => 70000000
[6] => 72000000
[7] => 74000000
)
Try this code. I've used it in a few charting scenarios and it works well. It's pretty fast too.
public static class AxisUtil
{
public static float CalculateStepSize(float range, float targetSteps)
{
// calculate an initial guess at step size
float tempStep = range/targetSteps;
// get the magnitude of the step size
float mag = (float)Math.Floor(Math.Log10(tempStep));
float magPow = (float)Math.Pow(10, mag);
// calculate most significant digit of the new step size
float magMsd = (int)(tempStep/magPow + 0.5);
// promote the MSD to either 1, 2, or 5
if (magMsd > 5.0)
magMsd = 10.0f;
else if (magMsd > 2.0)
magMsd = 5.0f;
else if (magMsd > 1.0)
magMsd = 2.0f;
return magMsd*magPow;
}
}
Sounds like the caller doesn't tell you the ranges it wants.
So you are free to changed the end points until you get it nicely divisible by your label count.
Let's define "nice". I would call nice if the labels are off by:
1. 2^n, for some integer n. eg. ..., .25, .5, 1, 2, 4, 8, 16, ...
2. 10^n, for some integer n. eg. ..., .01, .1, 1, 10, 100
3. n/5 == 0, for some positive integer n, eg, 5, 10, 15, 20, 25, ...
4. n/2 == 0, for some positive integer n, eg, 2, 4, 6, 8, 10, 12, 14, ...
Find the max and min of your data series. Let's call these points:
min_point and max_point.
Now all you need to do is find is 3 values:
- start_label, where start_label < min_point and start_label is an integer
- end_label, where end_label > max_point and end_label is an integer
- label_offset, where label_offset is "nice"
that fit the equation:
(end_label - start_label)/label_offset == label_count
There are probably many solutions, so just pick one. Most of the time I bet you can set
start_label to 0
so just try different integer
end_label
until the offset is "nice"
I'm still battling with this :)
The original Gamecat answer does seem to work most of the time, but try plugging in say, "3 ticks" as the number of ticks required (for the same data values 15, 234, 140, 65, 90)....it seems to give a tick range of 73, which after dividing by 10^2 yields 0.73, which maps to 0.75, which gives a 'nice' tick range of 75.
Then calculating upper bound:
75*round(1+234/75) = 300
and the lower bound:
75 * round(15/75) = 0
But clearly if you start at 0, and proceed in steps of 75 up to the upper bound of 300, you end up with 0,75,150,225,300
....which is no doubt useful, but it's 4 ticks (not including 0) not the 3 ticks required.
Just frustrating that it doesn't work 100% of the time....which could well be down to my mistake somewhere of course!
The answer by Toon Krijthe does work most of the time. But sometimes it will produce excess number of ticks. It won't work with negative numbers as well. The overal approach to the problem is ok but there is a better way to handle this. The algorithm you want to use will depend on what you really want to get. Below I'm presenting you my code which I used in my JS Ploting library. I've tested it and it always works (hopefully ;) ). Here are the major steps:
get global extremas xMin and xMax (inlucde all the plots you want to print in the algorithm )
calculate range between xMin and xMax
calculate the order of magnitude of your range
calculate tick size by dividing range by number of ticks minus one
this one is optional. If you want to have zero tick allways printed you use tick size to calculate number of positive and negative ticks. Total number of ticks will be their sum + 1 (the zero tick)
this one is not needed if you have zero tick allways printed. Calculate lower and upper bound but remember to center the plot
Lets start. First the basic calculations
var range = Math.abs(xMax - xMin); //both can be negative
var rangeOrder = Math.floor(Math.log10(range)) - 1;
var power10 = Math.pow(10, rangeOrder);
var maxRound = (xMax > 0) ? Math.ceil(xMax / power10) : Math.floor(xMax / power10);
var minRound = (xMin < 0) ? Math.floor(xMin / power10) : Math.ceil(xMin / power10);
I round minimum and maximum values to be 100% sure that my plot will cover all the data. It is also very important to floor log10 of range wheter or not it is negative and substract 1 later. Otherwise your algorithm won't work for numbers that are lesser than one.
var fullRange = Math.abs(maxRound - minRound);
var tickSize = Math.ceil(fullRange / (this.XTickCount - 1));
//You can set nice looking ticks if you want
//You can find exemplary method below
tickSize = this.NiceLookingTick(tickSize);
//Here you can write a method to determine if you need zero tick
//You can find exemplary method below
var isZeroNeeded = this.HasZeroTick(maxRound, minRound, tickSize);
I use "nice looking ticks" to avoid ticks like 7, 13, 17 etc. Method I use here is pretty simple. It is also nice to have zeroTick when needed. Plot looks much more professional this way. You will find all the methods at the end of this answer.
Now you have to calculate upper and lower bounds. This is very easy with zero tick but requires a little bit more effort in other case. Why? Because we want to center the plot within upper and lower bound nicely. Have a look at my code. Some of the variables are defined outside of this scope and some of them are properties of an object in which whole presented code is kept.
if (isZeroNeeded) {
var positiveTicksCount = 0;
var negativeTickCount = 0;
if (maxRound != 0) {
positiveTicksCount = Math.ceil(maxRound / tickSize);
XUpperBound = tickSize * positiveTicksCount * power10;
}
if (minRound != 0) {
negativeTickCount = Math.floor(minRound / tickSize);
XLowerBound = tickSize * negativeTickCount * power10;
}
XTickRange = tickSize * power10;
this.XTickCount = positiveTicksCount - negativeTickCount + 1;
}
else {
var delta = (tickSize * (this.XTickCount - 1) - fullRange) / 2.0;
if (delta % 1 == 0) {
XUpperBound = maxRound + delta;
XLowerBound = minRound - delta;
}
else {
XUpperBound = maxRound + Math.ceil(delta);
XLowerBound = minRound - Math.floor(delta);
}
XTickRange = tickSize * power10;
XUpperBound = XUpperBound * power10;
XLowerBound = XLowerBound * power10;
}
And here are methods I mentioned before which you can write by yourself but you can also use mine
this.NiceLookingTick = function (tickSize) {
var NiceArray = [1, 2, 2.5, 3, 4, 5, 10];
var tickOrder = Math.floor(Math.log10(tickSize));
var power10 = Math.pow(10, tickOrder);
tickSize = tickSize / power10;
var niceTick;
var minDistance = 10;
var index = 0;
for (var i = 0; i < NiceArray.length; i++) {
var dist = Math.abs(NiceArray[i] - tickSize);
if (dist < minDistance) {
minDistance = dist;
index = i;
}
}
return NiceArray[index] * power10;
}
this.HasZeroTick = function (maxRound, minRound, tickSize) {
if (maxRound * minRound < 0)
{
return true;
}
else if (Math.abs(maxRound) < tickSize || Math.round(minRound) < tickSize) {
return true;
}
else {
return false;
}
}
There is only one more thing that is not included here. This is the "nice looking bounds". These are lower bounds that are numbers similar to the numbers in "nice looking ticks". For example it is better to have the lower bound starting at 5 with tick size 5 than having a plot that starts at 6 with the same tick size. But this my fired I leave it to you.
Hope it helps.
Cheers!
Converted this answer as Swift 4
extension Int {
static func makeYaxis(yMin: Int, yMax: Int, ticks: Int = 10) -> [Int] {
var yMin = yMin
var yMax = yMax
var ticks = ticks
// This routine creates the Y axis values for a graph.
//
// Calculate Min amd Max graphical labels and graph
// increments. The number of ticks defaults to
// 10 which is the SUGGESTED value. Any tick value
// entered is used as a suggested value which is
// adjusted to be a 'pretty' value.
//
// Output will be an array of the Y axis values that
// encompass the Y values.
var result = [Int]()
// If yMin and yMax are identical, then
// adjust the yMin and yMax values to actually
// make a graph. Also avoids division by zero errors.
if yMin == yMax {
yMin -= ticks // some small value
yMax += ticks // some small value
}
// Determine Range
let range = yMax - yMin
// Adjust ticks if needed
if ticks < 2 { ticks = 2 }
else if ticks > 2 { ticks -= 2 }
// Get raw step value
let tempStep: CGFloat = CGFloat(range) / CGFloat(ticks)
// Calculate pretty step value
let mag = floor(log10(tempStep))
let magPow = pow(10,mag)
let magMsd = Int(tempStep / magPow + 0.5)
let stepSize = magMsd * Int(magPow)
// build Y label array.
// Lower and upper bounds calculations
let lb = stepSize * Int(yMin/stepSize)
let ub = stepSize * Int(ceil(CGFloat(yMax)/CGFloat(stepSize)))
// Build array
var val = lb
while true {
result.append(val)
val += stepSize
if val > ub { break }
}
return result
}
}
this works like a charm, if you want 10 steps + zero
//get proper scale for y
$maximoyi_temp= max($institucion); //get max value from data array
for ($i=10; $i< $maximoyi_temp; $i=($i*10)) {
if (($divisor = ($maximoyi_temp / $i)) < 2) break; //get which divisor will give a number between 1-2
}
$factor_d = $maximoyi_temp / $i;
$factor_d = ceil($factor_d); //round up number to 2
$maximoyi = $factor_d * $i; //get new max value for y
if ( ($maximoyi/ $maximoyi_temp) > 2) $maximoyi = $maximoyi /2; //check if max value is too big, then split by 2
The above algorithms do not take into consideration the case when the range between min and max value is too small. And what if these values are a lot higher than zero? Then, we have the possibility to start the y-axis with a value higher than zero. Also, in order to avoid our line to be entirely on the upper or the down side of the graph, we have to give it some "air to breathe".
To cover those cases I wrote (on PHP) the above code:
function calculateStartingPoint($min, $ticks, $times, $scale) {
$starting_point = $min - floor((($ticks - $times) * $scale)/2);
if ($starting_point < 0) {
$starting_point = 0;
} else {
$starting_point = floor($starting_point / $scale) * $scale;
$starting_point = ceil($starting_point / $scale) * $scale;
$starting_point = round($starting_point / $scale) * $scale;
}
return $starting_point;
}
function calculateYaxis($min, $max, $ticks = 7)
{
print "Min = " . $min . "\n";
print "Max = " . $max . "\n";
$range = $max - $min;
$step = floor($range/$ticks);
print "First step is " . $step . "\n";
$available_steps = array(5, 10, 20, 25, 30, 40, 50, 100, 150, 200, 300, 400, 500);
$distance = 1000;
$scale = 0;
foreach ($available_steps as $i) {
if (($i - $step < $distance) && ($i - $step > 0)) {
$distance = $i - $step;
$scale = $i;
}
}
print "Final scale step is " . $scale . "\n";
$times = floor($range/$scale);
print "range/scale = " . $times . "\n";
print "floor(times/2) = " . floor($times/2) . "\n";
$starting_point = calculateStartingPoint($min, $ticks, $times, $scale);
if ($starting_point + ($ticks * $scale) < $max) {
$ticks += 1;
}
print "starting_point = " . $starting_point . "\n";
// result calculation
$result = [];
for ($x = 0; $x <= $ticks; $x++) {
$result[] = $starting_point + ($x * $scale);
}
return $result;
}
For anyone who need this in ES5 Javascript, been wrestling a bit, but here it is:
var min=52;
var max=173;
var actualHeight=500; // 500 pixels high graph
var tickCount =Math.round(actualHeight/100);
// we want lines about every 100 pixels.
if(tickCount <3) tickCount =3;
var range=Math.abs(max-min);
var unroundedTickSize = range/(tickCount-1);
var x = Math.ceil(Math.log10(unroundedTickSize)-1);
var pow10x = Math.pow(10, x);
var roundedTickRange = Math.ceil(unroundedTickSize / pow10x) * pow10x;
var min_rounded=roundedTickRange * Math.floor(min/roundedTickRange);
var max_rounded= roundedTickRange * Math.ceil(max/roundedTickRange);
var nr=tickCount;
var str="";
for(var x=min_rounded;x<=max_rounded;x+=roundedTickRange)
{
str+=x+", ";
}
console.log("nice Y axis "+str);
Based on the excellent answer by Toon Krijtje.
This solution is based on a Java example I found.
const niceScale = ( minPoint, maxPoint, maxTicks) => {
const niceNum = ( localRange, round) => {
var exponent,fraction,niceFraction;
exponent = Math.floor(Math.log10(localRange));
fraction = localRange / Math.pow(10, exponent);
if (round) {
if (fraction < 1.5) niceFraction = 1;
else if (fraction < 3) niceFraction = 2;
else if (fraction < 7) niceFraction = 5;
else niceFraction = 10;
} else {
if (fraction <= 1) niceFraction = 1;
else if (fraction <= 2) niceFraction = 2;
else if (fraction <= 5) niceFraction = 5;
else niceFraction = 10;
}
return niceFraction * Math.pow(10, exponent);
}
const result = [];
const range = niceNum(maxPoint - minPoint, false);
const stepSize = niceNum(range / (maxTicks - 1), true);
const lBound = Math.floor(minPoint / stepSize) * stepSize;
const uBound = Math.ceil(maxPoint / stepSize) * stepSize;
for(let i=lBound;i<=uBound;i+=stepSize) result.push(i);
return result;
};
console.log(niceScale(15,234,6));
// > [0, 100, 200, 300]
Based on #Gamecat's algorithm, I produced the following helper class
public struct Interval
{
public readonly double Min, Max, TickRange;
public static Interval Find(double min, double max, int tickCount, double padding = 0.05)
{
double range = max - min;
max += range*padding;
min -= range*padding;
var attempts = new List<Interval>();
for (int i = tickCount; i > tickCount / 2; --i)
attempts.Add(new Interval(min, max, i));
return attempts.MinBy(a => a.Max - a.Min);
}
private Interval(double min, double max, int tickCount)
{
var candidates = (min <= 0 && max >= 0 && tickCount <= 8) ? new[] {2, 2.5, 3, 4, 5, 7.5, 10} : new[] {2, 2.5, 5, 10};
double unroundedTickSize = (max - min) / (tickCount - 1);
double x = Math.Ceiling(Math.Log10(unroundedTickSize) - 1);
double pow10X = Math.Pow(10, x);
TickRange = RoundUp(unroundedTickSize/pow10X, candidates) * pow10X;
Min = TickRange * Math.Floor(min / TickRange);
Max = TickRange * Math.Ceiling(max / TickRange);
}
// 1 < scaled <= 10
private static double RoundUp(double scaled, IEnumerable<double> candidates)
{
return candidates.First(candidate => scaled <= candidate);
}
}
A demo of accepted answer
function tickEvery(range, ticks) {
return Math.ceil((range / ticks) / Math.pow(10, Math.ceil(Math.log10(range / ticks) - 1))) * Math.pow(10, Math.ceil(Math.log10(range / ticks) - 1));
}
function update() {
const range = document.querySelector("#range").value;
const ticks = document.querySelector("#ticks").value;
const result = tickEvery(range, ticks);
document.querySelector("#result").textContent = `With range ${range} and ${ticks} ticks, tick every ${result} for a total of ${Math.ceil(range / result)} ticks at ${new Array(Math.ceil(range / result)).fill(0).map((v, n) => Math.round(n * result)).join(", ")}`;
}
update();
<input id="range" min="1" max="10000" oninput="update()" style="width:100%" type="range" value="5000" width="40" />
<br/>
<input id="ticks" min="1" max="20" oninput="update()" type="range" style="width:100%" value="10" />
<p id="result" style="font-family:sans-serif"></p>

Resources