This tutorial shows you how to make a graph that plots the sunrise and sunset times in MATLAB or Octave. You'll learn how to shade the area above, below and between curves, use date and time formatted axis tick labels and draw grid lines for each tick mark in a custom colour. The result will look like this:
This code works in Octave but it should be compatible with MATLAB, which I don't have access to at the moment.
For Octave users, fltk
will mess up the plots in a lot of ways, so make sure you set the graphics toolkit to gnuplot
:
% for Octave users graphics_toolkit("gnuplot")
You can get sunrise and sunset data in a variety of ways:
suncycle
outputs into decimal days, i.e. for a sunrise time of 11:38AM, suncyle
outputs 11.633 hours = 11.633 / 24 = 0.485 days.No matter where you get your data from, to use the code here, you need to convert the times into the appropriate timezone and then convert the times into decimal days to make use of MATLAB's date and time formatting for the graph.
If you are making the graphs for polar regions, you also need to take into account polar night and polar days (no sunrise or sunset) and adjust the code for nice graphs.
I don't want to complicate this tutorial so I will create some fake sunrise and sunset data for a non-polar city (Eutopia, United Democratic Republics of Freedomness):
clear all % create a fake sunrise / sunset curve using cos. year = 2099; dates = [datenum(2099,1,1):datenum(2099,12,31)]'; scale = 2*pi / length(dates); sunset_times = 2 * (cos(scale*(dates - floor(median(dates)))) + 1) + 17; sunrise_times = -2 * (cos(scale*(dates - floor(median(dates)))) + 1) + 8; daylength_times = sunset_times - sunrise_times; % DST adjustments dst_start_idx = find(dates == datenum(2099,3,10)); dst_end_idx = find(dates == datenum(2099,11,3)); sunset_times(dst_start_idx:dst_end_idx,:) = sunset_times(dst_start_idx:dst_end_idx,:) + 1; sunrise_times(dst_start_idx:dst_end_idx,:) = sunrise_times(dst_start_idx:dst_end_idx,:) + 1; city = 'Eutopia, UDRF';
The time from the code above and suncycle.m are both in decimal hours but we need the sunset and sunrise times to be in decimal days (or a valid date number) to make use of MATLAB's date formatting capabilities.
MATLAB represents dates and times with a date number, which is a decimal number of days since some internal reference date (January 0, 0000). To convert the hours to a date number, take the decimal hours [0,24] and normalize it to decimal days [0,1]:
% Convert the times to datenum by simply converting each time to a decimal day. sunset_times = sunset_times / 24; sunrise_times = sunrise_times / 24; daylength_times = daylength_times / 24; hours = [0:24] / 24; % hours of a day as decimal days datenum.
When 0 ⇐ date number ⇐ 1, MATLAB treats it as a date Jan 0, 0000 with only hours, minutes and seconds, which is what we want to be able to plot the sunrise and sunset time in hourly format.
If you think that the converted date numbers are odd (Octave, for example, which treats date any number that is [0,1] as Dec 31 -1 rather than January 0, 0000), you can add an arbitrary date number, e.g. datenum(2000,1,1)
to all of the converted times above but this is not necessary:
% optional, not necessary sunset_times = sunset_times + datenum(2000,1,1); sunrise_times = sunrise_times + datenum(2000,1,1); daylength_times = daylength_times + datenum(2000,1,1); hours = hours + datenum(2000,1,1); % hours of a day as decimal days datenum.
The explanations in the rest of the tutorial assumes you did not add the arbitrary date number and that the times are [0,1], but the code works for both ways.
We want the X-axis to show tickmarks for every month at the 1st of each month. So construct a list of date numbers corresponding to the start of each month.
% Get the datenums corresponding to the start of each month. month_starts = datenum(year, 1:12, 1);
Now let's start to build the graph. We want to shade the area between the sunrise and sunset curves with a light blue, representing day. We also want to shade the area below the sunrise curve and the area above the sunset curve with a dark blue, representing night. We do this by using building polygons and using patch
to draw them on the curve.
% create a new figure. figure; % ---- Plot the shaded areas below, between and above the curves ---- hold on; % fill between sunrise and sunset: day time. use flipud so the resulting vectors describe % a properly ordered polygon. patch([dates;flipud(dates)],[sunrise_times;flipud(sunset_times)],[0.7 0.9 1]) % fill between top of graph ( which is hours(end) ) and sunset time: night after sunset. patch([dates;flipud(dates)],[sunset_times;repmat(hours(end),length(dates),1)],2*[0.05 0.1 0.3]) % fill between bottom of graph (hours(1)) and sunrise time: night before sunrise. patch([dates;flipud(dates)],[sunrise_times;repmat(hours(1),length(dates),1)],2*[0.05 0.1 0.3])
Let's look at the first patch
statement, the polygon between the sunrise and sunset curves. For patch
, the first argument is the X coordinate of each point of the polygon and the second is the Y coordinate. We need to construct a polygon with the sunrise and sunset curve points in the right order. Consider the following:
C-------D | | A-------B
If we have:
Then, we need to construct the polygon with points in the order A-B-D-C. This is why the arguments to patch are:
patch([dates;flipud(dates)],[sunrise_times;flipud(sunset_times)],[0.7 0.9 1])
dates
and sunrise_times
are A-B. Then we flip dates
and sunset_times
to represent D-C. Since dates
and the different time arrays are column vectors, we use flipud
to flip it up and down.
The other 2 patches (time before sunrise and time after sunset) are filled similarly. For the Y values, we use the sunset time and the top of the graph (hours(end)) or the sunrise time and the bottom of the graph (hours(1)).
Set up the X and Y limits of the graph:
% set limits to Jan 1 to Dec 31. xlim([dates(1) dates(end)]) % set limits to time between 0:00 and 24:00 as decimal days. ylim([hours(1) hours(end)])
Treating the dates and times as simply numbers makes this pretty straightforward.
Now, set up the tickmarks with date and time formatting:
h = gca; % get current axis. % Set the XTicks to the values in month_starts. set(h,'XTick',month_starts) % Set the YTicks to the hours of the day. set(h,'YTick',hours) % Reformat the XTicks to 3 letter month names (Jan, Feb, etc) using 'mmm' format. % Preserve the total number of ticks by using the 'keepticks' option and % preserve the plot limits by using 'keeplimits' datetick(h,'x','mmm','keepticks','keeplimits') % Reformat YTicks to 24 hours format 'HH:MM' datetick(h,'y','HH:MM','keepticks','keeplimits')
Here, we've set the X ticks to the date number values for the 1st of each month and the Y ticks to the date number values for each hour from 0:00 to 24:00 (which are decimal days, so [0,1]). Then, datetick
formats the ticks to an actual date or time format, which you can specify using a format string(mmm
or HH:MM
).
We want vertical grid lines for each month tick and horizontal grid lines for each hour tick. Since we want to set the colour of the lines, we have to manually create each line to plot.
Create the vertical lines:
% vertical grid lines: one at each tick mark on the x-axis. % Get the limits of the y axis, which are datenums representing [0:00,24:00] ylimits= get(h,'Ylim'); % for each tick mark line, we have two points: (tickmark, 0:00) and (tickmark, 24:00). % create X = [Jan-1st Feb-1st ... Dec-1st; Jan-1st Feb-1st ... Dec-1st] (as datenums) X = repmat(month_starts,2,1); % x-values % create Y = [0:00 0:00 ... 0:00; 24:00 24:00 ... 24:00] (as datenums) Y = repmat(ylimits',1,size(month_starts,2)); % y-values. % Plot the grid lines with gray colour. plot(X,Y,'color',[0.5 0.5 0.5])
The above is pretty straightforward. X and Y represent the multiple grid lines, with each column representing one line. Each vertical grid line consists of two points, for example: (Jan 1st, 0:00) and (Jan 1st, 24:00), where both numbers are date numbers representing the date and time.
Create the horizontal grid lines:
% horizontal grid lines: one for each hour on the y-axis. % Get limits of the x axis, which are datenums representing [Jan-1st,Dec-31st] xlimits= get(h,'xlim'); % for each hour line, we have two points (as datenums): (Jan-1st, Hour) and (Dec-31st, Hour). % create X = [Jan-1st Jan-1st ... Jan-1st; Dec-31st Dec-31st ... Dec-31st; ] X2 = repmat(xlimits',1,size(hours,2)); % x-values % create Y = [0:00 1:00 ... 24:00; 0:00 1:00 ... 24:00] (as datenums) Y2 = repmat((hours),2,1); % y-values % Plot the grid lines with gray colour. plot(X2,Y2,'color',[0.5 0.5 0.5])
Again, X and Y represent the multiple grid lines, with each column representing one line. Each horizontal grid line consists of two points, for example: (Jan 1st, 0:00) and (Dec 31st, 0:00), where both numbers are date numbers representing the date and time.
Plot the sunrise time, sunset time and daylight length curves in a nice colour:
% finally, plot the sunset, sunrise and daylength times once so we get lines for them. plot(dates,sunrise_times,'color',[1 0.5 0],'linewidth',3) plot(dates,sunset_times,'color',[1 0.5 0],'linewidth',3) plot(dates,daylength_times,'color',[1 0.9 0],'linewidth',3)
Next, add text labels for each curve:
% Text labels for each curve. % get the index where the min / max occurs for each variable. % median is just used to deal with curves where there are multiple extreme values. sunrise_imin = floor(median(find(min(sunrise_times) == sunrise_times))); daylength_imax = floor(median(find(max(daylength_times) == daylength_times))); sunset_imax = floor(median(find(max(sunset_times) == sunset_times))); % For all 3 labels, use the daylength max as the horizontal position. % This could be adjusted for the southern hemisphere, since the % daylength minimum is in the middle of the year. % sunrise time label: use the sunrise min as the vertical position. text(dates(daylength_imax),sunrise_times(sunrise_imin),'Sunrise',... 'VerticalAlignment','bottom',... 'HorizontalAlignment','center',... 'FontSize',10) % sunset time label: use the sunset max as the vertical position. text(dates(daylength_imax),sunset_times(sunset_imax),'Sunset',... 'VerticalAlignment','top',... 'HorizontalAlignment','center',... 'FontSize',10) % daylength label: use the daylength max as the verical position. text(dates(daylength_imax),daylength_times(daylength_imax),'Daylight',... 'VerticalAlignment','top',... 'HorizontalAlignment','center',... 'FontSize',10)
The text labels are positioned horizontally where the daylight hours is maximum and positioned vertically at either the minimum (for sunrise) or maximum (sunset, daylight hours) curves.
For the southern hemisphere you may want to adjust the positions of the text labels since the curves are shaped differently (minimum daylight in June / July).
Finally add the title:
title(sprintf('Sunrise and Sunset Times\n%s (%d)', city, year))
The result, in Octave, looks like this:
I saved an SVG using Octave from sunrise and sunset data generated by suncycle.m for Toronto:
The above was exported from SVG to PNG using Inkscape and looks pretty good.
% Create a sunrise and sunset graph in MATLAB / Octave tutorial % By Peter Yu % http://www.peteryu.ca/tutorials/matlab/sunrise_sunset_graph % for Octave users graphics_toolkit("gnuplot") clear all % create a fake sunrise / sunset curve using cos. year = 2099; dates = [datenum(2099,1,1):datenum(2099,12,31)]'; scale = 2*pi / length(dates); sunset_times = 2 * (cos(scale*(dates - floor(median(dates)))) + 1) + 17; sunrise_times = -2 * (cos(scale*(dates - floor(median(dates)))) + 1) + 8; daylength_times = sunset_times - sunrise_times; % DST adjustments dst_start_idx = find(dates == datenum(2099,3,10)); dst_end_idx = find(dates == datenum(2099,11,3)); sunset_times(dst_start_idx:dst_end_idx,:) = sunset_times(dst_start_idx:dst_end_idx,:) + 1; sunrise_times(dst_start_idx:dst_end_idx,:) = sunrise_times(dst_start_idx:dst_end_idx,:) + 1; city = 'Eutopia, UDRF'; % Convert the times to datenum by simply converting each time to a decimal day. sunset_times = sunset_times / 24; sunrise_times = sunrise_times / 24; daylength_times = daylength_times / 24; hours = [0:24] / 24; % hours of a day as decimal days datenum. % Get the datenums corresponding to the start of each month. month_starts = datenum(year, 1:12, 1); % create a new figure. figure; % ---- Plot the shaded areas below, between and above the curves ---- hold on; % fill between sunrise and sunset: day time. patch([dates;flipud(dates)],[sunrise_times;flipud(sunset_times)],[0.7 0.9 1]) % fill between top of graph ( which is hours(end) ) and sunset time: night after sunset. patch([dates;flipud(dates)],[sunset_times;repmat(hours(end),length(dates),1)],2*[0.05 0.1 0.3]) % fill between bottom of graph (hours(1)) and sunrise time: night before sunrise. patch([dates;flipud(dates)],[sunrise_times;repmat(hours(1),length(dates),1)],2*[0.05 0.1 0.3]) % set limits to Jan 1 to Dec 31. xlim([dates(1) dates(end)]) % set limits to time between 0:00 and 24:00 as decimal days. ylim([hours(1) hours(end)]) h = gca; % get current axis. % Set the XTicks to the values in month_starts. set(h,'XTick',month_starts) % Set the YTicks to the hours of the day. set(h,'YTick',hours) % Reformat the XTicks to 3 letter month names (Jan, Feb, etc) using 'mmm' format. datetick(h,'x','mmm','keepticks','keeplimits') % Reformat YTicks to 24 hours format 'HH:MM' datetick(h,'y','HH:MM','keepticks','keeplimits') % vertical grid lines: one at each tick mark on the x-axis. % Get the limits of the y axis, which are datenums representing [0:00,24:00] ylimits= get(h,'Ylim'); % for each tick mark line, we have two points: (tickmark, 0:00) and (tickmark, 24:00). % create X = [Jan-1st Feb-1st ... Dec-1st; Jan-1st Feb-1st ... Dec-1st] (as datenums) X = repmat(month_starts,2,1); % x-values % create Y = [0:00 0:00 ... 0:00; 24:00 24:00 ... 24:00] (as datenums) Y = repmat(ylimits',1,size(month_starts,2)); % y-values. % Plot the grid lines with gray colour. plot(X,Y,'color',[0.5 0.5 0.5]) % horizontal grid lines: one for each hour on the y-axis. % Get limits of the x axis, which are datenums representing [Jan-1st,Dec-31st] xlimits= get(h,'xlim'); % for each hour line, we have two points (as datenums): (Jan-1st, Hour) and (Dec-31st, Hour). % create X = [Jan-1st Jan-1st ... Jan-1st; Dec-31st Dec-31st ... Dec-31st; ] X2 = repmat(xlimits',1,size(hours,2)); % x-values % create Y = [0:00 1:00 ... 24:00; 0:00 1:00 ... 24:00] (as datenums) Y2 = repmat((hours),2,1); % y-values % Plot the grid lines with gray colour. plot(X2,Y2,'color',[0.5 0.5 0.5]) % finally, plot the sunset, sunrise and daylength times once so we get lines for them. plot(dates,sunrise_times,'color',[1 0.5 0],'linewidth',3) plot(dates,sunset_times,'color',[1 0.5 0],'linewidth',3) plot(dates,daylength_times,'color',[1 0.9 0],'linewidth',3) % Text labels for each curve. % get the index where the min / max occurs for each variable. % median is just used to deal with curves where there are multiple extreme values. sunrise_imin = floor(median(find(min(sunrise_times) == sunrise_times))); daylength_imax = floor(median(find(max(daylength_times) == daylength_times))); sunset_imax = floor(median(find(max(sunset_times) == sunset_times))); % For all 3 labels, use the daylength max as the horizontal position. % This could be adjusted for the southern hemisphere, since the % daylength minimum is in the middle of the year. % sunrise time label: use the sunrise min as the vertical position. text(dates(daylength_imax),sunrise_times(sunrise_imin),'Sunrise',... 'VerticalAlignment','bottom',... 'HorizontalAlignment','center',... 'FontSize',10) % sunset time label: use the sunset max as the vertical position. text(dates(daylength_imax),sunset_times(sunset_imax),'Sunset',... 'VerticalAlignment','top',... 'HorizontalAlignment','center',... 'FontSize',10) % daylength label: use the daylength max as the verical position. text(dates(daylength_imax),daylength_times(daylength_imax),'Daylight',... 'VerticalAlignment','top',... 'HorizontalAlignment','center',... 'FontSize',10) title(sprintf('Sunrise and Sunset Times\n%s (%d)', city, year))
Discussion
I am a student a Bangor University Wales running some larval migration models in the Irish Sea. I wish to include your code to simulate vertical migrations for the larvae at dusk and dawn. However, I am not sure where in your code you input the two values from the suncycle (ans = 2.777 14.4277) as it is not clear.
In addition although you explain most things clearly it would be helpful for those such as my self, self learning to have individual parts of the script explained, especially within lines so I would have a clear understanding of what you have done
Many thanks
Hayden Close
MSc student at Bangor University Wales
Thank you for the helpful content.