avoiding code redundancies for a calculation in Java - java

I have a calculation where i can sum or subtract something but this depends on a simple condition. My problem is, that I don't know how I can change the code, so i don't need to write the calculation twice just with the difference of the + or -. Hopefully somebody can helf me. Thanks in advance.
public void changePrice(Stock stock, int amount, boolean isBuying) {
double roundRandomNumber = Math.round((0.5 + Math.random()) * 100) / 100.00;
double newPrice;
//calculates plus or minus depending on the buyoption
if (isBuying) {
newPrice = Math.round((stock.getPrice() + roundRandomNumber * 0.1 * amount) * 100) / 100.00;
} else {
newPrice = Math.round((stock.getPrice() - roundRandomNumber * 0.1 * amount) * 100) / 100.00;
}
if (newPrice > 0 && newPrice < 3000) {
stock.setPrice(newPrice);
}
}

You can use a +1 or -1 coefficient based on isBuying value.
newPrice = Math.round((stock.getPrice() + (isBuying ? 1.0 : -1.0) * roundRandomNumber * 0.1 * amount) * 100) / 100.00;

You could try by extracting just the factor in the condition:
public void changePrice(Stock stock, int amount, boolean isBuying) {
double roundRandomNumber = Math.round((0.5 + Math.random()) * 100) / 100.00;
double newPrice;
//calculates plus or minus depending on the buyoption
double factor;
if (isBuying) {
factor = 1.0
} else {
factor = -1.0
}
newPrice = Math.round((stock.getPrice() + (factor * roundRandomNumber) * 0.1 * amount) * 100) / 100.00;
if (newPrice > 0 && newPrice < 3000) {
stock.setPrice(newPrice);
}
}

I come up with substitution of roundRandomNumber yielding an expression with ( / 100) * 0.1 which can simplified to * 0.001.
Also round(0.5 + x) can be ceil(x).
Or use 1 + new Random().nextInt(100).
double roundRandomNumber = Math.ceil((Math.random()) * 100) * 0.001 * amount;
double newPrice = stock.getPrice();
if (isBuying) {
newPrice += roundRandomNumber;
} else {
newPrice -= roundRandomNumber;
}
newPrice = Math.round((newPrice * 100) / 100.00;
if (newPrice > 0 && newPrice < 3000) {
stock.setPrice(newPrice);
}
For the rest it is the reserved register principle: I do stepwise things to newPrice.
The boundary condition excludes 0, which is questionable.
Of course BigDecimal should be mentioned. The complexity basically stems from wanting fixed point arithmetic with floating point. Unless you need speed, BigDecimal code can be more readable, though verbose.

Related

Can someone explain what is wrong with my code?

The tax depends on how far apart their salaries are. If the salaries are within $10,000 of each other, the tax is 5% of the total. If they are more than $10,000 apart, the tax is 5% of the larger income plus 3% of the smaller one.
Can someone explain what I'm doing wrong with my if statements that isn't letting the test pass.
public double spouseDistance(double income1, double income2)
{
double tax;
double totalincome = income1 + income2;
double incomedistance = income1 - income2;
if (incomedistance > 10000)
{
if(income1 > income2)
{
tax = income1 * 0.05 + income2 * 0.03;
}else
{
tax = income2 * 0.05 + income1 * 0.03;
}
}
else
{
tax = totalincome * 0.05;
}
return tax;
}
}
#Test
public void testSpousesDistance()
{
TaxCalculator tc = new TaxCalculator();
// The border case here is when the difference between the
// salaries is 10,000 and the value changes at 10,000.01.
// Since the order of the parameters shouldn't matter, do
// both cases with both parameter orders
assertEquals(40000*0.05, tc.spouseDistance(25000,15000), 0.001);
assertEquals(40000*0.05, tc.spouseDistance(15000,25000), 0.001);
assertEquals(30000.01*0.05 + 20000*0.03,tc.spouseDistance(20000.00,30000.01), 0.001);
assertEquals(30000.01*0.05 + 20000*0.03,tc.spouseDistance(30000.01, 20000.00), 0.001);
}
}
You want to set incomeDistance to Math.abs(income1 - income2) to get the absolute value of the difference. The way you have it, if income2 > income1 you branch to the wrong block of code.
Take your 20,000 and 30,000.01 example. At that point incomeDistance become 20,000-30,000.01, or -10,000, which is less than 10,000.
You have to make sure that the result of the incomedistance is not negative. Because if income 2 is greater than income one, in your code, the result will appear negative.
If the result is negative, multiply the result by (-1) if you don't
want to include an entire library just for a single purpose.

How to iterate a variable in Java

So Im sort of stumped on how to do the following.
I have my program outputting the correct variables in a table like format that I want. However, I dont quite understand how to make one of the variables increase in value as well.
Below is the code.
package annualsalarywithcommisions;
/**
*
* #author TDavis
*/
public class Calculations {
public double baseSalary = 55000;
public double localTotalSalary;
public double salesTarget = 165000;
public double potentialSalary;
public void PayCalculator() {
//Method for calculating the total wages paid to a sales rep.
double localSales = ComissionsWk3.Sales;
if (localSales < (.75 * salesTarget)) {
localTotalSalary = baseSalary;
//When sales are less than 75% of sales target, they only make
//the salary.
} else if ((localSales > (.75 * salesTarget)) && (localSales <= salesTarget)) {
localTotalSalary = (.14 * localSales) + baseSalary;
//Total salary is calculated with the 14% commission rate when
//75% of the salesTarget has been met.
} else if (localSales < salesTarget) {
localTotalSalary = (.216 * localSales) + baseSalary;
//Total salary is calculated with the acceleration factor and the
//base salary included when total sales exceeds the sales target.
}
}
public double getCalculatedSalary() {
return localTotalSalary; //method for returnin te' calculated salary total.
}
public double CompensationTable() {
double localSales = ComissionsWk3.Sales;
for (double counter = localSales; counter < localSales * 1.5;
counter += 5000) {
if (counter < (.75 * salesTarget)) {
potentialSalary = baseSalary;
//When sales are less than 75% of sales target, they only make
//the salary.
} else if ((localSales > (.75 * salesTarget)) && (localSales <= salesTarget)) {
potentialSalary = (.14 * localSales) + baseSalary;
//Total salary is calculated with the 14% commission rate when
//75% of the salesTarget has been met.
} else if (localSales < salesTarget) {
potentialSalary = (.216 * localSales) + baseSalary;
//Total salary is calculated with the acceleration factor and the
//base salary included when total sales exceeds the sales target.
}
System.out.println("Total Sales: " + counter + "\tTotal " + "Compensation: " + potentialSalary);
}
return 0;
}
}
What I'm stumped on is how to make the output potentialSalary come back with the calculated potentialSalary.
Essentially whenever the 5k increase happens, get the 14% or 21.6% increase added into that variables total each time it runs through the loop and set it as such.
Any ideas are very much welcomed.
I have actually figured it out - but apparently cant answer my own question. Below is the fixed code.
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package annualsalarywithcommisions;
/**
*
* #author TDavis
*/
public class Calculations {
public double baseSalary = 55000;
public double localTotalSalary;
public double salesTarget = 165000;
public double potentialSalary;
public void PayCalculator() {
//Method for calculating the total wages paid to a sales rep.
double localSales = ComissionsWk3.Sales;
if (localSales < (.75 * salesTarget)) {
localTotalSalary = baseSalary;
//When sales are less than 75% of sales target, they only make
//the salary.
} else if ((localSales > (.75 * salesTarget)) && (localSales <= salesTarget)) {
localTotalSalary = (.14 * localSales) + baseSalary;
//Total salary is calculated with the 14% commission rate when
//75% of the salesTarget has been met.
} else if (localSales < salesTarget) {
localTotalSalary = (.216 * localSales) + baseSalary;
//Total salary is calculated with the acceleration factor and the
//base salary included when total sales exceeds the sales target.
}
}
public double getCalculatedSalary() {
return localTotalSalary; //method for returnin te' calculated salary total.
}
public double CompensationTable() {
double localSales = ComissionsWk3.Sales;
for (double counter = localSales; counter < localSales * 1.5;
counter += 5000)
{
if (counter < (.75 * salesTarget)) {
potentialSalary = baseSalary;
//When sales are less than 75% of sales target, they only make
//the salary.
} else if ((counter > (.75 * salesTarget)) && (counter <= salesTarget)) {
potentialSalary = (.14 * counter) + baseSalary;
//Total salary is calculated with the 14% commission rate when
//75% of the salesTarget has been met.
} else if (counter < salesTarget) {
potentialSalary = (.216 * counter) + baseSalary;
//Total salary is calculated with the acceleration factor and the
//base salary included when total sales exceeds the sales target.
}
System.out.println("Total Sales: " + counter + "\tTotal " + "Compensation: " + potentialSalary);
}
return 0;
}
}
The third case in your if/else will never be entered because of the previous case's <=. There's not really an upper bound on potential salary so it's hard to answer, but they have the potential to make base + 21.6% it looks like. So I'd make a function that took the sales and then provided the base + 21.6% of that. The CompensationTable() and PayCalculator() functions are confusing (should not be capitalized either). It looks like you were trying to make CompensationCalculator() into your potential function, but returned 0 instead of a value.

