Risultati per
File Exchange now offers the ability to download/restore previous versions of community contributed files. It's often a good practice to always update your software to the latest version, however there are times when this isn't always helpful. Sometimes a software update can break or alter something you've been relying on, in these cases you'll want to stick with the version that's working for you. This is why we've added the ability to download previous versions in File Exchange.
Using Version History
Navigate to any community member file and then click the View Version History link that appears above the Download button. This will show you a list of the previous versions contributed by the submission author. Each version will have a corresponding download button, date, version number, and a description of the changes made for that update.
Let us know what you think about this new feature by replying below.
Prior to r2020b the height (number of rows) and width (number of columns) of an array or table can be determined by the size function,
array = rand(102, 16);
% Method 1 [dimensions] = size(array); h = dimensions(1); w = dimensions(2);
% Method 2 [h, w] = size(array); %#ok<*ASGLU> % or [h, ~] = size(array); [~, w] = size(array);
% Method 3 h = size(array,1); w = size(array,2);
In r2013b, the height(T) and width(T) functions were introduced to return the size of single dimensions for tables and timetables.
Starting in r2020b, height() and width() can be applied to arrays as an alternative to the size() function.
Continuing from the section above,
h = height(array) % h = 102
w = width(array) % w = 16
height() and width() can also be applied to multidimensional arrays including cell and structure arrays
mdarray = rand(4,3,20); h = height(mdarray) % h = 4
w = width(mdarray) % w = 3
The expanded support of the height() and width() functions means,
- when reading code, you can no longer assume the variable T in height(T) or width(T) refers to a table or timetable
- greater flexibility in expressions such as the these, below
% C is a 1x4 cell array containing 4 matrices with different dimensions rng('default') C = {rand(5,2), rand(2,3), rand(3,4), rand(1,1)}; celldisp(C)
% C{1} = % 0.81472 0.09754 % 0.90579 0.2785 % 0.12699 0.54688 % 0.91338 0.95751 % 0.63236 0.96489 % C{2} = % 0.15761 0.95717 0.80028 % 0.97059 0.48538 0.14189 % C{3} = % 0.42176 0.95949 0.84913 0.75774 % 0.91574 0.65574 0.93399 0.74313 % 0.79221 0.035712 0.67874 0.39223 % C{4} = % 0.65548
What's the max number of rows in C?
maxRows1 = max(cellfun(@height,C)) % using height() % maxRows1 = 5;
maxRows2 = max(cellfun(@(x)size(x,1),C)) % using size() % maxRows2 = 5;
What's the total number of columns in C?
totCols1 = sum(cellfun(@width,C)) % using width() %totCols1 = 10
totCols2 = sum(cellfun(@(x)size(x,2),C)) % using size(x,2) % totCols2 = 10
Attached is a live script containing the content of this post.
Please join Loren Shure for her live sessions on the MATLAB YouTube channel starting October 1st and continuing through November 19th. You know Loren from her popular blog Loren on the Art of MATLAB.
Solve coding problems. Improve MATLAB skills. Have fun. See details and register .
Add a subtitle
Multi-lined titles have been supported for a long time but starting in r2020b, you can add a subtitle with its own independent properties to a plot in two easy ways.
- Use the new subtitle function: s=subtitle('mySubtitle')
- Use the new second argument to the title function: [t,s]=title('myTitle','mySubtitle')
figure() tiledlayout(2,2)
% Method 1 ax(1) = nexttile; th(1) = title('Pupil size'); sh(1) = subtitle('Happy faces');
ax(2) = nexttile; th(2) = title('Pupil size'); sh(2) = subtitle('Sad faces');
% Method 2 ax(3) = nexttile; [th(3), sh(3)] = title('Fixation duration', 'Happy faces');
ax(4) = nexttile; [th(4), sh(4)] = title('Fixation duration', 'Sad faces');
set(ax, 'xticklabel', [], 'yticklabel', [],'xlim',[0,1],'ylim',[0,1])
% Set all title colors to orange and subtitles colors to purple. set(th, 'Color', [0.84314, 0.53333, 0.1451]) set(sh, 'Color', [0, 0.27843, 0.56078])
Control title/Label alignment
Title and axis label positions can be changed via their Position, VerticalAlignment and HorizontalAlignment properties but this is usually clumsy and leads to other problems when trying to align the title or labels with an axis edge. For example, when the position units are set to 'data' and the axis limits change, the corresponding axis label will change position relative to the axis edges. If units are normalized and the axis position or size changes, the corresponding label will no longer maintain its relative position to the axis, and that's assuming the normalized position was computed correctly in the first place.
Starting in r2020b, title and axis label alignment can be set to center|left|right, relative to the axis edges.
- TitleHorizontalAlignment is a property of the axis: h.TitleHorizontalAlignment='left';
- LabelHorizontalAlignment is a property of the ruler object that defines the x | y | z axis: h.XAxis.LabelHorizontalAlignment='left';
% Create data x = randi(50,1,100)'; y = x.*[.2, -.2] + (rand(numel(x),2)-.5)*10; gray = [.65 .65 .65];
% Plot comparison between columns of y figure() tiledlayout(2,2,'TileSpacing','none') ax(1) = nexttile(1); plot(x, y(:,1), 'o', 'color', gray) lsline ylabel('Y1 (units)') title('Regression','Y1 & Y2 separately')
ax(2) = nexttile(3); plot(x, y(:,2), 'd', 'color', gray) lsline xlabel('X Label (units)') ylabel('Y2 (units)') grid(ax, 'on') linkaxes(ax, 'x')
% Move title and labels leftward set(ax, 'TitleHorizontalAlignment', 'left') set([ax.XAxis], 'LabelHorizontalAlignment', 'left') set([ax.YAxis], 'LabelHorizontalAlignment', 'left')
% Combine the two comparisons into plot and flip the second % y-axis so trend are in the same direction ax(3) = nexttile([2,1]); yyaxis('left') plot(x, y(:,1), 'o') ylim([-6,16]) lsline xlabel('X Label (units)') ylabel('Y1 (units) \rightarrow')
yyaxis('right') plot(x, y(:,2), 'd') ylim([-16,6]) lsline ylabel('\leftarrow Y2 (units)') title('Direct comparison','(Y2 axis flipped)') set(ax(3), 'YDir','Reverse')
% Align the ylabels with the minimum axis limit to emphasize the % directions of each axis. Keep the title and xlabel centered ax(3).YAxis(1).LabelHorizontalAlignment = 'left'; ax(3).YAxis(2).LabelHorizontalAlignment = 'right'; ax(3).TitleHorizontalAlignment = 'Center'; % not needed; default value. ax(3).XAxis.LabelHorizontalAlignment = 'Center'; % not needed; default value.
Starting in r2020a , you can change the mouse pointer symbol in apps and uifigures.
The Pointer property of a figure defines the cursor’s default pointer symbol within the figure. You can also create your own pointer symbols (see part 3, below).
Part 1. How to define a default pointer symbol for a uifigure or app
For figures or uifigures, set the pointer property when you define the figure or change the pointer property using the figure handle.
% Set pointer when creating the figure uifig = uifigure('Pointer', 'crosshair');
% Change pointer after creating the figure uifig.Pointer = 'crosshair';
For apps made in AppDesigner, you can either set the pointer from the Design View or you can set the pointer property of the app’s UIFigure from the startup function using the second syntax shown above.
Part 2. How to change the pointer symbol dynamically
The pointer can be changed by setting specific conditions that trigger a change in the pointer symbol.
For example, the pointer can be temporarily changed to a busy-symbol when a button is pressed. This ButtonPushed callback function changes the pointer for 1 second.
function WaitasecondButtonPushed(app, event) % Change pointer for 1 second. set(app.UIFigure, 'Pointer','watch') pause(1) % Change back to default. set(app.UIFigure, 'Pointer','arrow') app.WaitasecondButton.Value = false; end
The pointer can be changed every time it enters or leaves a uiaxes or any plotted object within the uiaxes. This is controlled by a set of pointer management functions that can be set in the app’s startup function.
iptSetPointerBehavior(obj,pointerBehavior) allows you to define what happens when the pointer enters, leaves, or moves within an object. Currently, only axes and axes objects seem to be supported for UIFigures.
iptPointerManager(hFigure,'enable') enables the figure’s pointer manager and updates it to recognize the newly added pointer behaviors.
The snippet below can be placed in the app’s startup function to change the pointer to crosshairs when the pointer enters the outerposition of a uiaxes and then change it back to the default arrow when it leaves the uiaxes.
% Define pointer behavior when pointer enter axes pm.enterFcn = @(~,~) set(app.UIFigure, 'Pointer', 'crosshair'); pm.exitFcn = @(~,~) set(app.UIFigure, 'Pointer', 'arrow'); pm.traverseFcn = []; iptSetPointerBehavior(app.UIAxes, pm)
% Enable pointer manager for app iptPointerManager(app.UIFigure,'enable');
Any function can be triggered when entering/exiting an axes object which makes the pointer management tools quite powerful. This snippet below defines a custom function cursorPositionFeedback() that responds to the pointer entering/exiting a patch object plotted within the uiaxes. When the pointer enters the patch, the patch color is changed to red, the pointer is changed to double arrows, and text appears in the app’s text area. When the pointer exits, the patch color changes back to blue, the pointer changes back to crosshairs, and the text area is cleared.
% Plot patch on uiaxes hold(app.UIAxes, 'on') region1 = patch(app.UIAxes,[1.5 3.5 3.5 1.5],[0 0 5 5],'b','FaceAlpha',0.07,... 'LineWidth',2,'LineStyle','--','tag','region1');
% Define pointer behavior for patch pm.enterFcn = @(~,~) cursorPositionFeedback(app, region1, 'in'); pm.exitFcn = @(~,~) cursorPositionFeedback(app, region1, 'out'); pm.traverseFcn = []; iptSetPointerBehavior(region1, pm)
% Enable pointer manager for app iptPointerManager(app.UIFigure,'enable');
function cursorPositionFeedback(app, hobj, inout) % When inout is 'in', change hobj facecolor to red and update textbox. % When inout is 'out' change hobj facecolor to blue, and clear textbox. % Check tag property of hobj to identify the object. switch lower(inout) case 'in' facecolor = 'r'; txt = 'Inside region 1'; pointer = 'fleur'; case 'out' facecolor = 'b'; txt = ''; pointer = 'crosshair'; end hobj.FaceColor = facecolor; app.TextArea.Value = txt; set(app.UIFigure, 'Pointer', pointer) end
The app showing the demo below is attached.
Part 3. Create your own custom pointer symbol
- Set the figure’s pointer property to ‘custom’.
- Set the figure’s PointerShapeCData property to the custom pointer matrix. A custom pointer is defined by a 16x16 or 32x32 matrix where NaN values are transparent, 1=black, and 2=white.
- Set the figure’s PointerShapeHotSpot to [m,n] where m and n are the coordinates that define the tip or "hotspot" of the matrix.
This demo uses the attached mat file to create a black hand pointer symbol.
iconData = load('blackHandPointer.mat'); uifig = uifigure(); uifig.Pointer = 'custom'; uifig.PointerShapeCData = iconData.blackHandIcon; uifig.PointerShapeHotSpot = iconData.hotspot;
Also see Jiro's pointereditor() function on the file exchange which allows you to draw your own pointer.
Today, I'm spotlighting Rik, our newest and the 31st MVP in MATLAB Answers. Two weeks ago, we just celebrated Ameer Hamza for reaching the MVP milestone. Today, we are thrilled that we have another new MVP!
Since his first answer in Feb 2017, Rik has been contributing high-quality answers every quarter!
Besides those high-quality answers, Rik so far has submitted 21 files to File Exchange, one of which was chosen by MathWorks as the 'Pick of the Week'. Check the shining badge below.
Congratulations Rik! Thank you for your hard work and outstanding contributions.
Today, I’m spotlighting Ameer Hamza , our newest MVP in Answers. Achieving MVP status is considered as a significant milestone and we know how hard it is to obtain 5,000 reputation points. Did you know Ameer earned 3000+ points and provided 1000+ answers in just 2 months? If you go to the leaderboard , you will find that Ameer ranks 1st in both 7-day leaderboard and 30-day leaderboard.
Due to Covid-19 pandemic, people have to stay at home and rely more on community for help. We have seen a significant increase in new questions per day. Luckily, we have a vibrant community! Many awesome contributors like Ameer double their effort to help people in need. Join me to thank Ameer and many other contributors!
Starting in r2020a, AppDesigner buttons and TreeNodes can display animated GIFs, SVG, and truecolor image arrays.
Every component in the App above is either a Button or a TreeNode!
Prior to r2020a the icon property of buttons and TreeNodes in AppDesigner supported JPEG, PNG, or GIF image files specified by a character vector or string array but did not support animation.
Here's how to display an animated GIF, SVG, or truecolor image in an App button or TreeNode starting in r2020a. And for the record, "GIF" is pronounced with a hard-g .
Display an animated GIF
Select the button or TreeNode from within AppDesigner > Design View and navigate to Component Browser > Inspector > Button dropdown list of properties (shown below). Select an animated GIF file and set the text and icon alignment properties.
To set the icon property programmatically,
app.Button.Icon = 'launch.gif'; % or "launch.gif"
The filename can be an image file on the Matlab path (see addpath ) or a full path to an image file.
Display SVG
Use “scalable vector graphics” files for high-resolution images that are scaled to different sizes while preserving their shape and retaining their clarity. A quick and easy way to remember which plotting function is assigned to each button in an app is to assign an image of the plot to the button.
After creating the figure, expand the axes by setting the position or outerposition property to [0 0 1 1] in normalized units and save the figure using File > Save as and select svg format. Save the image to the folder containing your app. Then follow the same procedure as animated GIFs.
Display truecolor image
A truecolor image comes in the form of an [m x n x 3] array where each m x n pixel color is specified by an RGB triplet (read more) . This feature allows you to dynamically create a digital image or to upload an image from a mat file rather than an image file.
In this example, a progress bar is created within the uibutton callback function and it’s updated within a loop. For a complete demo of this feature see this comment .
% Button pushed function: ProcessDataButton function ProcessDataButtonPushed(app, event) % Change button name to "Processing" app.ProcessDataButton.Text = 'Processing...'; % Put text on top of icon app.ProcessDataButton.IconAlignment = 'bottom'; % Create waitbar with same color as button wbar = permute(repmat(app.ProcessDataButton.BackgroundColor,15,1,200),[1,3,2]); % Black frame around waitbar wbar([1,end],:,:) = 0; wbar(:,[1,end],:) = 0; % Load the empty waitbar to the button app.ProcessDataButton.Icon = wbar; % Loop through something and update waitbar n = 10; for i = 1:n % Update image data (royalblue) % if mod(i,10)==0 % update every 10 cycles; improves efficiency currentProg = min(round((size(wbar,2)-2)*(i/n)),size(wbar,2)-2); RGB = app.ProcessDataButton.Icon; RGB(2:end-1, 2:currentProg+1, 1) = 0.25391; % (royalblue) RGB(2:end-1, 2:currentProg+1, 2) = 0.41016; RGB(2:end-1, 2:currentProg+1, 3) = 0.87891;
app.ProcessDataButton.Icon = RGB; % Pause to slow down animation pause(.3) % end end % remove waitbar app.ProcessDataButton.Icon = ''; % Change button name app.ProcessDataButton.Text = 'Process Data'; end
The for-loop above was improved on Feb-11-2022.
Credit for the black & teal GIF icons: lordicon.com
The File Exchange team is excited to announce that File Exchange now supports GitHub Releases!
Contributors can now develop software projects in GitHub without having to manually sync and maintain the same code in File Exchange.
To start using this feature, choose 'GitHub Releases' option when you update your existing File Exchange submission or link a new repository to File Exchange.
When you link your GitHub repository to File Exchange using GitHub Releases, your File Exchange submission will automatically update when you create a new release in GitHub that is compatible with File Exchange. In addition, if you package your code as a toolbox (.mltbx) and attach the toolbox package to your latest GitHub release, File Exchange will provide the toolbox to your users for download. If you do not attach a toolbox to the release, File Exchange will provide the zip release asset for download.
See this page for more details.
We encourage you to try out this feature and let us know of any feedback you have by replying below.
This week is National Volunteer Week in the USA and Canada and to celebrate, I’d like to pay tribute to the volunteers in the Matlab Central Answers forum who have given countless hours to help total strangers make progress in their education, careers, and hobbies.
As of April 20, 2020, there have been 375,869 [1,2] questions asked by 183,968 [3] contributors dating back to the earliest existing question on January 4, 2011.
41,890 volunteers have contributed at least one answer leading up to 68% of the questions answered.
There is no contribution too small for earning well-deserved recognition and appreciation. A single answer or comment may benefit countless individuals who finally find the ideal solution to a problem that kept them up at night.
A number of volunteers in the forum have contributed far and beyond the imaginable and have shared so much of their time and expertise that it’s difficult to fathom. The bar graph below shows the top 10 volunteers in the forum by the number of answers provided. It’s hard to believe that Walter Roberson , a single individual ( we think ), has contributed a portion of answers equal to more than 18% of answered questions in the forum [4]. The top two volunteers, adding Image Analyst , contributed enough answers to equal almost 30% of the answered questions. These folks along with many others not listed in the bar graph who can be found on the contributors page are the foundation of so many Matlab users’ success including my own from June 2014, when I asked my first question.
Whether you’ve come to the forum to look for an answer or to write an answer, you’re undoubtedly standing on the shoulders of giants.
Footnotes
- Based on the number of answered and unanswered questions listed in the ‘Status’ table in recently added questions .
- Questions and answers posted by the MathWorks Support Team are not included in the data presented here, though much appreciated.
- The number of people who provided an answer is based on sorting the contributors page by ‘answers given’ in descending order.
- Since a contributor can write more than one answer to a question, we can’t easily measure the number of questions answered by a contributor.
New to r2020a, leapseconds() creates a table showing all leap seconds recognized by the datetime data type.
>> leapseconds() ans = 27×2 timetable Date Type CumulativeAdjustment ___________ ____ ____________________ 30-Jun-1972 + 1 sec 31-Dec-1972 + 2 sec 31-Dec-1973 + 3 sec 31-Dec-1974 + 4 sec 31-Dec-1975 + 5 sec << 21 rows removed >> 31-Dec-2016 + 27 sec
Leap seconds can covertly sneak into your data and cause errors that are difficult to resolve if the leap seconds go undetected. A leap second was responsible for crashing Reddit , Mozilla, Yelp, LinkedIn and other sites in 2012.
Detect leap seconds present in a vector of years
% Define a vector of years t = 2005:2008;
allLeapSeconds = leapseconds(); isYearWithLeapSecond = ismember(t,year(allLeapSeconds.Date));
% Show years that contain a leap second t(isYearWithLeapSecond) ans = 2005 2008
Detect leap seconds present in a vector of months
% Define a vector of months t = datetime(1972, 1, 1) + calmonths(0:11);
t.Format = 'MMM-yyyy'; allLeapSeconds = leapseconds(); [tY,tM] = ymd(t); [leapSecY, leapSecM] = ymd(allLeapSeconds.Date); isMonthWithLeapSecond = ismember([tY(:),tM(:)], [leapSecY, leapSecM], 'rows');
% Show months that contain a leap second t(isMonthWithLeapSecond) ans = 1×2 datetime array Jun-1972 Dec-1972
List all leap seconds in your lifetime
% Enter your birthday in mm/dd/yyyy hh:mm:ss format yourBirthday = '01/15/1988 14:30:00';
yourTimeRange = timerange(datetime(yourBirthday), datetime('now')); allLeapSeconds = leapseconds(); lifeLeapSeconds = allLeapSeconds(yourTimeRange,:); lifeLeapSeconds.YourAge = lifeLeapSeconds.Date - datetime(yourBirthday); lifeLeapSeconds.YourAge.Format = 'y';
% Show table fprintf('\n Leap seconds in your lifetime:\n') disp(lifeLeapSeconds)
Leap seconds in your lifetime: Date Type CumulativeAdjustment YourAge ___________ ____ ____________________ __________ 31-Dec-1989 + 15 sec 1.9587 yrs 31-Dec-1990 + 16 sec 2.958 yrs << 8 rows removed >> 30-Jun-2012 + 25 sec 24.456 yrs 30-Jun-2015 + 26 sec 27.454 yrs 31-Dec-2016 + 27 sec 28.96 yrs
What is a leap second?
A second is defined as the time it takes a cesium-133 atom to oscillate 9,192,631,770 times under controlled conditions. The transition frequency is so precise that it takes 100 million years to gain 1 second of error [1]. If the earth’s rotation were perfectly synchronized to the atomic second, a day would be 86,400 seconds. But the earth’s rate of rotation is affected by climate, winds, atmospheric pressure, and the rate of rotation is gradually decreasing due to tidal friction [2,3]. Several months before the expected difference between the atomic clock-based time (UTC) and universal time (UT1) reaches +/- 0.9 seconds the IERS authorizes the addition (or subtraction) of 1 leap second (see plot below). Since the first leap second in 1972, all leap second adjustments have been made on the last day of June or December and all adjustments have been +1 second which explains the + signs in the type column of the leapseconds() table.
[ Image source ]
How to reference the leap second schedule in your code
Since leap second adjustments are not regularly timed, you can record the official IERS Bulletin C version used at the time of your analysis by accessing the 2nd output to leapseconds().
[T,vers] = leapseconds
What do leap seconds look like in datetime values?
A minute typically has 60 seconds spanning from 0:59. A minute containing a leap second has 61 seconds spanning from 0:60.
December 30, 2016 was a normal day. If we add the usual 86400 seconds to the start of that day, the result is the start of the next day.
d = datetime(2016, 12, 30, 'TimeZone','UTCLeapSeconds') + seconds(86400) d = datetime 2016-12-31T00:00:00.000Z
The next day, December 31, 2016, had a leap second. If we add 86400 seconds to the start of that day, the result is not the start of the next day.
d = datetime(2016, 12, 31, 'TimeZone','UTCLeapSeconds') + seconds(86400) d = datetime 2016-12-31T23:59:60.000Z
When will the next leap second be?
As of the current date (April 2020) the timing of the next leap second is unknown. Based on the data from the plot above, what's your guess?
References
Do date ranges from two different timetables intersect?
Is a specific datetime value within the range of a timetable?
Is the range of row times in a timetable within the limits of a datetime array?
Three new functions in r2020a will help to answer these questions.
- [tf, whichRows] = containsrange(TT,__)
- [tf, whichRows] = overlapsrange(TT,__)
- [tf, whichRows] = withinrange(TT,__)
In these function inputs, TT is a timetable and input #2 is one of the following:
- another timetable
- a time range object produced by timerange(startTime,endTime)
- a datetime scalar produced by datetime()
- a duration
The tf output is a logical scalar indicating pass|fail and the whichRows output is a logical vector identifying the rows of TT that are within the specified time range.
How do these functions differ?
Let's test all 3 functions with different time ranges and a timetable of electric utility outages in the United States, provided by Matlab. The first few rows of outages.csv are shown below in a timetable. You can see that the row times are not sorted which won't affect the behavior of these functions.
8×5 timetable OutageTime Region Loss Customers RestorationTime Cause ________________ _____________ ______ __________ ________________ ___________________ 2002-02-01 12:18 {'SouthWest'} 458.98 1.8202e+06 2002-02-07 16:50 {'winter storm' } 2003-01-23 00:49 {'SouthEast'} 530.14 2.1204e+05 NaT {'winter storm' } 2003-02-07 21:15 {'SouthEast'} 289.4 1.4294e+05 2003-02-17 08:14 {'winter storm' } 2004-04-06 05:44 {'West' } 434.81 3.4037e+05 2004-04-06 06:10 {'equipment fault'} 2002-03-16 06:18 {'MidWest' } 186.44 2.1275e+05 2002-03-18 23:23 {'severe storm' } 2003-06-18 02:49 {'West' } 0 0 2003-06-18 10:54 {'attack' } 2004-06-20 14:39 {'West' } 231.29 NaN 2004-06-20 19:16 {'equipment fault'} 2002-06-06 19:28 {'West' } 311.86 NaN 2002-06-07 00:51 {'equipment fault'}
The range of times in utility.csv is depicted by the gray timeline bar in the plot below labeled "Timetable row times". The timeline bars above it are various time ranges or scalar datetime values used to test the three new functions.
The three columns of checkboxes on the right of the plot show the results of the first output of each function, tf, for each time range.
The time ranges were created by the timerange function using the syntax timerange(startTime,endTime). The default intervalType in timerange() is 'openright' which means that datetimes are matched when they are equal to or greater than startTime and less than but not equal to endTime. The scalar datetime values were created with datetime().
The colorbar along with the colored points at the bottom of each timeline bar show the row numbers of timetable TT that were selected by the whichRows output of the three functions.
The containsrange() function returns true when all of the time range values are within the timetable including the timetable's minimum and maximum datetime.
The overlapsrange() function returns true when any of the time range values are with the timetable's range.
The withinrange() function returns true only when all of the timetable's datetime values are within the time range values. A careful observer may see that comparison number 1 is false even though that time range is exactly equal to the timetable's row time range. This is because the default value for intervalType in the timerange() function is 'openright' which does not match the end values if they are equal. If you change the intervalType to 'closed' the withinrange result for comparison 1 would be true.
The scalar datetime values for comparisons 8, 9 and 10 are all exact matches of datetimes within the timetable and result in a single match in the whichRows output. The datetime values for comparisons 7 and 11 do not match any of the timetable row times and the values in whichRows are all false even though comparison 7 is within the timetable range. For all three tests, the whichRows outputs are identical.
---------------------------------------------------------------
Here is the code used to generate this data, test the functions, and produce the plot.
% Read in the outage data as a table and convert it to a timetable TT. T = readtable('outages.csv'); TT = table2timetable(T);
% Look at the first few rows. head(TT) % Show that row time vector is not sorted. issorted(TT)
% Get the earliest and latest row time. outageTimeLims = [min(TT.OutageTime), max(TT.OutageTime)];
% Define time ranges to test [start,end] or scalar times [datetime, NaT] % The scalar times must be listed after time ranges. dateRanges = [ % start, end outageTimeLims; % original data outageTimeLims; % equal time range datetime(2005,2,1), datetime(2011,2,1); % all within datetime(1998,3,16), datetime(2018,4,11); % starts before, ends after datetime(2000,1,1), datetime(2010,4,11); % starts before, ends within datetime(2009,1,15), datetime(2019, 4,7); % starts within, ends after datetime(2015,6,15), datetime(2019,12,31); % all outside datetime(2008,6,6), NaT; % 1-value, inside, not a member [TT.OutageTime(find(year(TT.OutageTime)==2010,1)), NaT] % 1 value, inside, is a member outageTimeLims(1), NaT; % 1-value, on left edge outageTimeLims(2), NaT; % 1-value, on right edge datetime(2000,6,6), NaT; % 1-value, outside ]; nRanges = size(dateRanges,1); dateRangeLims = [min(dateRanges,[],'all'), max(dateRanges,[],'all')];
% Set up the figure and axes uifig = uifigure('Name', 'Timetable_intersection_demo', 'Resize', 'off'); uifig.Position(3:4) = [874,580]; movegui(uifig,'center') uiax = uiaxes(uifig, 'Position', [0,0,uifig.Position(3:4)], 'box', 'on', 'YAxisLocation', 'right',... 'ytick', -.5:1:nRanges, 'YTickLabel', [], 'ylim', [-3.5, nRanges], 'FontSize', 18); hold(uiax, 'on') grid(uiax, 'on') uiax.Toolbar.Visible = 'off'; % Add axes labels & title title(uiax, strrep(uifig.Name,'_',' ')) xlabel(uiax, 'Timeline') ylab = ylabel(uiax, 'Comparison number'); set(ylab, 'Rotation', -90, 'VerticalAlignment', 'Bottom')
% Add the timetable frame fill(uiax, outageTimeLims([1,2,2,1]), [-.7,-.7,nRanges-.3,nRanges-.3] , 0.85938*[1,1,1], ... %gainsboro 'EdgeColor', 0.41016*[1,1,1], 'LineStyle', '--', 'LineWidth', 1.5, 'FaceAlpha', .25) %dimgray
% Set xtick & xlim after x-axis is converted to datetime range = @(x)max(x)-min(x); uiax.XLim = dateRangeLims + range(dateRangeLims).*[-.01, .40]; uiax.XTick = dateshift(dateRangeLims(1),'start','Year') : calyears(2) : dateshift(dateRangeLims(2),'start','Year','next'); xtickformat(uiax,'yyyy')
% Set up timeline plot lineColors = [0.41016*[1,1,1]; lines(nRanges-1)]; %dimGray uiax.Colormap = parula(size(TT,1)); tfUniCodes = {char(09745),char(09746)}; %{true, false} checkbox characters barHeight = 0.8; rightMargin = [max(dateRangeLims),max(uiax.XLim)]; tfCenters = linspace(rightMargin(1),rightMargin(2),5); tfCenters([1,end]) = []; intervalType = 'openright'; % open, closed, openleft openright(def); see https://www.mathworks.com/help/matlab/ref/timerange.html#bvdk6vh-intervalType
% Loop through each row of dateRanges for i = 0:nRanges-1 % Plot timeline bar pObj = fill(uiax, dateRanges(i+1,[1,2,2,1]), i+[-barHeight,-barHeight,barHeight,barHeight]/2, lineColors(i+1,:), 'FaceAlpha', .4);
% Evaluate date ranges and single values differently if any(isnat(dateRanges(i+1,:))) % Test single datetime tr = dateRanges(i+1,~isnat(dateRanges(i+1,:))); set(pObj, 'LineWidth', 3, 'EdgeAlpha', .6, 'EdgeColor', lineColors(i+1,:)) else % Test date range tr = timerange(dateRanges(i+1,1), dateRanges(i+1,2),intervalType); end
% Create timerange obj and test for intersections [tf(1), whichRows{1}] = containsrange(TT,tr); [tf(2), whichRows{2}] = overlapsrange(TT,tr); [tf(3), whichRows{3}] = withinrange(TT,tr); % Confirm that all 'whichRows' are equal assert(isequal(whichRows{1},whichRows{2},whichRows{3}), 'Unequal whichRows outputs.')
if i>0 % Add pass/fail checkboxes text(uiax, tfCenters(tf), repmat(i,1,sum(tf)), repmat(tfUniCodes(1),1,sum(tf)), ... 'HorizontalAlignment', 'Center', 'Color', [0 .5 0], 'FontSize', 36, 'FontWeight', 'Bold') % Fail checkboxes text(uiax, tfCenters(~tf), repmat(i,1,sum(~tf)), repmat(tfUniCodes(2),1,sum(~tf)), ... 'HorizontalAlignment', 'Center', 'Color', [1 0 0], 'FontSize', 36, 'FontWeight', 'Bold')
% Plot the TT row number matches scatter(uiax, TT.OutageTime(whichRows{1}), repmat(i-barHeight/2+.1,1,sum(whichRows{1})), ... 10, uiax.Colormap(whichRows{1},:), 'filled', 'MarkerFaceAlpha', 0.2) else % add stripes to reference bar xHatch = linspace(dateRanges(i+1,1)-days(2), dateRanges(i+1,2)+days(2), 20); yHatch = repmat(unique(pObj.YData), 1, 19); plot(uiax, [xHatch(1:end-1);xHatch(2:end)], yHatch, '-', 'Color', [1 1 1 .5], 'LineWidth', 4) pObj.FaceAlpha = .9; text(uiax, mean(outageTimeLims), 0, 'Timetable row times', 'FontSize', 20, ... 'HorizontalAlignment', 'Center', 'VerticalAlignment', 'middle', 'FontWeight', 'Bold') end end
% Draw frame around checkboxs for duration comparisons and label intervalType rectEdges = linspace(rightMargin(1),rightMargin(2),33); nDurations = sum(~isnat(dateRanges(:,2)))-1; fill(uiax, rectEdges([6,end-5,end-5,6]), [.4 .4 nDurations+[.4,.4]], 'w', ... 'FaceAlpha', 0, 'LineWidth', 1.5, 'EdgeColor', 0.41016*[1,1,1]) % dimgray text(uiax, rectEdges(6), nDurations/2+.5, sprintf('intervalType: %s', intervalType), 'FontSize', 16, ... 'HorizontalAlignment', 'Center', 'VerticalAlignment', 'Bottom', 'Rotation', 90)
% Add text labels for checkboxes and comparison number text(uiax, tfCenters, [.5 .5 .5], {'containsrange ', 'overlapsrange ', 'withinrange '}, 'Fontsize', 22, 'Rotation', 90, ... 'HorizontalAlignment', 'right', 'FontWeight', 'b') text(uiax, repmat(rightMargin(2)-range(xlim(uiax))*.02,1,nRanges-1), 1:nRanges-1, cellstr(num2str((1:nRanges-1)')), ... 'HorizontalAlignment', 'Right', 'FontSize', 16)
% Add color bar; position it under the timetable bar (must be done after all other axes properties are set) % requires coordinate tranformation from data units to fig position units. cb = colorbar(uiax, 'Location', 'South', 'TickDirection', 'Out', 'YAxisLocation', 'Bottom', 'FontSize', 11); caxis(uiax, [1,size(TT,1)]) cb.Position(3) = range(outageTimeLims)/range(xlim(uiax)) * (uiax.InnerPosition(3)/uifig.Position(3)); cb.Position(1) = ((outageTimeLims(1)-min(xlim(uiax)))/range(xlim(uiax)) * uiax.InnerPosition(3) + uiax.InnerPosition(1)) / uifig.Position(3); cb.Position(4) = 0.008; cb.Position(2) = ((-barHeight-min(ylim(uiax))-.5)/range(ylim(uiax)) * uiax.InnerPosition(4) + uiax.InnerPosition(2)) / uifig.Position(4); ylabel(cb, 'Timetable row number')
The following is a list of updates and new features for File Exchange and Cody.
New Features
- (Coming soon) Support for GitHub releases added to File Exchange - Stay tuned for an upcoming announcement later this month.
- 2 new Cody problem groups - Visit Cody to see the new MATLAB Basics and Beyond the Basics problem groups.
Let us know what you think about these new features by replying below.