1
\$\begingroup\$

About this code

This Matlab function takes in the handle of a figure and exports a PDF containing a vector image of the plot.

  • The caller of this function can opt to even remove the axes if he or she really just wants the plot area (this also removes text like the plot title, units, etc.)

Any feedback is welcome but I am particularly looking for the following things:

  • Ways to improve readability.
  • Built in methods that I may have missed to make this function simpler or easier to follow.
  • Habits that I may have inherited from C or C# that I should avoid.
  • Corner cases for figures I missed that cause bugs (this works on every figure I have thrown at it, to the best of my knowledge this is working).

The function's code:

function SaveFigureAsVectorPDF(InputFigureHandle, OutFileName, ShouldPrintAxes)
    %% Check input parameters
    [NumberOfFigures, ~] = size(InputFigureHandle);    

    if(NumberOfFigures ~= 1)
        error('This function only supports one figure handle.');

    end

    if(isempty(OutFileName))
        error('No file path provided to save the figure to.');

    end

    cUnit = 'centimeters';

    %% Copy the input figure so we can mess with it    
    %Make a copy of the figure so we don't modify the properties of the
    %original.
    FigureHandleCopy = copy(InputFigureHandle);

    %NOTE:  Do not set this figure to be invisible, for some bizarre reason
    %       it must be visible otherwise Matlab will just ignore attempts 
    %       to set its properties.
    % 
    %       I would prefer it if the figure did not briefly flicker into
    %       view but I am not sure how to prevent that.

    %% Find the axis handle
    ChildAxisHandles     = get(FigureHandleCopy, 'Children');
    NumberOfChildFigures = length(ChildAxisHandles);

    if(NumberOfChildFigures ~= 1)
       %note that every plot has at least one child figure
       error('This function currently only supports plots with one child figure.');

    end

    AxisHandle = ChildAxisHandles(1);

    %% Set Units
    % It doesn't matter what unit you choose as long as it's the same for
    % the figure, axis, and paper. Note that 'PaperUnits' unfortunately
    % does not support 'pixels' units.

    set(FigureHandleCopy,   'PaperUnits',   cUnit);
    set(FigureHandleCopy,   'Unit',         cUnit);
    set(AxisHandle,         'Unit',         cUnit); 

    %% Get old axis position and inset offsets 
    %Note that the axes and title are contained in the inset
    OldAxisPosition = get(AxisHandle,   'Position');
    OldAxisInset    = get(AxisHandle,   'TightInset');

    OldAxisWidth    = OldAxisPosition(3);
    OldAxisHeight   = OldAxisPosition(4);

    OldAxisInsetLeft    = OldAxisInset(1);
    OldAxisInsetBottom  = OldAxisInset(2);
    OldAxisInsetRight   = OldAxisInset(3);
    OldAxisInsetTop     = OldAxisInset(4);

    %% Set positions and size of the figure and the Axis 
    if(~ShouldPrintAxes)

        FigurePosition = [0.0, 0.0, OldAxisWidth, OldAxisHeight];

        PaperSize = [OldAxisWidth, OldAxisHeight];

        AxisPosition = FigurePosition;      

    else
        WidthWithInset  = OldAxisWidth   + OldAxisInsetLeft + OldAxisInsetRight;
        HeightWithInset = OldAxisHeight  + OldAxisInsetTop  + OldAxisInsetBottom;

        FigurePosition = [0.0, 0.0, WidthWithInset,  HeightWithInset];

        PaperSize = [WidthWithInset, HeightWithInset];

        AxisPosition = [OldAxisInsetLeft, OldAxisInsetBottom, OldAxisWidth, OldAxisHeight];

    end

    set(FigureHandleCopy,   'Position', FigurePosition);
    set(AxisHandle,         'Position', AxisPosition);

    %Note:  these properties do not effect the preview but they are
    %       absolutely necessary for the pdf!!
    set(FigureHandleCopy,   'PaperSize',        PaperSize);
    set(FigureHandleCopy,   'PaperPosition',    FigurePosition);

    %% Write the figure to the PDF file
    print('-dpdf', OutFileName);

    set(FigureHandleCopy, 'name', 'PDF Figure Preview', 'numbertitle', 'off');

    %If you want to see the figure (e.g., for debugging purposes), comment
    %the line below out.
    close(FigureHandleCopy);

end

Usage / Testing Script:

%% Generates a graph and saves it to pdf

FigureHandle = figure;

plot(1:100);

title('testing');

%with axes
SaveFigureAsVectorPDF(FigureHandle, 'withaxes.pdf', true);

%without axes
SaveFigureAsVectorPDF(FigureHandle, 'withoutaxes.pdf', false);

Sample images:

With axes:

Sample PDF screenshot with axes