Java calculation method is returning 1.0 instead of 0.5? What do?

When testing this code with Max 70 and Min 59 it returns 1.0 instead of 0.5. Is my formula wrong? Also the 2nd if statement is for these directions: (public static double hdd(int max, int min) that returns the HDD for a single day. If either max
or min is -999 (missing) return 0.0. If max < min return 0.0.) So idk if relevant to the problem.
/**
* Calculate heating degree day.
* #param max The highest temperature for a given day.
* #param min The lowest temperature for a given day.
* #return heating degree day data for this day.
*/
public static double hdd(int max, int min)
{
double average = (max + min) / 2;
double hdd = 0.0;
if (average < 65.0)
{
hdd = 65.0 - average;
}
else
{
hdd = 0.0;
}
if(max == -999 || min == -999)
{
hdd = 0.0;
}
else if (max < min)
{
hdd = 0.0;
}
return hdd;
Your issue is that (max + min) / 2 is an integer division, which means it gets truncated to the integer below. In this case, (70 + 59) / 2 gives 64, which you are then storing in a double. But that won't restore the missing 0.5.
You need to convert either the numerator or the denominator to a double before dividing. Writing 2.0 in place of 2 is one way to achieve this.
when you divide an integer by another integer the result will be also an integer. so it will not have a fractional part.
you can fix this by changing this line
double average = (max + min) / 2;
to this
double average = (max + min) / 2.0;

Working out taxable income Java

I have just finished a Java test at university and I know that I have answered a particular question wrong and would just like some help / clarification please?
The question was as follows:
implement a method that takes someones income and works out the tax.
If that person earns less than 7500 then tax = 0.
If that person earns between 7501 and 45000 then tax = 20%, less the 7500, which is tax free.
Finally, if that person earns above 45001 then tax = 40%, less the income in the 20% bracket, and then less the 7500 which is tax free.
As time was running short, I just did a basic if else statement showing income and tax brackets, example below.
public static double incomeTax(double income){
if(income <= 7500){
income = income * 0;
}
else if(income >= 7501 && income <= 45000){
income = income * 0.8;
}
else(income >= 45001){
income = income * 0.6;
}
return income;
}
I know that the code is not correct, no where near, but as it was coming to the end of the test I gave it a go in a hope just to get a mark for the if else statements.
I would really appreciate any help here.
Thank you.
After great feedback, this is what I came back with (with a LOT of help!!:] )...
import java.util.Scanner;
public class TaxableIncome
{
public static void main(String[] args){
netIncome();
}
public static double netIncome() {
double income = 0;
Scanner in = new Scanner(System.in);
System.out.print("Enter income: ");
income = in.nextDouble();
System.out.println();
double tax1 = 0;
double tax2 = 0;
double totalTax = tax1 + tax2;
// high income bracket
if (income > 45000) {
double part1 = income - 45000; // part = income - 45000
tax1 += part1 * 0.4; // tax = tax + part * 0.4
System.out.println("High Tax Band - Greater than 45000: " + tax1);
}
// medium income bracket
if (income > 7500) {
double part2 = income - 7500;
tax2 += part2 * 0.2;
System.out.println("Medium Tax Band - Greater than 7500: " + tax2);
}
System.out.println("Total Tax = " + (tax1 + tax2));
// tax for low income is zero, we don't need to compute anything.
return totalTax;
}
}
A simple answer would be as this:
public static double netIncome(double income) {
double tax = 0;
// high income bracket
if (income > 45000) {
double part = income - 45000;
tax += part * 0.4;
income = 45000;
}
// medium income bracket
if (income > 7500) {
double part = income - 7500;
tax += part * 0.2;
}
// tax for low income is zero, we don't need to compute anything.
return tax;
}
This way you calculate the tax for each tax bracket and sum them.
I see nothing wrong with the logic. The main issue you had was you just need to return the income tax, not the total income. So the value you needed to return was income*whateverPercentTax.
Also, I would've tried:
if(income < 7001){
}else if(income >=45001){
}else{}
But that is just me.
I would start with something like this:
public static double incomeTax(int income) {
final double band00Income = (double) Math.min(income, 7500);
final double band20Income = (double) Math.min(income - band00Income, 45000 - 7500);
final double band40Income = (double) Math.max(income - 45000, 0);
final double tax = band20Income * 0.2 + band40Income * 0.4;
return tax;
}
Note that income is an int due to a peculiarity of the tax calculation in the UK - it also solves the problem with the unspecified cases between 7500.01 and 7500.99 inclusive.
A better solution would extract constants for all the magic numbers. An even better solution would generalise the bands and rates into a table so that they can be changed easily.
A complete answer might include test cases like this:
import org.junit.Assert;
import org.junit.Test;
public class TestTax
{
public static final double DELTA = 0.1;
#Test
public void testTax() {
Assert.assertEquals(0.0, incomeTax(-3000), DELTA);
Assert.assertEquals(0.0, incomeTax(0), DELTA);
Assert.assertEquals(0.2, incomeTax(7501), DELTA);
Assert.assertEquals(3000.0, incomeTax(22500), DELTA);
Assert.assertEquals(7500.0, incomeTax(45000), DELTA);
Assert.assertEquals(7500.4, incomeTax(45001), DELTA);
Assert.assertEquals(25500.0, incomeTax(90000), DELTA);
}
public static double incomeTax(int income) {
final double band00Income = (double) Math.min(income, 7500);
final double band20Income = (double) Math.min(income - band00Income, 45000 - 7500);
final double band40Income = (double) Math.max(income - 45000, 0);
final double tax = band20Income * 0.2 + band40Income * 0.4;
return tax;
}
}
You'd have to tax things in bands. Dirty (untested) code:
public static double incomeTax(double income){
double tax = 0;
if(income > 7500){
tax += Math.min(45000-7500, income-7500)*.2;
}
if(income > 45000){
tax += (income-45000)*.4;
}
return tax;
}
You need to attempt to apply the tax rates more than once. Your code only hits one tax bracket. If I made 100k, I would have been taxed at 40% for the whole 100k. Here is something I came up with quickly.
public static double incomeTax(double income)
{
double tax = 0.0;
int midLevel = 7500;
int highLevel = 45000;
if (income <= 7500)
{
// Could early exit at this point and return already set tax
tax = 0.0;
}
if (income > midLevel)
{
// Find range of income > 7500, less than 4500. 37499 max
double taxableIncome = Math.min(income - midLevel, highLevel - midLevel - 1);
tax += taxableIncome * 0.2;
}
if (income > highLevel)
{
// Income above 45k
double taxableIncome = income - highLevel;
tax += taxableIncome * 0.4;
}
return tax;
}
You could try this, just copy your income brackets in the bracket array. Make sure you have infinity in bracket array and start with 0 in rate array.
import java.util.Scanner;
import java.text.DecimalFormat;
class IncomeTaxWithBrackets {
public static void main(String[] args) {
double infinity = Double.POSITIVE_INFINITY;
double [] bracket = {0, 7565, 25903, 54987, 121121, 567894, infinity};
double [] rate = {0, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35};
// bracket[0] to bracket[1] are assigned rate[1]
double income = 0;
double tax = 0;
System.out.print("\nPlease enter your income: ");
Scanner keyboard = new Scanner (System.in);
DecimalFormat dollar = new DecimalFormat ("$#,##0.00");
income = keyboard.nextDouble();
int x,i;
for (i=0; i <= 5; i++) {
if (income > bracket[i] && income <= bracket[i+1]) {
for (x=0; x<i; ++x) {
tax = tax + (bracket[x+1] - bracket[x]) * rate[x+1];
}
tax = tax + (income - bracket[i]) * rate[i+1];
}
}
System.out.println("\nWith a taxable income of "+dollar.format(income)+", your personal income tax is "+dollar.format(tax));
}
}
Let me know what you think.

