Related
I have the following program running on an ESP32 as a test:
using System;
using System.Diagnostics;
using System.Threading;
using nanoFramework.Hardware.Esp32;
namespace NFApp2
{
public class Program
{
private static long Counter;
private static long Ticks;
public static void Main()
{
Ticks = 5000;
Thread CountThread = new Thread(new ThreadStart(CountFunction));
CountThread.Start();
while (true)
{
Thread.Sleep(1000);
Debug.WriteLine(string.Format("Ticks = {0}, Count = {1}", Ticks, Counter));
Counter = 0;
Ticks += 100;
}
}
private static void CountFunction()
{
while (true)
{
Thread.Sleep(new TimeSpan(Ticks));
Counter++;
}
}
}
}
Basically a thread is started that sleeps for some time (Ticks) and then increases a counter by 1.
Every second the count is printed and reset and the sleep time of the thread is increased by 100 ticks.
When running this I get the following output:
Ticks = 5000, Count = 3445
Ticks = 5100, Count = 3435
Ticks = 5200, Count = 3405
Ticks = 5300, Count = 3405
Ticks = 5400, Count = 3403
Ticks = 5500, Count = 3406
Ticks = 5600, Count = 3405
Ticks = 5700, Count = 3404
Ticks = 5800, Count = 3403
Ticks = 5900, Count = 3403
Ticks = 6000, Count = 3403
Ticks = 6100, Count = 3406
Ticks = 6200, Count = 3402
Ticks = 6300, Count = 3404
Ticks = 6400, Count = 3403
Ticks = 6500, Count = 3403
Ticks = 6600, Count = 3405
Ticks = 6700, Count = 3406
Ticks = 6800, Count = 3403
Ticks = 6900, Count = 3401
Ticks = 7000, Count = 3403
Ticks = 7100, Count = 3403
Ticks = 7200, Count = 3403
Ticks = 7300, Count = 3407
Ticks = 7400, Count = 3403
Ticks = 7500, Count = 3403
Ticks = 7600, Count = 3404
Ticks = 7700, Count = 3402
Ticks = 7800, Count = 3404
Ticks = 7900, Count = 3406
Ticks = 8000, Count = 3402
Ticks = 8100, Count = 3402
Ticks = 8200, Count = 3403
Ticks = 8300, Count = 3403
Ticks = 8400, Count = 3404
Ticks = 8500, Count = 3407
Ticks = 8600, Count = 3403
Ticks = 8700, Count = 3403
Ticks = 8800, Count = 3403
Ticks = 8900, Count = 3403
Ticks = 9000, Count = 3403
Ticks = 9100, Count = 3407
Ticks = 9200, Count = 3502
Ticks = 9300, Count = 3403
Ticks = 9400, Count = 3403
Ticks = 9500, Count = 3402
Ticks = 9600, Count = 3404
Ticks = 9700, Count = 3407
Ticks = 9800, Count = 3403
Ticks = 9900, Count = 3403
Ticks = 10000, Count = 100
Ticks = 10100, Count = 100
Ticks = 10200, Count = 100
Ticks = 10300, Count = 101
Ticks = 10400, Count = 100
Ticks = 10500, Count = 100
Ticks = 10600, Count = 99
Ticks = 10700, Count = 101
Ticks = 10800, Count = 99
Ticks = 10900, Count = 100
Ticks = 11000, Count = 100
Ticks = 11100, Count = 100
Ticks = 11200, Count = 100
Ticks = 11300, Count = 100
Ticks = 11400, Count = 100
Ticks = 11500, Count = 100
Ticks = 11600, Count = 100
Ticks = 11700, Count = 100
Ticks = 11800, Count = 100
Ticks = 11900, Count = 100
Ticks = 12000, Count = 100
Ticks = 12100, Count = 100
Ticks = 12200, Count = 100
Ticks = 12300, Count = 100
Ticks = 12400, Count = 101
Ticks = 12500, Count = 99
Ticks = 12600, Count = 100
I know the timing will never be exact but I would expect the count to slowly drop as the thread function takes longer to execute..... but it doesn't, it stays araound 3404.
And after a while an even weider thing happens when the sleeptime reaches 10000, the count suddenly drops to around 100 and stays there.
Maybe I'm doing something wrong or my expectations are wrong.
Some help would be great!
Cheers,
Robert.
I think your error is that the constructor new TimeSpan(int) does not take milliseconds, but multiples of the high-resolution tick value, which is 100ns. And since Thread.Sleep() has a minimal resolution of 1ms, a timespan of less than 1ms doesn't wait at all. Therefore, as long as Ticks is less than 10.000, your loop runs as fast as it can (apparently about 3400 times per second). As soon as Ticks is larger than 10.000 (which corresponds to a wait time of 1ms) things change: Now the loop waits regularly, and you get about 100 iterations per second. This is now likely since the minimal wait time of Thread.Sleep() is 10ms (a value that varies with hardware).
I am creating a simulation to model a robot moving through river currents. The more code I add, the slower the simulation gets. Sometimes, it runs so slow that the buttons don't work. Does anyone know how I might speed it up?
It is possible that it might be my computer. A few times, the simulation ran flawlessly.
Code:
function CleanRiverSolutionsSimulation
%Initialize Figure
f = figure('Visible','off','Position',[250,250,1000,500],'Name','Clean River Solutions Simulation');
%Initialize variables (when changing these, don't forget to change them in
%the stop button function)
robot_position = [0 0 0];
last_robot_position = [-1 0 0];
time_data = [0];
depth_data = robot_position(3);
simulation_mode = 'paused'; %options: 'paused' 'running' 'stopped'
river_depth = 10;
water_flow_rate = 1;
%Initializes Plots
main_plot = axes('Units','pixels','Position',[50,225,500,250]);
data_plot1 = axes('Units','pixels','Position',[625,350,200,100]);
%Initializes Sliders
water_flow_slider = uicontrol('style','slider','position',[50 175 200 20],'min',0,'max',2.5,'callback',#callback_waterflow,'value',water_flow_rate);
water_flow_text = uicontrol('Style','text','String','River Water Flow','Position',[30 140 200 30]);
water_flow_text.String = sprintf('River Water Flow: %f m/s',get(water_flow_slider,'value'));
river_depth_slider = uicontrol('style','slider','position',[50 125 200 20],'min',0,'max',10,'callback',#callback_riverdepth,'value',river_depth);
river_depth_text = uicontrol('Style','text','String','River Depth','Position',[30 90 200 30]);
river_depth_text.String = sprintf('River Depth: %f m',get(river_depth_slider,'value'));
%Initializes Push Buttons
run_button = uicontrol('Style','pushbutton','String','Run','Position',[300 175 50 20],'callback',#callback_runbutton);
pause_button = uicontrol('Style','pushbutton','String','Pause','Position',[360 175 50 20],'callback',#callback_pausebutton);
stop_button = uicontrol('Style','pushbutton','String','Stop','Position',[420 175 50 20],'callback',#callback_stopbutton);
status_text = uicontrol('Style','text','String','Status','Position',[300 140 150 30]);
status_text.String = sprintf('Status: %s','Paused');
%After setup, makes figure visible
f.Visible = 'on';
%Environment Settings
riv_length = 20;
riv_width = 20;
%River flow settings
flow_dots = 4;
%Initialize Main Plot (when changing these, don't forget to change them in
%the stop button function)
plot3(main_plot,robot_position(1),robot_position(2),robot_position(3),'Marker', 'o','Color','r')
main_plot.XLim = [-riv_length/2 riv_length/2];
main_plot.YLim = [-riv_width/2 riv_width/2];
main_plot.ZLim = [-1*river_depth 1];
main_plot.XGrid = 'on';
main_plot.YGrid = 'on';
main_plot.ZGrid = 'on';
%Initialize time sequence (when changing these, don't forget to change them in
%the stop button function)
time = clock;
pausedStartTime = time(6)+60*time(5)+3600*time(4);
startTime = pausedStartTime;
pausedTime = 0;
totalPausedTime = 0;
%MAIN SIMULATION LOOP
while ishandle(f)
switch simulation_mode
case 'running'
%TODO-add forces and have the positions depend on the forces
%creates a time t to use in simulated equations
time = clock;
t = ((time(6)+60*time(5)+3600*time(4))-startTime-totalPausedTime);
%X
last_robot_position(1) = robot_position(1);
robot_position(1) = 0;
%Y
last_robot_position(2) = robot_position(2);
robot_position(2) = 0;
%Z
last_robot_position(3) = robot_position(3);
robot_position(3) = -4+2*sin(2*t); %just did a sin wave to show movement
%UPDATE MAIN PLOT
%update robot position
plot3(main_plot,robot_position(1),robot_position(2),robot_position(3),'Marker','o','Color','r')
%update robot velocity vector?
forward_vector = [robot_position(1)-last_robot_position(1),...
robot_position(2)-last_robot_position(2),...
robot_position(3)-last_robot_position(3)];
forward_unit_vector = forward_vector./norm(forward_vector);
hold(main_plot,'on')
plot3(main_plot,[robot_position(1) (robot_position(1)+forward_unit_vector(1))],...
[robot_position(2) (robot_position(2)+forward_unit_vector(2))],...
[robot_position(3) (robot_position(3)+forward_unit_vector(3))]);
%update river flow dots
for dot = 1:flow_dots
plot3(main_plot,-riv_length/2+(dot-1)*(riv_length/flow_dots)+mod((t/water_flow_rate),(riv_length/flow_dots)),10,0,'Marker','o','Color','b')
end
%update plot settings
main_plot.XLim = [-riv_length/2 riv_length/2];
main_plot.YLim = [-riv_width/2 riv_width/2];
main_plot.ZLim = [-1*river_depth 1];
main_plot.XGrid = 'on';
main_plot.YGrid = 'on';
main_plot.ZGrid = 'on';
hold(main_plot,'off')
%Update Data Plot 1 (depth)
time = clock;
time_data = [time_data ((time(6)+60*time(5)+3600*time(4))-startTime-totalPausedTime)];
depth_data = [depth_data robot_position(3)];
plot(data_plot1,time_data,depth_data)
title('Robot Depth')
data_plot1.XLim = [max(0,(((time(6)+60*time(5)+3600*time(4))-startTime-totalPausedTime)-15)),...
max(0,(((time(6)+60*time(5)+3600*time(4))-startTime-totalPausedTime)-15)+20)];
data_plot1.YLim = [-1*river_depth 1];
pause(0.1); %anything lower and the simulation won't pause
case 'paused'
pause(0.01); %need this or the sim won't start
case 'stopped'
%so far, this line is never run
end
end
%executes callback for water flow slider
function callback_waterflow(source,eventdata)
water_flow_text.String = sprintf('River Water Flow: %f m/s',get(water_flow_slider,'value'));
water_flow_rate = get(water_flow_slider,'value');
end
%executes callback for river depth slider
function callback_riverdepth(source,eventdata)
river_depth_text.String = sprintf('River Depth: %f m',get(river_depth_slider,'value'));
river_depth = get(river_depth_slider,'value');
end
%executes callback for run button
function callback_runbutton(source,eventdata)
switch simulation_mode
case 'stopped'
%Reinitialize time sequence
time = clock;
startTime = time(6)+60*time(5)+3600*time(4);
pausedTime = 0;
totalpausedTime = 0;
case 'paused'
time = clock;
currentTime = time(6)+60*time(5)+3600*time(4);
pausedTime = currentTime-pausedStartTime;
totalPausedTime = totalPausedTime + pausedTime;
pausedTime = 0;
end
simulation_mode = 'running';
status_text.String = sprintf('Status: %s','Running');
end
%executes callback for run button
function callback_pausebutton(source,eventdata)
switch simulation_mode
case 'running'
status_text.String = sprintf('Status: %s','Paused');
time = clock;
pausedStartTime = time(6)+60*time(5)+3600*time(4);
end
simulation_mode = 'paused';
end
%executes callback for run button
function callback_stopbutton(source,eventdata)
simulation_mode = 'stopped';
status_text.String = sprintf('Status: %s','Stopping');
%Reset all simulation data
robot_position = [0 0 0];
time_data = [0];
depth_data = robot_position(3);
%Reinitialize time sequence
time = clock;
pausedStartTime = time(6)+60*time(5)+3600*time(4);
startTime = pausedStartTime;
pausedTime = 0;
totalPausedTime = 0;
%Reinitialize Plots
plot3(main_plot,robot_position(1),robot_position(2),robot_position(3),'Marker','o','Color','r')
main_plot.XLim = [-riv_length/2 riv_length/2];
main_plot.YLim = [-riv_width/2 riv_width/2];
main_plot.ZLim = [-river_depth 1];
plot(data_plot1,time_data,depth_data)
status_text.String = sprintf('Status: %s','Data is reset.');
simulation_mode = 'paused';
end
end
matlab provides you tools to do this - it is called a profiler. try this
profiler on;
your_script_name;
profiler viewer;
from the output, you can tell which lines or functions cost most of your run time. I use it every time I need to speed up my matlab code.
I am trying to create a very simple distribution chart and I want to display the counts of tests score percentages in their corresponding 10's ranges.
I thought about just doing the grouping on the Math.Round((d.Percentage/10-0.5),0)*10 which should give me the 10's value....but I wasn't sure the best way to do this given that I would probably have missing ranges and all ranges need to appear even if the count is zero. I also thought about doing an outer join on the ranges array but since I'm fairly new to Linq so for the sake of time I opted for the code below. I would however like to know what a better way might be.
Also note: As I tend to work with larger teams with varying experience levels, I'm not all that crazy about ultra compact code unless it remains very readable to the average developer.
Any suggestions?
public IEnumerable<TestDistribution> GetDistribution()
{
var distribution = new List<TestDistribution>();
var ranges = new int[] { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110 };
var labels = new string[] { "0%'s", "10%'s", "20%'s", "30%'s", "40%'s", "50%'s", "60%'s", "70%'s", "80%'s", "90%'s", "100%'s", ">110% "};
for (var n = 0; n < ranges.Count(); n++)
{
var count = 0;
var min = ranges[n];
var max = (n == ranges.Count() - 1) ? decimal.MaxValue : ranges[n+1];
count = (from d in Results
where d.Percentage>= min
&& d.Percentage<max
select d)
.Count();
distribution.Add(new TestDistribution() { Label = labels[n], Frequency = count });
}
return distribution;
}
// ranges and labels in a list of pairs of them
var rangesWithLabels = ranges.Zip(labels, (r,l) => new {Range = r, Label = l});
// create a list of intervals (ie. 0-10, 10-20, .. 110 - max value
var rangeMinMax = ranges.Zip(ranges.Skip(1), (min, max) => new {Min = min, Max = max})
.Union(new[] {new {Min = ranges.Last(), Max = Int32.MaxValue}});
//the grouping is made by the lower bound of the interval found for some Percentage
var resultsDistribution = from c in Results
group c by
rangeMinMax.FirstOrDefault(r=> r.Min <= c.Percentage && c.Percentage < r.Max).Min into g
select new {Percentage = g.Key, Frequency = g.Count() };
// left join betweem the labels and the results with frequencies
var distributionWithLabels =
from l in rangesWithLabels
join r in resultsDistribution on l.Range equals r.Percentage
into rd
from r in rd.DefaultIfEmpty()
select new TestDistribution{
Label = l.Label,
Frequency = r != null ? r.Frequency : 0
};
distribution = distributionWithLabels.ToList();
Another solution if the ranges and labels can be created in another way
var ranges = Enumerable.Range(0, 10)
.Select(c=> new {
Min = c * 10,
Max = (c +1 )* 10,
Label = (c * 10) + "%'s"})
.Union(new[] { new {
Min = 100,
Max = Int32.MaxValue,
Label = ">110% "
}});
var resultsDistribution = from c in Results
group c by ranges.FirstOrDefault(r=> r.Min <= c.Percentage && c.Percentage < r.Max).Min
into g
select new {Percentage = g.Key, Frequency = g.Count() };
var distributionWithLabels =
from l in ranges
join r in resultsDistribution on l.Min equals r.Percentage
into rd
from r in rd.DefaultIfEmpty()
select new TestDistribution{
Label = l.Label,
Frequency = r != null ? r.Frequency : 0
};
This works
public IEnumerable<TestDistribution> GetDistribution()
{
var range = 12;
return Enumerable.Range(0, range).Select(
n => new TestDistribution
{
Label = string.Format("{1}{0}%'s", n*10, n==range-1 ? ">" : ""),
Frequency =
Results.Count(
d =>
d.Percentage >= n*10
&& d.Percentage < ((n == range - 1) ? decimal.MaxValue : (n+1)*10))
});
}
I want to calculate Daylight hours based on given Latitude and Longitude and DateTime
I mean calculate the time of sunrise and the time of sunset in a specefic Date and based on gegraphic coordinate.
Check this Latitude and Longitude and Daylight Hours
D = daylength
L = latitude
J = day of the year
P = asin[.39795*cos(.2163108 + 2*atan{.9671396*tan[.00860(J-186)]})]
_ _
/ sin(0.8333*pi/180) + sin(L*pi/180)*sin(P) \
D = 24 - (24/pi)*acos{ ----------------------------------------- }
\_ cos(L*pi/180)*cos(P) _/
Here is a python function that returns the number of hours of daylight with arguments of latitude and day of the year(number between 1-356):
import math
def Daylight(latitude,day):
P = math.asin(0.39795 * math.cos(0.2163108 + 2 * math.atan(0.9671396 * math.tan(.00860 * (day - 186)))))
pi = math.pi
daylightamount = 24 - (24 / pi) * math.acos(
(math.sin((0.8333 * pi / 180) + math.sin(latitude * pi / 180) * math.sin(P)) / (math.cos(latitude * pi / 180) * math.cos(P))))
return daylightamount
I just answered another question, and think that my solution is fitting here aswell. It's a Javascript solution, so you should be able to convert easily to other languages if you need.
I've created a repository under GitHub Sundial it is licenced under the permissive modified BSD license, so you can use it freely in your own projects.
It should be accurate to 0.0001 minutes and takes into account the axial tilt of the earth, and the equation of time.
Sundial AMD Loadable Sun Day Light Calculator
/* Credit and References */
// http://lexikon.astronomie.info/zeitgleichung/ EOT
// http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?bibcode=1989MNRAS.238.1529H&db_key=AST&page_ind=2&plate_select=NO&data_type=GIF&type=SCREEN_GIF&classic=YES
// http://code.google.com/p/eesim/source/browse/trunk/EnergySim/src/sim/_environment.py?spec=svn6&r=6
// http://mathforum.org/library/drmath/view/56478.html
// http://www.jgiesen.de/elevaz/basics/meeus.htm
// http://www.ehow.com/how_8495097_calculate-sunrise-latitude.html
// http://www.jgiesen.de/javascript/Beispiele/TN_Applet/DayNight125d.java
// http://astro.unl.edu/classaction/animations/coordsmotion/daylighthoursexplorer.html
// http://www.neoprogrammics.com/nutations/Nutation_In_Longitude_And_RA.php
(function (factory) {
if (typeof define === 'function' && define.amd ) {
// AMD. Register as module
if(typeof dojo === 'object') {
define(["dojo/_base/declare"], function(declare){
return declare( "my.calc.Sun", null, factory());
});
} else {
define( 'Sundial', null, factory());
}
} else {
Sun = new factory();
}
}(function () {
return {
date : new Date(),
getDate : function(){
return this.date;
},
setDate : function(d){
this.date = d;
return this;
},
getJulianDays: function(){
this._julianDays = Math.floor(( this.date / 86400000) - ( this.date.getTimezoneOffset() / 1440) + 2440587.5);
return this._julianDays;
},
// Calculate the Equation of Time
// The equation of time is the difference between apparent solar time and mean solar time.
// At any given instant, this difference will be the same for every observer on Earth.
getEquationOfTime : function (){
var K = Math.PI/180.0;
var T = (this.getJulianDays() - 2451545.0) / 36525.0;
var eps = this._getObliquity(T); // Calculate the Obliquity (axial tilt of earth)
var RA = this._getRightAscension(T);
var LS = this._getSunsMeanLongitude(T);
var deltaPsi = this._getDeltaPSI(T);
var E = LS - 0.0057183 - RA + deltaPsi*Math.cos(K*eps);
if (E>5) {
E = E - 360.0;
}
E = E*4; // deg. to min
E = Math.round(1000*E)/1000;
return E;
},
getTotalDaylightHoursInYear : function(lat){
// We can just use the current Date Object, and incrementally
// Add 1 Day 365 times...
var totalDaylightHours = 0 ;
for (var d = new Date(this.date.getFullYear(), 0, 1); d <= new Date(this.date.getFullYear(), 11, 30); d.setDate(d.getDate() + 1)) {
this.date = d;
// console.log( this.getDaylightHours(lat) );
totalDaylightHours += this.getDaylightHours(lat);
}
return totalDaylightHours;
},
getDaylightHours : function (lat) {
var K = Math.PI/180.0;
var C, Nenner, C2, dlh;
var T = (this.getJulianDays() - 2451545.0) / 36525.0;
this._getRightAscension(T); // Need to get the Suns Declination
Nenner = Math.cos(K*lat)*Math.cos(K*this._sunDeclination);
C = -Math.sin(K*this._sunDeclination)*Math.sin(K*lat)/Nenner;
C2=C*C;
// console.log( T, C2, C, Nenner, lat, K, Math.cos(K*lat) );
if ((C>-1) && (C<1)) {
dlh=90.0 - Math.atan(C / Math.sqrt(1 - C2)) / K;
dlh=2.0*dlh/15.0;
dlh=Math.round(dlh*100)/100;
}
if (C>1) {
dlh=0.0;
}
if (C<-1) {
dlh=24.0;
}
return dlh;
},
_getRightAscension : function(T) {
var K = Math.PI/180.0;
var L, M, C, lambda, RA, eps, delta, theta;
L = this._getSunsMeanLongitude(T); // Calculate the mean longitude of the Sun
M = 357.52910 + 35999.05030*T - 0.0001559*T*T - 0.00000048*T*T*T; // Mean anomoly of the Sun
M = M % 360;
if (M<0) {
M = M + 360;
}
C = (1.914600 - 0.004817*T - 0.000014*T*T)*Math.sin(K*M);
C = C + (0.019993 - 0.000101*T)*Math.sin(K*2*M);
C = C + 0.000290*Math.sin(K*3*M);
theta = L + C; // get true longitude of the Sun
eps = this._getObliquity(T);
eps = eps + 0.00256*Math.cos(K*(125.04 - 1934.136*T));
lambda = theta - 0.00569 - 0.00478*Math.sin(K*(125.04 - 1934.136*T)); // get apparent longitude of the Sun
RA = Math.atan2(Math.cos(K*eps)*Math.sin(K*lambda), Math.cos(K*lambda));
RA = RA/K;
if (RA<0) {
RA = RA + 360.0;
}
delta = Math.asin(Math.sin(K*eps)*Math.sin(K*lambda));
delta = delta/K;
this._sunDeclination = delta;
return RA;
},
// Calculate the Mean Longitude of the Sun
_getSunsMeanLongitude : function(T){
var L = 280.46645 + 36000.76983*T + 0.0003032*T*T;
L = L % 360;
if (L<0) {
L = L + 360;
}
return L;
},
// Nutation in ecliptical longitude expressed in degrees.
_getDeltaPSI : function(T){
var K = Math.PI/180.0;
var deltaPsi, omega, LS, LM;
LS = this._getSunsMeanLongitude(T);
LM = 218.3165 + 481267.8813*T;
LM = LM % 360;
if (LM<0) {
LM = LM + 360;
}
// Longitude of ascending node of lunar orbit on the ecliptic as measured from the mean equinox of date.
omega = 125.04452 - 1934.136261*T + 0.0020708*T*T + T*T*T/450000;
deltaPsi = -17.2*Math.sin(K*omega) - 1.32*Math.sin(K*2*LS) - 0.23*Math.sin(K*2*LM) + 0.21*Math.sin(K*2*omega);
deltaPsi = deltaPsi/3600.0;
return deltaPsi;
},
// T = Time Factor Time factor in Julian centuries reckoned from J2000.0, corresponding to JD
// Calculate Earths Obliquity Nutation
_getObliquity : function (T) {
var K = Math.PI/180.0;
var LS = this._getSunsMeanLongitude(T);
var LM = 218.3165 + 481267.8813*T;
var eps0 = 23.0 + 26.0/60.0 + 21.448/3600.0 - (46.8150*T + 0.00059*T*T - 0.001813*T*T*T)/3600;
var omega = 125.04452 - 1934.136261*T + 0.0020708*T*T + T*T*T/450000;
var deltaEps = (9.20*Math.cos(K*omega) + 0.57*Math.cos(K*2*LS) + 0.10*Math.cos(K*2*LM) - 0.09*Math.cos(K*2*omega))/3600;
return eps0 + deltaEps;
}
};
}));
Demo jsFiddle
You can check out a demo of how you might use it on jsfiddle.
http://jsfiddle.net/wjKRw/
And then when I get around to it, check out the sample use cases at the repository.
GitHub Sundial
sin24+(24cos-18^12)^(day number of the year)+(latitude)^24= #of daylight hours
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>