Without axes:

Sample PDF screenshot without axes

Note: I posted this code as an answer on Stack Overflow.

\$\endgroup\$
  • \$\begingroup\$ If anyone's wondering why I haven't updated my SO answer with the helpful suggestions provided here, well, unfortunately my Matlab license expired and I don't like the idea of editing in changes that I can't test... \$\endgroup\$ – jrh Jul 10 '18 at 20:28
2
\$\begingroup\$

I don't have MATLAB currently, so I can't run your code. I don't have much to comment, but there's one thing I'd change if I were you.


[NumberOfFigures, ~] = size(InputFigureHandle);
if(NumberOfFigures ~= 1)
    error('This function only supports one figure handle.');
end

Size will return the number of [rows, columns] of the InputFigureHandle. You discard the column number, so this checks only the number of rows. This can be done like this:

NumberOfFigures = size(InputFigureHandle, 1);

But, this will only work if there are several figure handles in a vertical vector. If there are several figure handles, but over several columns on one row, then this will not work as you expect. Therefore, I suggest you substitute size with numel (number of elements):

NumberOfFigures = numel(InputFigureHandle)

You have very good variable names, and the input verification is good. If you're sharing this function with others, I'd suggest adding the following in the start of the function:

 function SaveFigureAsVectorPDF(InputFigureHandle, OutFileName, ShouldPrintAxes)
 % SaveFigureAsVectorPDF Saves a figure as a pdf file.
 %   InputFigureHandle must contain exactly one figure handle
 %   OutFileName must contain exactly one OutFileName
 %   ShouldPrintAxes must be a boolean true / false that determines if the axes should be printed.

This will allow you to both read the description in the editor, but also write help SaveFigureAsVectorPDF to get the explanation shown in the GUI. This is similar to what you get if you type help sum, help conj etc.


I suggest that you make ShouldPrintAxes an optional input, and gives it a default value of true or false (depending on what you think is most common).


Other than that:

  • I like the way you name your variables!
  • I don't like the newlines in front of end. I've never seen it in MATLAB code, and I personally think it looks awkward. But that might be me.
\$\endgroup\$
  • \$\begingroup\$ Very good point on numel, I was not aware of that command. Also I forgot about Matlab's editor support for documentation. As for end my coding style is a slight variant on allman style, I guess I put an extra newline in there because end sort of blends in more with the body text than }. Thanks for the review! \$\endgroup\$ – jrh Aug 30 '17 at 0:18
1
\$\begingroup\$

A few more comments:

The line

set(FigureHandleCopy, 'name', ...

comes after print. It will likely not do anything to the output.

What is the purpose of setting units, if you don't provide any values? The additions you do should be valid with any units. Ah, it is to make sure axes and figure have the same units. That makes sense now. Consider adding a comment for that.

I always use the same set(h,'property',value) syntax, because I've been using MATLAB for so long, but do consider the new h.property=value syntax. I find it clearer. Especially if you only set one property with each statement anyway. Otherwise, consider setting all properties with one statement:

set(FigureHandleCopy, ...
   'PaperSize', PaperSize, ...
   'PaperPosition', FigurePosition);

I often find less verbose code easier to read... :)

Cool useful function, by the way! I'm always annoyed at MATLAB using a full page for PDF output, instead of adjusting page size to the figure like it does for all other file formats.

\$\endgroup\$
  • \$\begingroup\$ Thanks, good points. I did not know about the new h.property= syntax. Technically I already have a comment regarding the units -- % It doesn't matter what unit you choose as long as it's the same for % the figure, axis, and paper. Was the comment unclear, or would it help if I moved that comment somewhere else? Also, just wondering, why do you find less verbose code easier to read? Terse code seems to be popular in Matlab and I've been curious if there's a real benefit to it or if it's just a holdover from math equations (/ punch cards?). \$\endgroup\$ – jrh Dec 8 '17 at 15:33
  • \$\begingroup\$ Sorry, you're right, the comment was there. I guess I missed the nuance on the first read. Verbosity in code is good if it makes the intention clearer. Verbosity that doesn't add anything (like repeating set(FigureHandleCopy for each property set) doesn't make the intention clearer. Instead it makes me read more text to know what is going on. This might very well be an old-fart preference (though I'm not old enough to have used punch cards, that's my father in law). \$\endgroup\$ – Cris Luengo Dec 8 '17 at 16:31
  • \$\begingroup\$ Ok, makes sense. I agree that the set statements should be combined into one single set (the only reason why I didn't was that I forgot that set supported multiple properties), I wasn't sure if you were referring to that or the long variable names (e.g., if you would prefer h instead of FigureHandleCopy). \$\endgroup\$ – jrh Dec 8 '17 at 17:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.