Nice Label Algorithm for Charts with minimum ticks

I need to calculate the Ticklabels and the Tickrange for charts manually.
I know the "standard" algorithm for nice ticks (see http://books.google.de/books?id=fvA7zLEFWZgC&pg=PA61&lpg=PA61&redir_esc=y#v=onepage&q&f=false) and I also know this Java implementation.
The problem is, that with this algorithm, the ticks are "too smart". That means, The algorithm decides how much ticks should be displayed. My requirement is, that there are always 5 Ticks, but these should of course be "pretty". The naive approach would be to get the maximum value, divide with 5 and multiply with the ticknumber. The values here are - of course - not optimal and the ticks are pretty ugly.
Does anyone know a solution for the problem or have a hint for a formal algorithm description?
I am the author of "Algorithm for Optimal Scaling on a Chart Axis". It used to be hosted on trollop.org, but I have recently moved domains/blogging engines. Anyhow, I'll post the contents here for easier access.
I've been working on an Android charting application for an assignment and ran into a bit of an issue when it came to presenting the chart in a nicely scaled format. I spent a some time trying to create this algorithm on my own and came awfully close, but in the end I found a pseudo-code example in a book called "Graphics Gems, Volume 1" by Andrew S. Glassner. An excellent description of the problem is given in the chapter on "Nice Numbers for Graph Labels":
When creating a graph by computer, it is desirable to label the x and
y axes with "nice" numbers: simple decimal numbers. For example, if
the data range is 105 to 543, we'd probably want to plot the range
from 100 to 600 and put tick marks every 100 units. Or if the data
range is 2.04 to 2.16, we'd probably plot a range from 2.00 to 2.20
with a tick spacing of 0.05. Humans are good at choosing such "nice"
numbers, but simplistic algorithms are not. The naïve label-selection
algorithm takes the data range and divides it into n equal intervals,
but this usually results in ugly tick labels. We here describe a
simple method for generating nice graph labels.
The primary observation is that the "nicest" numbers in decimal are 1,
2, and 5, and all power-of-ten multiples of these numbers. We will use
only such numbers for the tick spacing, and place tick marks at
multiples of the tick spacing...
I used the pseudo-code example in this book to create the following class in Java:
public class NiceScale {
private double minPoint;
private double maxPoint;
private double maxTicks = 10;
private double tickSpacing;
private double range;
private double niceMin;
private double niceMax;
/**
* Instantiates a new instance of the NiceScale class.
*
* #param min the minimum data point on the axis
* #param max the maximum data point on the axis
*/
public NiceScale(double min, double max) {
this.minPoint = min;
this.maxPoint = max;
calculate();
}
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
private void calculate() {
this.range = niceNum(maxPoint - minPoint, false);
this.tickSpacing = niceNum(range / (maxTicks - 1), true);
this.niceMin =
Math.floor(minPoint / tickSpacing) * tickSpacing;
this.niceMax =
Math.ceil(maxPoint / tickSpacing) * tickSpacing;
}
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling if round = false.
*
* #param range the data range
* #param round whether to round the result
* #return a "nice" number to be used for the data range
*/
private double niceNum(double range, boolean round) {
double exponent; /** exponent of range */
double fraction; /** fractional part of range */
double niceFraction; /** nice, rounded fraction */
exponent = Math.floor(Math.log10(range));
fraction = range / 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);
}
/**
* Sets the minimum and maximum data points for the axis.
*
* #param minPoint the minimum data point on the axis
* #param maxPoint the maximum data point on the axis
*/
public void setMinMaxPoints(double minPoint, double maxPoint) {
this.minPoint = minPoint;
this.maxPoint = maxPoint;
calculate();
}
/**
* Sets maximum number of tick marks we're comfortable with
*
* #param maxTicks the maximum number of tick marks for the axis
*/
public void setMaxTicks(double maxTicks) {
this.maxTicks = maxTicks;
calculate();
}
}
We can then make use of the above code like this:
NiceScale numScale = new NiceScale(-0.085, 0.173);
System.out.println("Tick Spacing:\t" + numScale.getTickSpacing());
System.out.println("Nice Minimum:\t" + numScale.getNiceMin());
System.out.println("Nice Maximum:\t" + numScale.getNiceMax());
Which will then output nicely formatted numbers for use in whatever application for which you need to create pretty scales. =D
Tick Spacing: 0.05
Nice Minimum: -0.1
Nice Maximum: 0.2
Here is a javascript version:
var minPoint;
var maxPoint;
var maxTicks = 10;
var tickSpacing;
var range;
var niceMin;
var niceMax;
/**
* Instantiates a new instance of the NiceScale class.
*
* min the minimum data point on the axis
* max the maximum data point on the axis
*/
function niceScale( min, max) {
minPoint = min;
maxPoint = max;
calculate();
return {
tickSpacing: tickSpacing,
niceMinimum: niceMin,
niceMaximum: niceMax
};
}
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
function calculate() {
range = niceNum(maxPoint - minPoint, false);
tickSpacing = niceNum(range / (maxTicks - 1), true);
niceMin =
Math.floor(minPoint / tickSpacing) * tickSpacing;
niceMax =
Math.ceil(maxPoint / tickSpacing) * tickSpacing;
}
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling if round = false.
*
* localRange the data range
* round whether to round the result
* a "nice" number to be used for the data range
*/
function niceNum( localRange, round) {
var exponent; /** exponent of localRange */
var fraction; /** fractional part of localRange */
var niceFraction; /** nice, rounded fraction */
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);
}
/**
* Sets the minimum and maximum data points for the axis.
*
* minPoint the minimum data point on the axis
* maxPoint the maximum data point on the axis
*/
function setMinMaxPoints( localMinPoint, localMaxPoint) {
minPoint = localMinPoint;
maxPoint = localMaxoint;
calculate();
}
/**
* Sets maximum number of tick marks we're comfortable with
*
* maxTicks the maximum number of tick marks for the axis
*/
function setMaxTicks(localMaxTicks) {
maxTicks = localMaxTicks;
calculate();
}
Enjoy!
I have converted above java code to Python as per my requirement.
import math
class NiceScale:
def __init__(self, minv,maxv):
self.maxTicks = 6
self.tickSpacing = 0
self.lst = 10
self.niceMin = 0
self.niceMax = 0
self.minPoint = minv
self.maxPoint = maxv
self.calculate()
def calculate(self):
self.lst = self.niceNum(self.maxPoint - self.minPoint, False)
self.tickSpacing = self.niceNum(self.lst / (self.maxTicks - 1), True)
self.niceMin = math.floor(self.minPoint / self.tickSpacing) * self.tickSpacing
self.niceMax = math.ceil(self.maxPoint / self.tickSpacing) * self.tickSpacing
def niceNum(self, lst, rround):
self.lst = lst
exponent = 0 # exponent of range */
fraction = 0 # fractional part of range */
niceFraction = 0 # nice, rounded fraction */
exponent = math.floor(math.log10(self.lst));
fraction = self.lst / math.pow(10, exponent);
if (self.lst):
if (fraction < 1.5):
niceFraction = 1
elif (fraction < 3):
niceFraction = 2
elif (fraction < 7):
niceFraction = 5;
else:
niceFraction = 10;
else :
if (fraction <= 1):
niceFraction = 1
elif (fraction <= 2):
niceFraction = 2
elif (fraction <= 5):
niceFraction = 5
else:
niceFraction = 10
return niceFraction * math.pow(10, exponent)
def setMinMaxPoints(self, minPoint, maxPoint):
self.minPoint = minPoint
self.maxPoint = maxPoint
self.calculate()
def setMaxTicks(self, maxTicks):
self.maxTicks = maxTicks;
self.calculate()
a=NiceScale(14024, 17756)
print "a.lst ", a.lst
print "a.maxPoint ", a.maxPoint
print "a.maxTicks ", a.maxTicks
print "a.minPoint ", a.minPoint
print "a.niceMax ", a.niceMax
print "a.niceMin ", a.niceMin
print "a.tickSpacing ", a.tickSpacing
This is the Swift version:
class NiceScale {
private var minPoint: Double
private var maxPoint: Double
private var maxTicks = 10
private(set) var tickSpacing: Double = 0
private(set) var range: Double = 0
private(set) var niceMin: Double = 0
private(set) var niceMax: Double = 0
init(min: Double, max: Double) {
minPoint = min
maxPoint = max
calculate()
}
func setMinMaxPoints(min: Double, max: Double) {
minPoint = min
maxPoint = max
calculate()
}
private func calculate() {
range = niceNum(maxPoint - minPoint, round: false)
tickSpacing = niceNum(range / Double((maxTicks - 1)), round: true)
niceMin = floor(minPoint / tickSpacing) * tickSpacing
niceMax = floor(maxPoint / tickSpacing) * tickSpacing
}
private func niceNum(range: Double, round: Bool) -> Double {
let exponent = floor(log10(range))
let fraction = range / pow(10, exponent)
let niceFraction: Double
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 * pow(10, exponent)
}
}
Here's the C++ version. As a bonus you get a function that returns the minimum number of decimal points to display the tick labels on the axis.
The header file:
class NiceScale
{ public:
float minPoint;
float maxPoint;
float maxTicks;
float tickSpacing;
float range;
float niceMin;
float niceMax;
public:
NiceScale()
{ maxTicks = 10;
}
/**
* Instantiates a new instance of the NiceScale class.
*
* #param min the minimum data point on the axis
* #param max the maximum data point on the axis
*/
NiceScale(float min, float max)
{ minPoint = min;
maxPoint = max;
calculate();
}
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
void calculate() ;
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling if round = false.
*
* #param range the data range
* #param round whether to round the result
* #return a "nice" number to be used for the data range
*/
float niceNum(float range, boolean round) ;
/**
* Sets the minimum and maximum data points for the axis.
*
* #param minPoint the minimum data point on the axis
* #param maxPoint the maximum data point on the axis
*/
void setMinMaxPoints(float minPoint, float maxPoint) ;
/**
* Sets maximum number of tick marks we're comfortable with
*
* #param maxTicks the maximum number of tick marks for the axis
*/
void setMaxTicks(float maxTicks) ;
int decimals(void);
};
And the CPP file:
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
void NiceScale::calculate()
{
range = niceNum(maxPoint - minPoint, false);
tickSpacing = niceNum(range / (maxTicks - 1), true);
niceMin = floor(minPoint / tickSpacing) * tickSpacing;
niceMax = ceil(maxPoint / tickSpacing) * tickSpacing;
}
/**
* Returns a "nice" number approximately equal to range
Rounds the number if round = true Takes the ceiling if round = false.
*
* #param range the data range
* #param round whether to round the result
* #return a "nice" number to be used for the data range
*/
float NiceScale::niceNum(float range, boolean round)
{ float exponent; /** exponent of range */
float fraction; /** fractional part of range */
float niceFraction; /** nice, rounded fraction */
exponent = floor(log10(range));
fraction = range / pow(10.f, 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 * pow(10, exponent);
}
/**
* Sets the minimum and maximum data points for the axis.
*
* #param minPoint the minimum data point on the axis
* #param maxPoint the maximum data point on the axis
*/
void NiceScale::setMinMaxPoints(float minPoint, float maxPoint)
{
this->minPoint = minPoint;
this->maxPoint = maxPoint;
calculate();
}
/**
* Sets maximum number of tick marks we're comfortable with
*
* #param maxTicks the maximum number of tick marks for the axis
*/
void NiceScale::setMaxTicks(float maxTicks)
{
this->maxTicks = maxTicks;
calculate();
}
// minimum number of decimals in tick labels
// use in sprintf statement:
// sprintf(buf, "%.*f", decimals(), tickValue);
int NiceScale::decimals(void)
{
float logTickX = log10(tickSpacing);
if(logTickX >= 0)
return 0;
return (int)(abs(floor(logTickX)));
}
You should be able to use the Java implementation with minor corrections.
Change maxticks to 5.
Change the calculate mehod to this:
private void calculate() {
this.range = niceNum(maxPoint - minPoint, false);
this.tickSpacing = niceNum(range / (maxTicks - 1), true);
this.niceMin =
Math.floor(minPoint / tickSpacing) * tickSpacing;
this.niceMax = this.niceMin + tickSpacing * (maxticks - 1); // Always display maxticks
}
Disclaimer: Note that I haven't tested this, so you may have to tweak it to make it look good. My suggested solution adds extra space at the top of the chart to always make room for 5 ticks. This may look ugly in some cases.
Here is the same thing in Objective C
YFRNiceScale.h
#import <Foundation/Foundation.h>
#interface YFRNiceScale : NSObject
#property (nonatomic, readonly) CGFloat minPoint;
#property (nonatomic, readonly) CGFloat maxPoint;
#property (nonatomic, readonly) CGFloat maxTicks;
#property (nonatomic, readonly) CGFloat tickSpacing;
#property (nonatomic, readonly) CGFloat range;
#property (nonatomic, readonly) CGFloat niceRange;
#property (nonatomic, readonly) CGFloat niceMin;
#property (nonatomic, readonly) CGFloat niceMax;
- (id) initWithMin: (CGFloat) min andMax: (CGFloat) max;
- (id) initWithNSMin: (NSDecimalNumber*) min andNSMax: (NSDecimalNumber*) max;
#end
YFRNiceScale.m
#import "YFRNiceScale.h"
#implementation YFRNiceScale
#synthesize minPoint = _minPoint;
#synthesize maxPoint = _maxPoint;
#synthesize maxTicks = _maxTicks;
#synthesize tickSpacing = _tickSpacing;
#synthesize range = _range;
#synthesize niceRange = _niceRange;
#synthesize niceMin = _niceMin;
#synthesize niceMax = _niceMax;
- (id)init {
self = [super init];
if (self) {
}
return self;
}
- (id) initWithMin: (CGFloat) min andMax: (CGFloat) max {
if (self) {
_maxTicks = 10;
_minPoint = min;
_maxPoint = max;
[self calculate];
}
return [self init];
}
- (id) initWithNSMin: (NSDecimalNumber*) min andNSMax: (NSDecimalNumber*) max {
if (self) {
_maxTicks = 10;
_minPoint = [min doubleValue];
_maxPoint = [max doubleValue];
[self calculate];
}
return [self init];
}
/**
* Calculate and update values for tick spacing and nice minimum and maximum
* data points on the axis.
*/
- (void) calculate {
_range = [self niceNumRange: (_maxPoint-_minPoint) roundResult:NO];
_tickSpacing = [self niceNumRange: (_range / (_maxTicks - 1)) roundResult:YES];
_niceMin = floor(_minPoint / _tickSpacing) * _tickSpacing;
_niceMax = ceil(_maxPoint / _tickSpacing) * _tickSpacing;
_niceRange = _niceMax - _niceMin;
}
/**
* Returns a "nice" number approximately equal to range Rounds the number if
* round = true Takes the ceiling if round = false.
*
* #param range
* the data range
* #param round
* whether to round the result
* #return a "nice" number to be used for the data range
*/
- (CGFloat) niceNumRange:(CGFloat) aRange roundResult:(BOOL) round {
CGFloat exponent;
CGFloat fraction;
CGFloat niceFraction;
exponent = floor(log10(aRange));
fraction = aRange / 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 = 2;
} else {
niceFraction = 10;
}
}
return niceFraction * pow(10, exponent);
}
- (NSString*) description {
return [NSString stringWithFormat:#"NiceScale [minPoint=%.2f, maxPoint=%.2f, maxTicks=%.2f, tickSpacing=%.2f, range=%.2f, niceMin=%.2f, niceMax=%.2f]", _minPoint, _maxPoint, _maxTicks, _tickSpacing, _range, _niceMin, _niceMax ];
}
#end
Usage:
YFRNiceScale* niceScale = [[YFRNiceScale alloc] initWithMin:0 andMax:500];
NSLog(#"Nice: %#", niceScale);
Here it is in TypeScript!
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
function calculateTicks(maxTicks: number, minPoint: number, maxPoint: number): [number, number, number] {
let range = niceNum(maxPoint - minPoint, false);
let tickSpacing = niceNum(range / (maxTicks - 1), true);
let niceMin = Math.floor(minPoint / tickSpacing) * tickSpacing;
let niceMax = Math.ceil(maxPoint / tickSpacing) * tickSpacing;
let tickCount = range / tickSpacing;
return [tickCount, niceMin, niceMax];
}
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling if round = false.
*
* #param range the data range
* #param round whether to round the result
* #return a "nice" number to be used for the data range
*/
function niceNum(range: number, round: boolean): number {
let exponent: number;
/** exponent of range */
let fraction: number;
/** fractional part of range */
let niceFraction: number;
/** nice, rounded fraction */
exponent = Math.floor(Math.log10(range));
fraction = range / 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);
}
I found this thread while writing some php, so now the same code is available in php too!
class CNiceScale {
private $minPoint;
private $maxPoint;
private $maxTicks = 10;
private $tickSpacing;
private $range;
private $niceMin;
private $niceMax;
public function setScale($min, $max) {
$this->minPoint = $min;
$this->maxPoint = $max;
$this->calculate();
}
private function calculate() {
$this->range = $this->niceNum($this->maxPoint - $this->minPoint, false);
$this->tickSpacing = $this->niceNum($this->range / ($this->maxTicks - 1), true);
$this->niceMin = floor($this->minPoint / $this->tickSpacing) * $this->tickSpacing;
$this->niceMax = ceil($this->maxPoint / $this->tickSpacing) * $this->tickSpacing;
}
private function niceNum($range, $round) {
$exponent; /** exponent of range */
$fraction; /** fractional part of range */
$niceFraction; /** nice, rounded fraction */
$exponent = floor(log10($range));
$fraction = $range / 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 * pow(10, $exponent);
}
public function setMinMaxPoints($minPoint, $maxPoint) {
$this->minPoint = $minPoint;
$this->maxPoint = $maxPoint;
$this->calculate();
}
public function setMaxTicks($maxTicks) {
$this->maxTicks = $maxTicks;
$this->calculate();
}
public function getTickSpacing() {
return $this->tickSpacing;
}
public function getNiceMin() {
return $this->niceMin;
}
public function getNiceMax() {
return $this->niceMax;
}
}
I needed this algorithm converted to C#, so here it is...
public static class NiceScale {
public static void Calculate(double min, double max, int maxTicks, out double range, out double tickSpacing, out double niceMin, out double niceMax) {
range = niceNum(max - min, false);
tickSpacing = niceNum(range / (maxTicks - 1), true);
niceMin = Math.Floor(min / tickSpacing) * tickSpacing;
niceMax = Math.Ceiling(max / tickSpacing) * tickSpacing;
}
private static double niceNum(double range, bool round) {
double pow = Math.Pow(10, Math.Floor(Math.Log10(range)));
double fraction = range / pow;
double niceFraction;
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 * pow;
}
}
Here'a better organized C# code.
public class NiceScale
{
public double NiceMin { get; set; }
public double NiceMax { get; set; }
public double TickSpacing { get; private set; }
private double _minPoint;
private double _maxPoint;
private double _maxTicks = 5;
private double _range;
/**
* Instantiates a new instance of the NiceScale class.
*
* #param min the minimum data point on the axis
* #param max the maximum data point on the axis
*/
public NiceScale(double min, double max)
{
_minPoint = min;
_maxPoint = max;
Calculate();
}
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
private void Calculate()
{
_range = NiceNum(_maxPoint - _minPoint, false);
TickSpacing = NiceNum(_range / (_maxTicks - 1), true);
NiceMin = Math.Floor(_minPoint / TickSpacing) * TickSpacing;
NiceMax = Math.Ceiling(_maxPoint / TickSpacing) * TickSpacing;
}
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling if round = false.
*
* #param range the data range
* #param round whether to round the result
* #return a "nice" number to be used for the data range
*/
private double NiceNum(double range, bool round)
{
double exponent; /** exponent of range */
double fraction; /** fractional part of range */
double niceFraction; /** nice, rounded fraction */
exponent = Math.Floor(Math.Log10(range));
fraction = range / 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);
}
/**
* Sets the minimum and maximum data points for the axis.
*
* #param minPoint the minimum data point on the axis
* #param maxPoint the maximum data point on the axis
*/
public void SetMinMaxPoints(double minPoint, double maxPoint)
{
_minPoint = minPoint;
_maxPoint = maxPoint;
Calculate();
}
/**
* Sets maximum number of tick marks we're comfortable with
*
* #param maxTicks the maximum number of tick marks for the axis
*/
public void SetMaxTicks(double maxTicks)
{
_maxTicks = maxTicks;
Calculate();
}
}
Here's the Kotlin version!
import java.lang.Math.*
/**
* Instantiates a new instance of the NiceScale class.
*
* #param min Double The minimum data point.
* #param max Double The maximum data point.
*/
class NiceScale(private var minPoint: Double, private var maxPoint: Double) {
private var maxTicks = 15.0
private var range: Double = 0.0
var niceMin: Double = 0.0
var niceMax: Double = 0.0
var tickSpacing: Double = 0.0
init {
calculate()
}
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
private fun calculate() {
range = niceNum(maxPoint - minPoint, false)
tickSpacing = niceNum(range / (maxTicks - 1), true)
niceMin = floor(minPoint / tickSpacing) * tickSpacing
niceMax = ceil(maxPoint / tickSpacing) * tickSpacing
}
/**
* Returns a "nice" number approximately equal to range. Rounds
* the number if round = true. Takes the ceiling if round = false.
*
* #param range Double The data range.
* #param round Boolean Whether to round the result.
* #return Double A "nice" number to be used for the data range.
*/
private fun niceNum(range: Double, round: Boolean): Double {
/** Exponent of range */
val exponent: Double = floor(log10(range))
/** Fractional part of range */
val fraction: Double
/** Nice, rounded fraction */
val niceFraction: Double
fraction = range / pow(10.0, exponent)
niceFraction = if (round) {
when {
fraction < 1.5 -> 1.0
fraction < 3 -> 2.0
fraction < 7 -> 5.0
else -> 10.0
}
} else {
when {
fraction <= 1 -> 1.0
fraction <= 2 -> 2.0
fraction <= 5 -> 5.0
else -> 10.0
}
}
return niceFraction * pow(10.0, exponent)
}
/**
* Sets the minimum and maximum data points.
*
* #param minPoint Double The minimum data point.
* #param maxPoint Double The maximum data point.
*/
fun setMinMaxPoints(minPoint: Double, maxPoint: Double) {
this.minPoint = minPoint
this.maxPoint = maxPoint
calculate()
}
/**
* Sets maximum number of tick marks we're comfortable with.
*
* #param maxTicks Double The maximum number of tick marks.
*/
fun setMaxTicks(maxTicks: Double) {
this.maxTicks = maxTicks
calculate()
}
}
Since everybody and his dog is posting a translation to other popular languages, here is my version for the Nim programming language. I also added handling of cases where the amount of ticks is less than two:
import math, strutils
const
defaultMaxTicks = 10
type NiceScale = object
minPoint: float
maxPoint: float
maxTicks: int
tickSpacing: float
niceMin: float
niceMax: float
proc ff(x: float): string =
result = x.formatFloat(ffDecimal, 3)
proc `$`*(x: NiceScale): string =
result = "Input minPoint: " & x.minPoint.ff &
"\nInput maxPoint: " & x.maxPoint.ff &
"\nInput maxTicks: " & $x.maxTicks &
"\nOutput niceMin: " & x.niceMin.ff &
"\nOutput niceMax: " & x.niceMax.ff &
"\nOutput tickSpacing: " & x.tickSpacing.ff &
"\n"
proc calculate*(x: var NiceScale)
proc init*(x: var NiceScale; minPoint, maxPoint: float;
maxTicks = defaultMaxTicks) =
x.minPoint = minPoint
x.maxPoint = maxPoint
x.maxTicks = maxTicks
x.calculate
proc initScale*(minPoint, maxPoint: float;
maxTicks = defaultMaxTicks): NiceScale =
result.init(minPoint, maxPoint, maxTicks)
proc niceNum(scaleRange: float; doRound: bool): float =
var
exponent: float ## Exponent of scaleRange.
fraction: float ## Fractional part of scaleRange.
niceFraction: float ## Nice, rounded fraction.
exponent = floor(log10(scaleRange));
fraction = scaleRange / pow(10, exponent);
if doRound:
if fraction < 1.5:
niceFraction = 1
elif fraction < 3:
niceFraction = 2
elif fraction < 7:
niceFraction = 5
else:
niceFraction = 10
else:
if fraction <= 1:
niceFraction = 1
elif fraction <= 2:
niceFraction = 2
elif fraction <= 5:
niceFraction = 5
else:
niceFraction = 10
return niceFraction * pow(10, exponent)
proc calculate*(x: var NiceScale) =
assert x.maxPoint > x.minPoint, "Wrong input range!"
assert x.maxTicks >= 0, "Sorry, can't have imaginary ticks!"
let scaleRange = niceNum(x.maxPoint - x.minPoint, false)
if x.maxTicks < 2:
x.niceMin = floor(x.minPoint)
x.niceMax = ceil(x.maxPoint)
x.tickSpacing = (x.niceMax - x.niceMin) /
(if x.maxTicks == 1: 2.0 else: 1.0)
else:
x.tickSpacing = niceNum(scaleRange / (float(x.maxTicks - 1)), true)
x.niceMin = floor(x.minPoint / x.tickSpacing) * x.tickSpacing
x.niceMax = ceil(x.maxPoint / x.tickSpacing) * x.tickSpacing
when isMainModule:
var s = initScale(57.2, 103.3)
echo s
This is the comment stripped version. Full one can be read at GitHub integrated into my project.
//Swift, more compact
public struct NiceScale
{
var minPoint: Double
var maxPoint: Double
var maxTicks = 10
var tickSpacing: Double { niceNum(range: range / Double(maxTicks - 1), round: true) }
var range: Double { niceNum(range: maxPoint - minPoint, round: false) }
var niceMin: Double { floor(minPoint / tickSpacing) * tickSpacing }
var niceMax: Double { ceil(maxPoint / tickSpacing) * tickSpacing }
// min the minimum data point on the axis
// max the maximum data point on the axis
init( min: Double, max: Double, maxTicks: Int = 10)
{
minPoint = min
maxPoint = max
self.maxTicks = maxTicks
}
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling
* if round = false range the data range
* round whether to round the result
* return a "nice" number to be used for the data range
*/
func niceNum( range: Double, round: Bool) -> Double
{
let exponent: Double = floor(log10(range)) // exponent of range
let fraction: Double = range / pow(10, exponent) // fractional part of range
var niceFraction: Double = 10.0 // nice, rounded fraction
if round {
if fraction < 1.5 { niceFraction = 1 }
else if fraction < 3 { niceFraction = 2 }
else if fraction < 7 { niceFraction = 5 }
} else {
if fraction <= 1 { niceFraction = 1 }
else if fraction <= 2 { niceFraction = 2 }
else if fraction <= 5 { niceFraction = 5 }
}
return niceFraction * pow(10, exponent)
}
static func testNiceScale()
{
var numScale = NiceScale(min: -0.085, max: 0.173)
print("Tick Spacing:\t \( numScale.tickSpacing)")
print("Nice Minimum:\t\( numScale.niceMin)")
print("Nice Maximum:\t\( numScale.niceMax)")
numScale = NiceScale(min: 4.44, max: 7.962)
print("nice num:\t\( numScale.niceNum(range: 7.962 - 4.44, round: false))")
print("Tick Spacing:\t\( numScale.tickSpacing)")
print("Nice Minimum:\t\( numScale.niceMin)")
print("Nice Maximum:\t\( numScale.niceMax)")
}
}
<\pre> <\code>
This is the VB.NET version.
Public Class NiceScale
Private minPoint As Double
Private maxPoint As Double
Private maxTicks As Double = 10
Private tickSpacing
Private range As Double
Private niceMin As Double
Private niceMax As Double
Public Sub New(min As Double, max As Double)
minPoint = min
maxPoint = max
calculate()
End Sub
Private Sub calculate()
range = niceNum(maxPoint - minPoint, False)
tickSpacing = niceNum(range / (maxTicks - 1), True)
niceMin = Math.Floor(minPoint / tickSpacing) * tickSpacing
niceMax = Math.Ceiling(maxPoint / tickSpacing) * tickSpacing
End Sub
Private Function niceNum(range As Double, round As Boolean) As Double
Dim exponent As Double '/** exponent of range */
Dim fraction As Double '/** fractional part of range */
Dim niceFraction As Double '/** nice, rounded fraction */
exponent = Math.Floor(Math.Log10(range))
fraction = range / Math.Pow(10, exponent)
If round Then
If (fraction < 1.5) Then
niceFraction = 1
ElseIf (fraction < 3) Then
niceFraction = 2
ElseIf (fraction < 7) Then
niceFraction = 5
Else
niceFraction = 10
End If
Else
If (fraction <= 1) Then
niceFraction = 1
ElseIf (fraction <= 2) Then
niceFraction = 2
ElseIf (fraction <= 5) Then
niceFraction = 5
Else
niceFraction = 10
End If
End If
Return niceFraction * Math.Pow(10, exponent)
End Function
Public Sub setMinMaxPoints(minPoint As Double, maxPoint As Double)
minPoint = minPoint
maxPoint = maxPoint
calculate()
End Sub
Public Sub setMaxTicks(maxTicks As Double)
maxTicks = maxTicks
calculate()
End Sub
Public Function getTickSpacing() As Double
Return tickSpacing
End Function
Public Function getNiceMin() As Double
Return niceMin
End Function
Public Function getNiceMax() As Double
Return niceMax
End Function
End Class
Much BETTER and SIMPLER algorythm on swift. Size is fixed, values are not "hardcoded":
class NiceNumbers {
/// Returns nice range of specified size. Result min <= min argument, result max >= max argument.
static func getRange(forMin minInt: Int, max maxInt: Int, ofSize size: Int) -> [Int] {
let niceMinInt = getMinCloseToZero(min: minInt, max: maxInt)
let step = Double(maxInt - niceMinInt) / Double(size - 1)
let niceStepInt = Int(get(for: step, min: false))
var result = [Int]()
result.append(niceMinInt)
for i in 1...size - 1 {
result.append(niceMinInt + i * Int(niceStepInt))
}
return result
}
/// Returns nice min or zero if it is much smaller than max.
static func getMinCloseToZero(min: Int, max: Int) -> Int {
let nice = get(for: Double(min), min: true)
return nice <= (Double(max) / 10) ? 0 : Int(nice)
}
/// Get nice number. If min is true returns smaller number, if false - bigger one.
static func get(for number: Double, min: Bool) -> Double {
if number == 0 { return 0 }
let exponent = floor(log10(number)) - (min ? 0 : 1)
let fraction = number / pow(10, exponent)
let niceFraction = min ? floor(fraction) : ceil(fraction)
return niceFraction * pow(10, exponent)
}
}
Tested only on positive numbers.
Dart / Flutter Version:
import 'dart:math';
void main() {
double min = 3, max = 28;
var scale = NiceScale(min, max, 5);
print("Range: $min-$max; Max Point: ${scale.niceMax}; Min Point: ${scale.niceMin}; Steps: ${scale.tickSpacing};");
}
class NiceScale {
double _niceMin, _niceMax;
double _tickSpacing;
double get tickSpacing { return _tickSpacing; }
double get niceMin{ return _niceMin; }
double get niceMax{ return _niceMax; }
double _minPoint, _maxPoint;
double _maxTicks;
double _range;
NiceScale(double minP, double maxP, double maxTicks){
this._minPoint = minP;
this._maxPoint = maxP;
this._maxTicks = maxTicks;
_calculate();
}
void _calculate(){
_range = _niceNum(_maxPoint - _minPoint, false);
_tickSpacing = _niceNum(_range / (_maxTicks - 1), true);
_niceMin = _calcMin();
_niceMax = _calcMax();
}
double _calcMin() {
int floored = (_minPoint / _tickSpacing).floor();
return floored * _tickSpacing;
}
double _calcMax() {
int ceiled = (_maxPoint / _tickSpacing).ceil();
return ceiled * _tickSpacing;
}
double _niceNum(double range, bool round){
double exponent; /** exponent of range */
double fraction; /** fractional part of range */
double niceFraction; /** nice, rounded fraction */
exponent = (log(range)/ln10).floor().toDouble();
fraction = range / 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 * pow(10, exponent);
}
}
Here is a Ruby version
class NiceScale
attr_accessor :min_point, :max_point
attr_reader :tick_spacing, :nice_min, :nice_max
def initialize(options = {})
#min_point = options[:min_point]
#max_point = options[:max_point]
#max_ticks = [(options[:max_ticks] || 5), 2].max
self.calculate
end
def calculate
range = nice_num(#max_point - #min_point, false)
#tick_spacing = nice_num(range / (#max_ticks - 1))
#nice_min = (#min_point / tick_spacing).floor * tick_spacing
#nice_max = (#max_point / tick_spacing).floor * tick_spacing
end
private
def nice_num(num, round = true)
num = num.to_f
exponent = num > 0 ? Math.log10(num).floor : 0
fraction = num / (10 ** exponent)
if round
if fraction < 1.5
nice_fraction = 1
elsif fraction < 3
nice_fraction = 2
elsif fraction < 7
nice_fraction = 5
else
nice_fraction = 10
end
else
if fraction <= 1
nice_fraction = 1
elsif fraction <= 2
nice_fraction = 2
elsif fraction <= 5
nice_fraction = 5
else
nice_fraction = 10
end
end
nice_fraction.to_f * (10 ** exponent)
end
end
Another JS version:
const getTicks = {
minPoint: 0,
maxPoint: 10,
maxTicks: 10,
tickSpacing: 1,
range: 1,
niceMin: 1,
niceMax: 1,
niceScale(min, max) {
this.minPoint = min;
this.maxPoint = max;
this.calculate();
return {
tickSpacing: this.tickSpacing,
niceMinimum: this.niceMin,
niceMaximum: this.niceMax
};
},
calculate() {
this.range = this.niceNum(this.maxPoint - this.minPoint, false);
this.tickSpacing = this.niceNum(this.range / (this.maxTicks - 1), true);
this.niceMin = Math.floor(this.minPoint / this.tickSpacing) * this.tickSpacing;
this.niceMax = Math.ceil(this.maxPoint / this.tickSpacing) * this.tickSpacing;
},
niceNum(localRange, round) {
var exponent; /** exponent of localRange */
var fraction; /** fractional part of localRange */
var niceFraction; /** nice, rounded fraction */
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);
},
setMinMaxPoints(localMinPoint, localMaxPoint) {
this.minPoint = localMinPoint;
this.maxPoint = localMaxPoint;
this.calculate();
},
setMaxTicks(localMaxTicks) {
this.maxTicks = localMaxTicks;
this.calculate();
}
}
Just the code entered before, embedding it inside a JS object (I don't like the global vars). It can be used like this:
const tickScale = getTicks.niceScale(minValue, maxValue);
for (let i = tickScale.niceMinimum; i < tickScale.niceMaximum; i+=tickScale.tickSpacing) {
// Whatever you want to do
}
I find it quite useful this way, and easer to isolate from the rest of the code.
The solution produces nice results, but often too few tick marks depending on the min and max point values and the snapping effect.
Here's a quick hack fix to improve the tick mark spacing.
Original:
private void calculate() {
this.range = niceNum(maxPoint - minPoint, false);
this.tickSpacing = niceNum(range / (maxTicks - 1), true);
this.niceMin = Math.floor(minPoint / tickSpacing) * tickSpacing;
this.niceMax = Math.ceil(maxPoint / tickSpacing) * tickSpacing;
}
New:
private void calculate() {
this.range = niceNum(maxPoint - minPoint, false);
this.tickSpacing = niceNum(range / (maxTicks - 1), true);
// Attempt to correct tick spacing due to snapping behavior and nice method.
double tickCountF = (maxPoint - minPoint) / tickSpacing;
if( (tickCountF / maxTicks) <= 0.5)
{ tickSpacing /= 2.0; }
this.niceMin = Math.floor(minPoint / tickSpacing) * tickSpacing;
this.niceMax = Math.ceil(maxPoint / tickSpacing) * tickSpacing;
}
Here are some Swift Unit Test to verify the definition of "Nice" meets your understanding.
import XCTest
#testable import AssetsFolio
final class NiceGridLineValues_Tests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func test_NiceGridLinesValues_originalExample() throws {
let numScale = NiceGridLineValues(min: -0.085, max: 0.173)
/*
// Which will then output nicely formatted numbers for use in whatever
// application for which you need to create pretty scales. =D
Tick Spacing: 0.05
Nice Minimum: -0.1
Nice Maximum: 0.2
*/
XCTAssertEqual(0.05, numScale.tickSpacing)
XCTAssertEqual(-0.1, numScale.niceMin)
XCTAssertEqual(0.2, numScale.niceMax)
}
func test_NiceGridLinesValues_173_183_example() throws {
let numScale = NiceGridLineValues(min: 173.82, max: 182.93)
XCTAssertEqual(1.0, numScale.tickSpacing)
XCTAssertEqual(173.0, numScale.niceMin)
XCTAssertEqual(183.0, numScale.niceMax)
}
func test_NiceGridLinesValues_176_180_example() throws {
let numScale = NiceGridLineValues(min: 176.82, max: 179.99)
XCTAssertEqual(0.5, numScale.tickSpacing)
XCTAssertEqual(176.5, numScale.niceMin)
XCTAssertEqual(180.0, numScale.niceMax)
}
func test_NiceGridLinesValues_179_180_example() throws {
let numScale = NiceGridLineValues(min: 179.62, max: 179.99)
XCTAssertEqual(0.05, numScale.tickSpacing)
XCTAssertEqual(179.6, numScale.niceMin, accuracy: 0.01)
XCTAssertEqual(180.0, numScale.niceMax)
}
}

Categories

Resources