function matlab2tikz(varargin)
    %MATLAB2TIKZ    Save figure in native LaTeX (TikZ/Pgfplots).
    %   MATLAB2TIKZ() saves the current figure as LaTeX file.
    %   MATLAB2TIKZ comes with several options that can be combined at will. For
    %   example:
    %
    %       MATLAB2TIKZ('showInfo', false, 'strict', true, "figure.tex")
    %
    %   MATLAB2TIKZ(FILENAME,...) or MATLAB2TIKZ('filename',FILENAME,...)
    %   stores the LaTeX code in FILENAME.
    %
    %   MATLAB2TIKZ('filehandle',FILEHANDLE,...) stores the LaTeX code in the file
    %   referenced by FILEHANDLE. (default: [])
    %
    %   MATLAB2TIKZ('figurehandle',FIGUREHANDLE,...) explicitly specifies the
    %   handle of the figure that is to be stored. (default: gcf)
    %
    %   MATLAB2TIKZ('colormap',DOUBLE,...) explicitly specifies the colormap to be
    %   used. (default: current color map)
    %
    %   MATLAB2TIKZ('strict',BOOL,...) tells MATLAB2TIKZ to adhere to MATLAB(R)
    %   conventions wherever there is room for relaxation. (default: false)
    %
    %   MATLAB2TIKZ('strictFontSize',BOOL,...) retains the exact font sizes
    %   specified in MATLAB for the TikZ code. This goes against normal LaTeX
    %   practice. (default: false)
    %
    %   MATLAB2TIKZ('showInfo',BOOL,...) turns informational output on or off.
    %   (default: true)
    %
    %   MATLAB2TIKZ('showWarnings',BOOL,...) turns warnings on or off.
    %   (default: true)
    %
    %   MATLAB2TIKZ('imagesAsPng',BOOL,...) stores MATLAB(R) images as (lossless)
    %   PNG files. This is more efficient than storing the image color data as TikZ
    %   matrix. (default: true)
    %
    %   MATLAB2TIKZ('externalData',BOOL,...) stores all data points in external
    %   files as tab separated values (TSV files). (default: false)
    %
    %   MATLAB2TIKZ('dataPath',CHAR, ...) defines where external data files
    %   and/or PNG figures are saved. It can be either an absolute or a relative
    %   path with respect to your MATLAB work directory. By default, data files are
    %   placed in the same directory as the TikZ output file. To place data files
    %   in your MATLAB work directory, you can use '.'. (default: [])
    %
    %   MATLAB2TIKZ('relativeDataPath',CHAR, ...) tells MATLAB2TIKZ to use the
    %   given path to follow the external data files and PNG files. This is the
    %   relative path from your main LaTeX file to the data file directory.
    %   By default the same directory is used as the output (default: [])
    %
    %   MATLAB2TIKZ('height',CHAR,...) sets the height of the image. This can be
    %   any LaTeX-compatible length, e.g., '3in' or '5cm' or '0.5\textwidth'.  If
    %   unspecified, MATLAB2TIKZ tries to make a reasonable guess.
    %
    %   MATLAB2TIKZ('width',CHAR,...) sets the width of the image.
    %   If unspecified, MATLAB2TIKZ tries to make a reasonable guess.
    %
    %   MATLAB2TIKZ('noSize',BOOL,...) determines whether 'width', 'height', and
    %   'scale only axis' are specified in the generated TikZ output. For compatibility with the
    %   tikzscale package set this to true. (default: false)
    %
    %   MATLAB2TIKZ('extraCode',CHAR or CELLCHAR,...) explicitly adds extra code
    %   at the beginning of the output file. (default: [])
    %
    %   MATLAB2TIKZ('extraCodeAtEnd',CHAR or CELLCHAR,...) explicitly adds extra
    %   code at the end of the output file. (default: [])
    %
    %   MATLAB2TIKZ('extraAxisOptions',CHAR or CELLCHAR,...) explicitly adds extra
    %   options to the Pgfplots axis environment. (default: [])
    %
    %   MATLAB2TIKZ('extraColors', {{'name',[R G B]}, ...} , ...) adds
    %   user-defined named RGB-color definitions to the TikZ output.
    %   R, G and B are expected between 0 and 1. (default: {})
    %
    %   MATLAB2TIKZ('extraTikzpictureOptions',CHAR or CELLCHAR,...)
    %   explicitly adds extra options to the tikzpicture environment. (default: [])
    %
    %   MATLAB2TIKZ('encoding',CHAR,...) sets the encoding of the output file.
    %
    %   MATLAB2TIKZ('floatFormat',CHAR,...) sets the format used for float values.
    %   You can use this to decrease the file size. (default: '%.15g')
    %
    %   MATLAB2TIKZ('maxChunkLength',INT,...) sets maximum number of data points
    %   per \addplot for line plots (default: 4000)
    %
    %   MATLAB2TIKZ('parseStrings',BOOL,...) determines whether title, axes labels
    %   and the like are parsed into LaTeX by MATLAB2TIKZ's parser.
    %   If you want greater flexibility, set this to false and use straight LaTeX
    %   for your labels. (default: true)
    %
    %   MATLAB2TIKZ('parseStringsAsMath',BOOL,...) determines whether to use TeX's
    %   math mode for more characters (e.g. operators and figures). (default: false)
    %
    %   MATLAB2TIKZ('showHiddenStrings',BOOL,...) determines whether to show
    %   strings whose were deliberately hidden. This is usually unnecessary, but
    %   can come in handy for unusual plot types (e.g., polar plots). (default:
    %   false)
    %
    %   MATLAB2TIKZ('interpretTickLabelsAsTex',BOOL,...) determines whether to
    %   interpret tick labels as TeX. MATLAB(R) doesn't allow to do that in R2014a
    %   or before. In R2014b and later, please set the "TickLabelInterpreter"
    %   property of the relevant axis to get the same effect. (default: false)
    %
    %   MATLAB2TIKZ('arrowHeadSize', FLOAT, ...) allows to resize the arrow heads
    %   in quiver plots by rescaling the arrow heads by a positive scalar. (default: 10)
    %
    %   MATLAB2TIKZ('tikzFileComment',CHAR,...) adds a custom comment to the header
    %   of the output file. (default: '')
    %
    %   MATLAB2TIKZ('addLabels',BOOL,...) add labels to plots: using Tag property
    %   or automatic names (where applicable) which make it possible to refer to
    %   them using \ref{...} (e.g., in the caption of a figure). (default: false)
    %
    %   MATLAB2TIKZ('standalone',BOOL,...) determines whether to produce
    %   a standalone compilable LaTeX file. Setting this to true may be useful for
    %   taking a peek at what the figure will look like. (default: false)
    %
    %   MATLAB2TIKZ('checkForUpdates',BOOL,...) determines whether to automatically
    %   check for updates of matlab2tikz. (default: true (if not using git))
    %
    %   MATLAB2TIKZ('semanticLineWidths',CELLMATRIX,...) allows you to customize
    %   the mapping of semantic "line width" values.
    %   A valid entry is an Nx2 cell array:
    %     - the first column contains the semantic names,
    %     - the second column contains the corresponding line widths in points.
    %   The entries you provide are used in addition to the pgf defaults:
    %     {'ultra thin', 0.1; 'very thin' , 0.2; 'thin', 0.4; 'semithick', 0.6;
    %      'thick'     , 0.8; 'very thick', 1.2; 'ultra thick', 1.6}
    %   or a single "NaN" can be provided to turn off this feature alltogether.
    %   If you specify the default names, their mapping will be overwritten.
    %   Inside your LaTeX document, you are responsible to make sure these TikZ
    %   styles are properly defined.
    %   (Default: NaN)
    %
    %   Example
    %      x = -pi:pi/10:pi;
    %      y = tan(sin(x)) - sin(tan(x));
    %      plot(x,y,'--rs');
    %      matlab2tikz('myfile.tex');
    %
    %   See also: cleanfigure

    %% Check if we are in MATLAB or Octave.
    minimalVersion = struct('MATLAB', struct('name','2014a', 'num',[8 3]), ...
                            'Octave', struct('name','3.8', 'num',[3 8]));
    checkDeprecatedEnvironment(minimalVersion);

    m2t.args            = []; % For command line arguments
    m2t.current         = []; % For currently active objects
    m2t.transform       = []; % For hgtransform groups
    m2t.pgfplotsVersion = [1,3];
    m2t.about.name      = 'matlab2tikz';
    m2t.about.version   = '1.1.0';
    m2t.about.years     = '2008--2016';
    m2t.about.website   = 'http://www.mathworks.com/matlabcentral/fileexchange/22022-matlab2tikz-matlab2tikz';
    m2t.about.github    = 'https://github.com/matlab2tikz/matlab2tikz';
    m2t.about.wiki      = [m2t.about.github '/wiki'];
    m2t.about.issues    = [m2t.about.github '/issues'];
    m2t.about.develop   = [m2t.about.github '/tree/develop'];
    VCID = VersionControlIdentifier();
    m2t.about.versionFull = strtrim(sprintf('v%s %s', m2t.about.version, VCID));

    m2t.tol = 1.0e-15; % numerical tolerance (e.g. used to test equality of doubles)

    % the actual contents of the TikZ file go here
    m2t.content = struct('name',     '', ...
                         'comment',  [], ...
                         'options',  {opts_new()}, ...
                         'content',  {cell(0)}, ...
                         'children', {cell(0)});
    m2t.preamble = sprintf(['\\usepackage[T1]{fontenc}\n', ...
                            '\\usepackage[utf8]{inputenc}\n', ...
                            '\\usepackage{pgfplots}\n', ...
                            '\\usepackage{grffile}\n', ...
                            '\\pgfplotsset{compat=newest}\n', ...
                            '\\usetikzlibrary{plotmarks}\n', ...
                            '\\usetikzlibrary{arrows.meta}\n', ...
                            '\\usepgfplotslibrary{patchplots}\n', ...
                            '\\usepackage{amsmath}\n']);

    %% scan the options
    ipp = m2tInputParser;

    ipp = ipp.addOptional(ipp, 'filename',   '', @(x) filenameValidation(x,ipp));
    ipp = ipp.addOptional(ipp, 'filehandle', [], @filehandleValidation);

    ipp = ipp.addParamValue(ipp, 'figurehandle', get(0,'CurrentFigure'), @ishandle);
    ipp = ipp.addParamValue(ipp, 'colormap', [], @isnumeric);
    ipp = ipp.addParamValue(ipp, 'strict', false, @islogical);
    ipp = ipp.addParamValue(ipp, 'strictFontSize', false, @islogical);
    ipp = ipp.addParamValue(ipp, 'showInfo', true, @islogical);
    ipp = ipp.addParamValue(ipp, 'showWarnings', true, @islogical);
    ipp = ipp.addParamValue(ipp, 'checkForUpdates', isempty(VCID), @islogical);

    ipp = ipp.addParamValue(ipp, 'semanticLineWidths', NaN, @isValidSemanticLineWidthDefinition);

    ipp = ipp.addParamValue(ipp, 'encoding' , '', @ischar);
    ipp = ipp.addParamValue(ipp, 'standalone', false, @islogical);
    ipp = ipp.addParamValue(ipp, 'tikzFileComment', '', @ischar);
    ipp = ipp.addParamValue(ipp, 'extraColors', {}, @isColorDefinitions);
    ipp = ipp.addParamValue(ipp, 'extraCode', {}, @isCellOrChar);
    ipp = ipp.addParamValue(ipp, 'extraCodeAtEnd', {}, @isCellOrChar);
    ipp = ipp.addParamValue(ipp, 'extraAxisOptions', {}, @isCellOrChar);
    ipp = ipp.addParamValue(ipp, 'extraTikzpictureOptions', {}, @isCellOrChar);
    ipp = ipp.addParamValue(ipp, 'floatFormat', '%.15g', @ischar);
    ipp = ipp.addParamValue(ipp, 'automaticLabels', false, @islogical);
    ipp = ipp.addParamValue(ipp, 'addLabels', false, @islogical);
    ipp = ipp.addParamValue(ipp, 'showHiddenStrings', false, @islogical);
    ipp = ipp.addParamValue(ipp, 'height', '', @ischar);
    ipp = ipp.addParamValue(ipp, 'width' , '', @ischar);
    ipp = ipp.addParamValue(ipp, 'imagesAsPng', true, @islogical);
    ipp = ipp.addParamValue(ipp, 'externalData', false, @islogical);
    ipp = ipp.addParamValue(ipp, 'dataPath', '', @ischar);
    ipp = ipp.addParamValue(ipp, 'relativeDataPath', '', @ischar);
    ipp = ipp.addParamValue(ipp, 'noSize', false, @islogical);
    ipp = ipp.addParamValue(ipp, 'arrowHeadSize', 10, @(x) x>0);

    % Maximum chunk length.
    % TeX parses files line by line with a buffer of size buf_size. If the
    % plot has too many data points, pdfTeX's buffer size may be exceeded.
    % As a work-around, the plot is split into several smaller chunks.
    %
    % What is a "large" array?
    % TeX parser buffer is buf_size=200 000 char on Mac TeXLive, let's say
    % 100 000 to be on the safe side.
    % 1 point is represented by 25 characters (estimation): 2 coordinates (10
    % char), 2 brackets, comma and white space, + 1 extra char.
    % That gives a magic arbitrary number of 4000 data points per array.
    ipp = ipp.addParamValue(ipp, 'maxChunkLength', 4000, @isnumeric);

    % By default strings like axis labels are parsed to match the appearance of
    % strings as closely as possible to that generated by MATLAB.
    % If the user wants to have particular strings in the matlab2tikz output that
    % can't be generated in MATLAB, they can disable string parsing. In that case
    % all strings are piped literally to the LaTeX output.
    ipp = ipp.addParamValue(ipp, 'parseStrings', true, @islogical);

    % In addition to regular string parsing, an additional stage can be enabled
    % which uses TeX's math mode for more characters like figures and operators.
    ipp = ipp.addParamValue(ipp, 'parseStringsAsMath', false, @islogical);

    % As opposed to titles, axis labels and such, MATLAB(R) does not interpret tick
    % labels as TeX. matlab2tikz retains this behavior, but if it is desired to
    % interpret the tick labels as TeX, set this option to true.
    ipp = ipp.addParamValue(ipp, 'interpretTickLabelsAsTex', false, @islogical);

    %% deprecated parameters (will auto-generate warnings upon parse)
    ipp = ipp.addParamValue(ipp, 'relativePngPath', '', @ischar);
    ipp = ipp.deprecateParam(ipp, 'relativePngPath', 'relativeDataPath');
    ipp = ipp.deprecateParam(ipp, 'automaticLabels', 'addLabels');

    %% Finally parse all the arguments
    ipp = ipp.parse(ipp, varargin{:});
    m2t.args = ipp.Results; % store the input arguments back into the m2t data struct

    %% Inform users of potentially dangerous options
    warnAboutParameter(m2t, 'parseStringsAsMath', @(opt)(opt==true), ...
        ['This may produce undesirable string output. For full control over output\n', ...
         'strings please set the parameter "parseStrings" to false.']);
    warnAboutParameter(m2t, 'noSize', @(opt)(opt==true), ...
         'This may impede both axes sizing and placement!');
    warnAboutParameter(m2t, 'imagesAsPng', @(opt)(opt==false), ...
         ['It is highly recommended to use PNG data to store images.\n', ...
          'Make sure to set "imagesAsPng" to true.']);

    %% Do some global initialization
    m2t.color = configureColors(m2t.args.extraColors);
    m2t.semantic.LineWidth = configureSemanticLineWidths(m2t.args.semanticLineWidths);

    % define global counter variables
    m2t.count.pngFile     = 0; % number of PNG files
    m2t.count.tsvFile     = 0; % number of TSV files
    m2t.count.autolabel   = 0; % number of automatic labels
    m2t.count.plotyylabel = 0; % number of plotyy labels

    %% shortcut
    m2t.ff = m2t.args.floatFormat;

    %% add global elements
    if isempty(m2t.args.figurehandle)
        error('matlab2tikz:figureNotFound','MATLAB figure not found.');
    end
    m2t.current.gcf = m2t.args.figurehandle;
    if m2t.args.colormap
        m2t.current.colormap = m2t.args.colormap;
    else
        m2t.current.colormap = get(m2t.current.gcf, 'colormap');
    end

    %% handle output file handle/file name
    [m2t, fid, fileWasOpen] = openFileForOutput(m2t);

    % By default, reference the PNG (if required) from the TikZ file
    % as the file path of the TikZ file itself. This works if the MATLAB script
    % is executed in the same folder where the TeX file sits.
    if isempty(m2t.args.relativeDataPath)
        if ~isempty(m2t.args.relativePngPath)
            %NOTE: eventually break backwards compatibility of relative PNG path
            m2t.relativeDataPath = m2t.args.relativePngPath;
            userWarning(m2t, ['Using "relativePngPath" for "relativeDataPath".', ...
                ' This will stop working in a future release.']);
        else
            m2t.relativeDataPath = m2t.args.relativeDataPath;
        end
    else
        m2t.relativeDataPath = m2t.args.relativeDataPath;
    end
    if isempty(m2t.args.dataPath)
        m2t.dataPath = fileparts(m2t.tikzFileName);
    else
        m2t.dataPath = m2t.args.dataPath;
    end

    %% print some version info to the screen
    userInfo(m2t, ['(To disable info messages, pass [''showInfo'', false] to matlab2tikz.)\n', ...
        '(For all other options, type ''help matlab2tikz''.)\n']);

    userInfo(m2t, '\nThis is %s %s.\n', m2t.about.name, m2t.about.versionFull)

    % In Octave, put a new line and some spaces in between the URLs for clarity.
    % In MATLAB this is not necessary, since the URLs get (shorter) descriptions.
    sep = switchMatOct('', sprintf('\n '));
    versionInfo = ['The latest developments can be retrieved from %s.\n', ...
                   'You can find more documentation on %s and %s.\n', ...
                   'If you encounter bugs or want a new feature, go to %s.\n', ...
                   'Please visit %s to rate %s or download the stable release.\n'];
    userInfo(m2t, versionInfo, ...
             clickableUrl(m2t.about.develop, 'our development branch'), ...
             [sep clickableUrl(m2t.about.github, 'our GitHub page') sep], ...
             [sep clickableUrl(m2t.about.wiki, 'wiki')], ...
             [sep clickableUrl(m2t.about.issues, 'our issue tracker')],...
             [clickableUrl(m2t.about.website, 'FileExchange') sep],...
             m2t.about.name);

    %% Save the figure as TikZ to file
    m2t = saveToFile(m2t, fid, fileWasOpen);

    %% Check for a new matlab2tikz version outside version control
    if m2t.args.checkForUpdates
        m2tUpdater(m2t.about, m2t.args.showInfo);
    end

end
% ==============================================================================
function [m2t, counterValue] = incrementGlobalCounter(m2t, counterName)
    % Increments a global counter value and returns its value
    m2t.count.(counterName) = m2t.count.(counterName) + 1;
    counterValue = m2t.count.(counterName);
end
% ==============================================================================
function colorConfig = configureColors(extraColors)
    % Sets the global color options for matlab2tikz
    colorConfig = struct();

    % Set the color resolution.
    colorConfig.depth     = 48; %[bit] RGB color depth (typical values: 24, 30, 48)
    colorConfig.precision = 2^(-colorConfig.depth/3);
    colorConfig.format    = sprintf('%%0.%df',ceil(-log10(colorConfig.precision)));

    % The following color RGB-values which will need to be defined:
    %
    %   - 'extraNames' contains their designated names,
    %   - 'extraSpecs' their RGB specifications.
    [colorConfig.extraNames, colorConfig.extraSpecs] = ...
        dealColorDefinitions(extraColors);
end
% ==============================================================================
function [m2t, fid, fileWasOpen] = openFileForOutput(m2t)
    % opens the output file and/or show a dialog to select one
    if ~isempty(m2t.args.filehandle)
        fid         = m2t.args.filehandle;
        fileWasOpen = true;
        if ~isempty(m2t.args.filename)
            userWarning(m2t, ...
                'File handle AND file name for output given. File handle used, file name discarded.')
        end
        m2t.tikzFileName = fopen(fid);
    else
        fid         = [];
        fileWasOpen = false;
        % set filename
        if ~isempty(m2t.args.filename)
            filename = m2t.args.filename;
        else
            [filename, pathname] = uiputfile({'*.tex;*.tikz'; '*.*'}, 'Save File');
            filename = fullfile(pathname, filename);
        end
        m2t.tikzFileName = filename;
    end

end
% ==============================================================================
function l = filenameValidation(x, p)
    % is the filename argument NOT another keyword?
    l = ischar(x) && ~any(strcmp(x,p.Parameters)); %FIXME: See #471
end
% ==============================================================================
function l = filehandleValidation(x)
    % is the filehandle the handle to an opened file?
    l = isnumeric(x) && any(x==fopen('all'));
end
% ==============================================================================
function bool = isCellOrChar(x)
    bool = iscell(x) || ischar(x);
end
% ==============================================================================
function bool = isRGBTuple(color)
    % Returns true when the color is a valid RGB tuple
    bool = numel(color) == 3 && ...
           all(isreal(color)) && ...
           all( 0<=color & color<=1 ); % this also disallows NaN entries
end
% ==============================================================================
function bool = isColorDefinitions(colors)
    % Returns true when the input is a cell array of color definitions, i.e.
    %  a cell array with in each cell a cell of the form {'name', [R G B]}
    isValidEntry = @(e)( iscell(e) && ischar(e{1}) && isRGBTuple(e{2}) );

    bool = iscell(colors) && all(cellfun(isValidEntry, colors));
end
% ==============================================================================
function bool = isValidSemanticLineWidthDefinition(defMat)
    % Returns true when the input is a cell array of shape Nx2 and
    % contents in each column a set of string and numerical value as needed
    % for the semanticLineWidth option.
    bool = iscell(defMat) && size(defMat, 2) == 2; % Nx2 cell array
    bool = bool && all(cellfun(@ischar   , defMat(:,1))); % first column: names
    bool = bool && all(cellfun(@isnumeric, defMat(:,2))); % second column: line width in points

    % alternatively: just 1 NaN to remove the defaults
    bool = bool || (numel(defMat)==1 && isnan(defMat));
end
% ==============================================================================
function fid = fileOpenForWrite(m2t, filename)
    % Set the encoding of the output file.
    % Currently only MATLAB supports different encodings.
    fid = -1;

    [filepath] = fileparts(filename);
    if ~exist(filepath,'dir') && ~isempty(filepath)
        mkdir(filepath);
    end

    switch getEnvironment()
        case 'MATLAB'
            fid = fopen(filename, 'w', ...
                        'native', m2t.args.encoding);
        case 'Octave'
            fid = fopen(filename, 'w');
        otherwise
            errorUnknownEnvironment();
    end

    if fid == -1
        error('matlab2tikz:fileOpenError', ...
            'Unable to open file ''%s'' for writing.', filename);
    end
end
% ==============================================================================
function path = TeXpath(path)
    path = strrep(path, filesep, '/');
    % TeX uses '/' as a file separator (as UNIX). Windows, however, uses
    % '\' which is not supported by TeX as a file separator
end
% ==============================================================================
function m2t = saveToFile(m2t, fid, fileWasOpen)
    % Save the figure as TikZ to a file. All other routines are called from here.

    % get all axes handles
    [m2t, axesHandles] = findPlotAxes(m2t, m2t.current.gcf);

    % Turn around the handles vector to make sure that plots that appeared
    % first also appear first in the vector. This makes sure the z-order of
    % superimposed axes is respected and is fundamental for plotyy.
    axesHandles = axesHandles(end:-1:1);

    % Alternative Positioning of axes.
    % Select relevant Axes and draw them.
    [m2t, axesBoundingBox] = getRelevantAxes(m2t, axesHandles);

    m2t.axesBoundingBox = axesBoundingBox;
    m2t.axes = {};
    for relevantAxesHandle = m2t.relevantAxesHandles(:)'
        m2t = drawAxes(m2t, relevantAxesHandle);
    end

    % Handle color bars.
    for cbar = m2t.cbarHandles(:)'
        m2t = handleColorbar(m2t, cbar);
    end

    % Draw annotations
    m2t = drawAnnotations(m2t);

    % Add all axes containers to the file contents.
    for axesContainer = m2t.axes
        m2t.content = addChildren(m2t.content, axesContainer);
    end

    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % actually print the stuff
    minimalPgfplotsVersion = formatPgfplotsVersion(m2t.pgfplotsVersion);

    m2t.content.comment = sprintf('This file was created by %s.\n', m2t.about.name);

    if m2t.args.showInfo
        % disable this info if showInfo=false
        m2t.content.comment = [m2t.content.comment, ...
            sprintf(['\n',...
            'The latest updates can be retrieved from\n', ...
            '  %s\n', ...
            'where you can also make suggestions and rate %s.\n'], ...
            m2t.about.website, m2t.about.name ) ...
            ];
    end

    userInfo(m2t, 'You will need pgfplots version %s or newer to compile the TikZ output.',...
        minimalPgfplotsVersion);

    % Add custom comment.
    if ~isempty(m2t.args.tikzFileComment)
        m2t.content.comment = [m2t.content.comment, ...
            sprintf('\n%s\n', m2t.args.tikzFileComment)
            ];
    end

    m2t.content.name = 'tikzpicture';

    % Add custom TikZ options if any given.
    m2t.content.options = opts_append_userdefined(m2t.content.options, ...
                                   m2t.args.extraTikzpictureOptions);

    m2t.content.colors = generateColorDefinitions(m2t.color);

    % Open file if was not open
    if ~fileWasOpen
        fid = fileOpenForWrite(m2t, m2t.tikzFileName);
        finally_fclose_fid = onCleanup(@() fclose(fid));
    end

    % Finally print it to the file
    fprintf(fid, '%s', texcomment(m2t.content.comment));
    addStandalone(m2t, fid, 'preamble');
    addCustomCode(fid, '', m2t.args.extraCode, '');
    addStandalone(m2t, fid, 'begin');

    printAll(m2t, m2t.content, fid); % actual plotting happens here

    addCustomCode(fid, '\n', m2t.args.extraCodeAtEnd, '');

    addStandalone(m2t, fid, 'end');
end
% ==============================================================================
function addStandalone(m2t, fid, part)
    % writes a part of a standalone LaTeX file definition
    if m2t.args.standalone
        switch part
            case 'preamble'
                fprintf(fid, '\\documentclass[tikz]{standalone}\n%s\n',  m2t.preamble);
            case 'begin'
                fprintf(fid, '\\begin{document}\n');
            case 'end'
                fprintf(fid, '\n\\end{document}');
            otherwise
                error('m2t:unknownStandalonePart', ...
                      'Unknown standalone part "%s"', part);
        end
    end
end
% ==============================================================================
function str = generateColorDefinitions(colorConfig)
    % Output the color definitions to LaTeX
    str   = '';
    names = colorConfig.extraNames;
    specs = colorConfig.extraSpecs;
    ff    = colorConfig.format;

    if ~isempty(names)
        colorDef = cell(1, length(names));
        for k = 1:length(names)
            % Append with '%' to avoid spacing woes in LaTeX
            FORMAT      = ['\\definecolor{%s}{rgb}{' ff ',' ff ',' ff '}%%\n'];
            colorDef{k} = sprintf(FORMAT, names{k}, specs{k});
        end
        str = m2tstrjoin([colorDef, sprintf('%%\n')], '');
    end
end
% ==============================================================================
function [m2t, axesHandles] = findPlotAxes(m2t, fh)
    % find axes handles that are not legends/colorbars
    % store detected legends and colorbars in 'm2t'
    % fh            figure handle
    axesHandles = findall(fh, 'type', 'axes');

    % Remove all legend handles, as they are treated separately.
    if ~isempty(axesHandles)
        % TODO fix for octave
        tagKeyword = switchMatOct('Tag', 'tag');
        % Find all legend handles. This is MATLAB-only.
        m2t.legendHandles = findall(fh, tagKeyword, 'legend');
        m2t.legendHandles = m2t.legendHandles(:)';
        idx               = ~ismember(axesHandles, m2t.legendHandles);
        axesHandles       = axesHandles(idx);
    end

    % Remove all colorbar handles, as they are treated separately.
    if ~isempty(axesHandles)
        colorbarKeyword = switchMatOct('Colorbar', 'colorbar');
        % Find all colorbar handles. This is MATLAB-only.
        cbarHandles = findall(fh, tagKeyword, colorbarKeyword);
        % Octave also finds text handles here; no idea why. Filter.
        m2t.cbarHandles = [];
        for h = cbarHandles(:)'
            if any(strcmpi(get(h, 'Type'),{'axes','colorbar'}))
                m2t.cbarHandles = [m2t.cbarHandles, h];
            end
        end
        m2t.cbarHandles = m2t.cbarHandles(:)';
        idx             = ~ismember(axesHandles, m2t.cbarHandles);
        axesHandles     = axesHandles(idx);
    else
        m2t.cbarHandles = [];
    end

    % Remove scribe layer holding annotations (MATLAB < R2014b)
    m2t.scribeLayer = findall(axesHandles, 'Tag','scribeOverlay');
    idx             = ~ismember(axesHandles, m2t.scribeLayer);
    axesHandles     = axesHandles(idx);
end
% ==============================================================================
function str = texcomment(str)
    % format a (multiline) string as TeX comment
    if ~isempty(str)
        EOL = sprintf('\n');
        str = ['% ' strrep(str, EOL, [EOL '%']) EOL];
    end
end
% ==============================================================================
function addCustomCode(fid, before, code, after)
    if ~isempty(code)
        fprintf(fid, before);
        if ischar(code)
            code = {code};
        end
        if iscellstr(code)
            for str = code(:)'
                fprintf(fid, '%s\n', str{1});
            end
        else
            error('matlab2tikz:saveToFile', 'Need str or cellstr.');
        end
        fprintf(fid,after);
    end
end
% ==============================================================================
function [m2t, pgfEnvironments] = handleAllChildren(m2t, h)
    % Draw all children of a graphics object (if they need to be drawn).
    % #COMPLEX: mainly a switch-case
    str = '';
    children = allchild(h);

    % prepare cell array of pgfEnvironments
    pgfEnvironments = cell(1, numel(children));
    envCounter      = 1;

    % It's important that we go from back to front here, as this is
    % how MATLAB does it, too. Significant for patch (contour) plots,
    % and the order of plotting the colored patches.
    for child = children(end:-1:1)'

        % Check if object has legend. Some composite objects need to determine
        % their status at the root level. For detailed explanations check
        % getLegendEntries().
        % TODO: could move this check into drawHggroup. Need to verify how
        % hgtransform behaves though. (priority - LOW)
        m2t = hasLegendEntry(m2t,child);

        switch char(get(child, 'Type'))
            % 'axes' environments are treated separately.

            case 'line'
                [m2t, str] = handleObject(m2t, child, @drawLine);
                
            case 'constantline'
                [m2t, str] = handleObject(m2t, child, @drawConstantLine);

            case 'patch'
                [m2t, str] = handleObject(m2t, child, @drawPatch);

            case 'image'
                [m2t, str] = handleObject(m2t, child, @drawImage);

            case {'hggroup', 'matlab.graphics.primitive.Group', ...
                  'scatter', 'bar', 'stair', 'stem' ,'errorbar', 'area', ...
                  'quiver','contour'}
                [m2t, str] = handleObject(m2t, child, @drawHggroup);

            case 'hgtransform'
                % From http://www.mathworks.de/de/help/matlab/ref/hgtransformproperties.html:
                % Matrix: 4-by-4 matrix
                %   Transformation matrix applied to hgtransform object and its
                %   children. The hgtransform object applies the transformation
                %   matrix to all its children.
                % More information at http://www.mathworks.de/de/help/matlab/creating_plots/group-objects.html.
                m2t.transform = get(child, 'Matrix');
                [m2t, str] = handleAllChildren(m2t, child);
                m2t.transform = [];

            case 'surface'
                [m2t, str] = handleObject(m2t, child, @drawSurface);

            case 'text'
                [m2t, str] = handleObject(m2t, child, @drawVisibleText);

            case 'rectangle'
                [m2t, str] = handleObject(m2t, child, @drawRectangle);
		
	    case 'doubleendarrowshape'
                [m2t, str] = handleObject(m2t, child, @drawArrow);
	
	    case 'arrowshape'
                [m2t, str] = handleObject(m2t, child, @drawArrow);

            case 'histogram'
                [m2t, str] = handleObject(m2t, child, @drawHistogram);

            case guitypes()
                % don't do anything for GUI objects and their children
                [m2t, str] = handleObject(m2t, child, @drawNothing);

            case 'light'
                % These objects are not supported and should not/cannot be
                % supported by matlab2tikz or pgfplots.
                [m2t, str] = handleObject(m2t, child, @drawNothing);

            case ''
                % No children found for handle. (It has only a title and/or
                % labels). Carrying on as if nothing happened

            otherwise
                userWarning(m2t, 'matlab2tikz does not know how to handle object with type "%s"', get(child,'Type'));
                % just try with the drawNothing handler (which does nothing)
                % that at least allows a user to attach a custom handler from outside
                [m2t, str] = handleObject(m2t, child, @drawNothing);
        end

        % A composite object might nest handleAllChildren calls that can
        % modify the m2t.currentHandleHasLegend value. Re-instate the
        % legend status. For detailed explanations check getLegendEntries().
        m2t                          = hasLegendEntry(m2t,child);
        [m2t, legendLabel, labelRef] = addPlotyyReference(m2t, child);
        legendInfo                   = addLegendInformation(m2t, child);
        % Add labelRef BEFORE next plot to preserve color order
        str = join(m2t, {labelRef, str, legendLabel, legendInfo}, '');

        % append the environment
        pgfEnvironments{envCounter} = str;
        envCounter = envCounter +1;
    end
end
% ==============================================================================
function [m2t, label, labelRef] = addPlotyyReference(m2t, h)
    % Create labelled references to legend entries of the main plotyy axis

    % This ensures we are either on the main or secondary axis
    label    = '';
    labelRef = '';
    if ~isAxisPlotyy(m2t.current.gca)
        return
    end

    % Get current label counter

    if hasPlotyyReference(m2t,h)
        % Label the plot to later reference it. Only legend entries on the main
        % plotyy axis will have a label
        [m2t, labelNum] = incrementGlobalCounter(m2t, 'plotyylabel');
        label = sprintf('\\label{%s}\n\n', plotyyLabelName(labelNum));

    elseif m2t.currentHandleHasLegend && ~isempty(m2t.axes{end}.PlotyyReferences)
        % We are on the secondary axis.

        % We have produced a number of labels we can refer to so far.
        % Also, here we have a number of references that are to be recorded.
        % So, we make the last references (assuming the other ones have been
        % realized already)
        nReferences  = numel(m2t.axes{end}.PlotyyReferences);
        nLabels      = m2t.count.plotyylabel;

        % This is the range of labels, corresponding to the references
        labelRange   = (nLabels-nReferences+1):nLabels;

        labelRef = cell(1, numel(labelRange));
        % Create labelled references to legend entries of the main axis
        for iRef = 1:nReferences
            ref            = m2t.axes{end}.PlotyyReferences(iRef);
            lString        = getLegendString(m2t,ref);
            labelRef{iRef} = sprintf('\\addlegendimage{/pgfplots/refstyle=%s}\n\\addlegendentry{%s}\n',...
                                  plotyyLabelName(labelRange(iRef)), lString);
        end
        labelRef = join(m2t, labelRef, '');

        % Clear plotyy references. Ensures that references are created only once
        m2t.axes{end}.PlotyyReferences = [];
    else
        % Do nothing: it's gonna be a legend entry.
        % Not a label nor a referenced entry from the main axis.
    end
end
% ==============================================================================
function label = plotyyLabelName(num)
    % creates a LaTeX label for a plotyy trace
    label = sprintf('plotyyref:leg%d', num);
end
% ==============================================================================
function legendInfo = addLegendInformation(m2t, h)
    % Add the actual legend string

    legendInfo = '';
    if ~m2t.currentHandleHasLegend
        return
    end
    legendString = getLegendString(m2t,h);

    % We also need a legend alignment option to make multiline
    % legend entries work. This is added by default in getLegendOpts().
    legendInfo = sprintf('\\addlegendentry{%s}\n\n', legendString);
end
% ==============================================================================
function data = applyHgTransform(m2t, data)
    if ~isempty(m2t.transform)
        R = m2t.transform(1:3,1:3);
        t = m2t.transform(1:3,4);
        n = size(data, 1);
        data = data * R' + kron(ones(n,1), t');
    end
end
% ==============================================================================
function m2t = drawAxes(m2t, handle)
    % Input arguments:
    %    handle.................The axes environment handle.

    assertRegularAxes(handle);

    % Initialize empty environment.
    % Use a struct instead of a custom subclass of hgsetget (which would
    % facilitate writing clean code) as structs are more portable (old MATLAB(R)
    % versions, GNU Octave).
    m2t.axes{end+1} = struct('handle',   handle, ...
                           'name',     '', ...
                           'comment',  [], ...
                           'options',  {opts_new()}, ...
                           'content',  {cell(0)}, ...
                           'children', {cell(0)});

    % update gca
    m2t.current.gca = handle;

    % Check if axis is 3d
    % In MATLAB, all plots are treated as 3D plots; it's just the view that
    % makes 2D plots appear like 2D.
    m2t.axes{end}.is3D = isAxis3D(handle);

    % Flag if axis contains barplot
    m2t.axes{end}.barAddedAxisOption = false;

    % Get legend entries
    m2t.axes{end}.LegendHandle  = getAssociatedLegend(m2t, handle);
    m2t.axes{end}.LegendEntries = getLegendEntries(m2t);
    m2t = getPlotyyReferences(m2t, handle);

    m2t = retrievePositionOfAxes(m2t, handle);

    m2t = addAspectRatioOptionsOfAxes(m2t, handle);

    % Axis direction
    for axis = 'xyz'
        m2t.([axis 'AxisReversed']) = ...
            strcmpi(get(handle,[upper(axis),'Dir']), 'reverse');
    end
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % Add color scaling
    CLimMode = get(handle,'CLimMode');
    if strcmpi(CLimMode,'manual') || ~isempty(m2t.cbarHandles)
        clim = caxis(handle);
        m2t = m2t_addAxisOption(m2t, 'point meta min', sprintf(m2t.ff, clim(1)));
        m2t = m2t_addAxisOption(m2t, 'point meta max', sprintf(m2t.ff, clim(2)));
    end
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % Recurse into the children of this environment.
    [m2t, childrenEnvs] = handleAllChildren(m2t, handle);
    m2t.axes{end} = addChildren(m2t.axes{end}, childrenEnvs);
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % The rest of this is handling axes options.
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % Get other axis options (ticks, axis color, label,...).
    % This is set here such that the axis orientation indicator in m2t is set
    % before -- if ~isVisible(handle) -- the handle's children are called.
    [m2t, xopts] = getAxisOptions(m2t, handle, 'x');
    [m2t, yopts] = getAxisOptions(m2t, handle, 'y');

    m2t.axes{end}.options = opts_merge(m2t.axes{end}.options, xopts, yopts);

    m2t = add3DOptionsOfAxes(m2t, handle);

    if ~isVisible(handle)
        % Setting hide{x,y} axis also hides the axis labels in Pgfplots whereas
        % in MATLAB, they may still be visible. Instead use the following.
        m2t = m2t_addAxisOption(m2t, 'axis line style', '{draw=none}');
        m2t = m2t_addAxisOption(m2t, 'ticks', 'none');
        %    % An invisible axes container *can* have visible children, so don't
        %    % immediately bail out here.
        %    children = allchild(handle);
        %    for child = children(:)'
        %        if isVisible(child)
        %            % If the axes contain something that's visible, add an invisible
        %            % axes pair.
        %            m2t.axes{end}.name = 'axis';
        %            m2t.axes{end}.options = {m2t.axes{end}.options{:}, ...
        %                                               'hide x axis', 'hide y axis'};
        %            NOTE: getTag was removed in 76d260d12e615602653d6f7b357393242b2430b3
        %            m2t.axes{end}.comment = getTag(handle);
        %            break;
        %        end
        %    end
        %    % recurse into the children of this environment
        %    [m2t, childrenEnvs] = handleAllChildren(m2t, handle);
        %    m2t.axes{end} = addChildren(m2t.axes{end}, childrenEnvs);
        %    return
    end
    m2t.axes{end}.name = 'axis';

    m2t = drawBackgroundOfAxes(m2t, handle);
    m2t = drawTitleOfAxes(m2t, handle);
    m2t = drawBoxAndLineLocationsOfAxes(m2t, handle);
    m2t = drawGridOfAxes(m2t, handle);
    m2t = drawLegendOptionsOfAxes(m2t);

    m2t.axes{end}.options = opts_append_userdefined(m2t.axes{end}.options, ...
                                                  m2t.args.extraAxisOptions);
end
% ==============================================================================
function m2t = drawGridOfAxes(m2t, handle)
    % Draws the grids of an axis
    options = opts_new();

    % Check for major/minor grids
    hasGrid = [isOn(get(handle, 'XGrid'));
               isOn(get(handle, 'YGrid'));
               isOn(get(handle, 'ZGrid')) && isAxis3D(handle)];

    hasMinorGrid = [isOn(get(handle, 'XMinorGrid'));
                    isOn(get(handle, 'YMinorGrid'));
                    isOn(get(handle, 'ZMinorGrid')) && isAxis3D(handle)];

    xyz = {'x', 'y', 'z'};

    % Check for local grid options
    % NOTE: for individual axis color options see the pfgmanual under
    % major x grid style
    for i=1:3
        if hasGrid(i)
            grid    = [xyz{i}, 'majorgrids'];
            options = opts_add(options, grid);
        end
        if hasMinorGrid(i)
            grid    = [xyz{i}, 'minorgrids'];
            options = opts_add(options, grid);
        end
    end

    % Check for global grid options
    if any(hasGrid)
        gridOpts = opts_new();
        % Get the line style and translate it to pgfplots
        [gridLS, isDefault] = getAndCheckDefault(...
            'axes', handle, 'GridLineStyle', ':');
        if ~isDefault || m2t.args.strict
            gridOpts = opts_add(gridOpts, translateLineStyle(gridLS));
        end

        % Get the color of the grid and translate it to pgfplots usable
        % values
        [gridColor, defaultColor] = getAndCheckDefault(...
            'axes', handle, 'GridColor', [0.15, 0.15, 0.15]);
        if ~defaultColor
            [m2t, gridColor] = getColor(m2t, handle, gridColor, 'patch');
            gridOpts = opts_add(gridOpts, gridColor);
        end

        % Get the alpha of the grid and translate it to pgfplots
        [gridAlpha, defaultAlpha] = getAndCheckDefault(...
            'axes', handle, 'GridAlpha', 0.1);
        if ~defaultAlpha
            gridOpts = opts_add(gridOpts, 'opacity', num2str(gridAlpha));
        end

        if ~isempty(gridOpts)
            options = opts_addSubOpts(options, 'grid style', gridOpts);
        end
    end

    if any(hasMinorGrid)
        minorGridOpts = opts_new();
        % Get the line style and translate it to pgfplots
        [minorGridLS, isDefault] = getAndCheckDefault(...
            'axes', handle, 'MinorGridLineStyle', ':');
        if ~isDefault || m2t.args.strict
            minorGridOpts = opts_add(minorGridOpts, translateLineStyle(minorGridLS));
        end

        % Get the color of the grid and translate it to pgfplots usable
        % values
        [minorGridColor, defaultColor] = getAndCheckDefault(...
            'axes', handle, 'MinorGridColor', [0.1, 0.1, 0.1]);
        if ~defaultColor
            [m2t, minorGridColor] = getColor(m2t, handle, minorGridColor, 'patch');
            minorGridOpts = opts_add(minorGridOpts, minorGridColor);
        end

        % Get the alpha of the grid and translate it to pgfplots
        [minorGridAlpha, defaultAlpha] = getAndCheckDefault(...
            'axes', handle, 'MinorGridAlpha', 0.1);
        if ~defaultAlpha
            minorGridOpts = opts_add(minorGridOpts, 'opacity', num2str(minorGridAlpha));
        end

        if ~isempty(minorGridOpts)
            options = opts_addSubOpts(options, 'minor grid style', minorGridOpts);
        end
    end

    if ~any(hasGrid) && ~any(hasMinorGrid)
        % When specifying 'axis on top', the axes stay above all graphs (which is
        % default MATLAB behavior), but so do the grids (which is not default
        % behavior).
        %TODO: use proper grid ordering
        if m2t.args.strict
            options = opts_add(options, 'axis on top');
        end
        % FIXME: axis background, axis grid, main, axis ticks, axis lines, axis tick labels, axis descriptions, axis foreground
    end

    m2t.axes{end}.options = opts_merge(m2t.axes{end}.options, options);
end
% ==============================================================================
function m2t = add3DOptionsOfAxes(m2t, handle)
    % adds 3D specific options of an axes object
    if isAxis3D(handle)
        [m2t, zopts]        = getAxisOptions(m2t, handle, 'z');
        m2t.axes{end}.options = opts_merge(m2t.axes{end}.options, zopts);

        VIEWFORMAT = ['{' m2t.ff '}{' m2t.ff '}'];
        m2t = m2t_addAxisOption(m2t, 'view', sprintf(VIEWFORMAT, get(handle, 'View')));
    end
end
% ==============================================================================
function legendhandle = getAssociatedLegend(m2t, axisHandle)
    % Get legend handle associated with current axis

    legendhandle = [];
    [env, envVersion] = getEnvironment();
    switch env
        case 'Octave'
            % Make sure that m2t.legendHandles is a row vector.
            for lhandle = m2t.legendHandles(:)'
                if isVersionBelow(envVersion, [4,2,2]) % Octave commit 5865d2fef424
                  lhandleProp{1}='UserData';
                  lhandleProp{2}='handle';
                else
                  lhandleProp{1}='__appdata__';
                  lhandleProp{2}='__axes_handle__';
                end
                ud = get(lhandle, lhandleProp{1});
                % Empty if no legend and multiple handles if plotyy
                if ~isempty(ud) && any(axisHandle == ud.(lhandleProp{2}))
                    legendhandle = lhandle;
                    break
                end
            end
        case 'MATLAB'
	    if isprop(axisHandle, 'Legend') % R2017b behavior
                legendhandle = axisHandle.Legend;
	    else
		legendhandle = legend(axisHandle);
		% this is the proper way for old versions of MATLAB
		% in newer versions (see #1006), calling `legend` may
		% create a legend in the figure where none existed before
            end
    end

    % NOTE: there is a BUG in HG1 and Octave. Setting the box off sets the
    % legend visibility off too. We assume the legend is visible if it has
    % a visible child.
    isInvisibleHG2 = isHG2() && ~isVisible(legendhandle);
    isInvisibleHG1orOctave = (~isHG2() || strcmpi(env,'Octave')) &&...
        ~isVisibleContainer(legendhandle);

    % Do not return the handle if legend is invisible
    if isInvisibleHG1orOctave || isInvisibleHG2;
        legendhandle = [];
    end
end
% ==============================================================================
function entries = getLegendEntries(m2t)
    % Retrieve the handles of the objects that have a legend entry

    % Non-composite objects are straightforward, e.g. line, and have the
    % legend entry at their same level, hence we return their handle.
    %
    % Hggroups behave differently depending on the environment and we might
    % return the handle to the hgroot or to one of its children:
    %   1) Matlab places the legend entry at the hgroot.
    %
    %      Usually, the decision to place the legend is either unchanged from
    %      the first call to handleAllChildrena(axis) or delegated to a
    %      specialized drawing routine, e.g. drawContour(), if the group has to
    %      be drawn atomically. In this case, the legend entry stays with the
    %      hgroot.
    %
    %      If the hggroup is a pure container like in a bodeplot, i.e. the
    %      `type` is not listed in drawHggroup(), a nested call to
    %      handleAllChildren(hgroot) follows. But, this second call cannot detect
    %      legend entries on the children. Hence, we pass down the legend entry
    %      from the hgroot to its first child.
    %
    %   2) Octave places the entry with one of the children of the hgroot.
    %      Hence, most of the hggroups are correctly dealt by a nested
    %      handleAllChildren() call which detects the entry on the child.
    %      However, when we can guess the type of hggroup with
    %      guessOctavePlotType(), the legend entry should be placed at the root
    %      level, hence we bubble it up from the child to the hgroot.

    entries = [];
    legendHandle = m2t.axes{end}.LegendHandle;

    if isempty(legendHandle)
        return
    end

    switch getEnvironment()
        case 'Octave'
            if isappdata(legendHandle,'__peer_objects__')
                % Octave version 6.x or newer with refactored legend
                entries = getappdata(legendHandle,'__peer_objects__')
            else
                % See set(hlegend, "deletefcn", {@deletelegend2, ca, [], [], t1, hplots}); in legend.m
                delfun  = get(legendHandle,'deletefcn');
                entries = delfun{6};
            end

            % Bubble-up legend entry properties from child to hggroup root
            % for guessable objects
            for ii = 1:numel(entries)
                child = entries(ii);
                anc   = ancestor(child,'hggroup');
                if isempty(anc) % not an hggroup
                    continue
                end
                cl = guessOctavePlotType(anc);
                if ~strcmpi(cl, 'unknown') % guessable hggroup, then bubble-up
                    legendString = get(child,'displayname');
                    set(anc,'displayname',legendString);
                    entries(ii) = anc;
                end
            end

        case 'MATLAB'
            % Undocumented property (exists at least since 2008a)
            entries = get(legendHandle,'PlotChildren');

            % Take only the first child from a pure hggroup (e.g. bodeplots)
            for ii = 1:numel(entries)
                entry     = entries(ii);
                % Note that class() is not supported in Octave
                isHggroupClass = strcmpi(class(handle(entry)),'hggroup');
                if isHggroupClass
                    children    = get(entry, 'Children');
                    firstChild  = children(1);
                    if isnumeric(firstChild)
                        firstChild = handle(firstChild);
                    end
                    % Inherits DisplayName from hggroup root
                    set(firstChild, 'DisplayName', get(entry, 'DisplayName'));
                    entries(ii) = firstChild;
                end
            end
    end
end
% ==============================================================================
function m2t = getPlotyyReferences(m2t,axisHandle)
    % Retrieve references to legend entries of the main plotyy axis
    %
    % A plotyy plot has a main and a secondary axis. The legend is associated
    % with the main axis and hence m2t will only include the legend entries
    % that belong to the \axis[] that has a legend.
    %
    % One way to include the legend entries from the secondary axis (in the
    % same legend) is to first label the \addplot[] and then reference them.
    % See https://tex.stackexchange.com/questions/42697/42752#42752
    %
    % However, in .tex labels should come before they are referenced. Hence,
    % we actually label the legend entries from the main axis and swap the
    % legendhandle to the secondary axis.
    %
    % The legend will not be plotted with the main \axis[] and the labelled
    % legend entries will be skipped until the secondary axis. Then, they will
    % be listed before any legend entry from the secondary axis.

    % Retrieve legend handle
    if isAxisMain(axisHandle)
        legendHandle = m2t.axes{end}.LegendHandle;
    else
        legendHandle = getAssociatedLegend(m2t,getPlotyyPeer(axisHandle));
        m2t.axes{end}.LegendHandle = legendHandle;
    end

    % Not a plotyy axis or no legend
    if ~isAxisPlotyy(axisHandle) || isempty(legendHandle)
        m2t.axes{end}.PlotyyReferences = [];

    elseif isAxisMain(axisHandle)
        % Mark legend entries of the main axis for labelling
        legendEntries = m2t.axes{end}.LegendEntries;
        ancAxes       = ancestor(legendEntries,'axes');
        idx           = ismember([ancAxes{:}], axisHandle);
        m2t.axes{end}.PlotyyReferences = legendEntries(idx);

        % Ensure no legend is created on the main axis
        m2t.axes{end}.LegendHandle = [];
    else
        % Get legend entries associated to secondary plotyy axis. We can do
        % this because we took the legendhandle from the peer (main axis)
        legendEntries = getLegendEntries(m2t);
        ancAxes       = ancestor(legendEntries,'axes');
        if iscell(ancAxes)
            ancAxes = [ancAxes{:}];
        end
        idx = ismember(double(ancAxes), axisHandle);
        m2t.axes{end}.LegendEntries = legendEntries(idx);

        % Recover referenced legend entries of the main axis
        m2t.axes{end}.PlotyyReferences = legendEntries(~idx);
    end
end
% ==============================================================================
function bool = isAxisMain(h)
    % Check if it is the main axis e.g. in a plotyy plot

    if ~isAxisPlotyy(h)
        bool = true;
        return % an axis not constructed by plotyy is always(?) a main axis
    end

    % If it is a Plotyy axis
    switch getEnvironment()
        case 'Octave'
            plotyyAxes = get(h, '__plotyy_axes__');
            bool       = find(plotyyAxes == h) == 1;

        case 'MATLAB'
            % LegendPeerHandle was renamed to LayoutPeers in 9.1 (R2016b)
            if verLessThan('matlab', '9.1')
                bool = ~isempty(getappdata(h, 'LegendPeerHandle'));
            else
                bool = ~isempty(getappdata(h, 'LayoutPeers'));
            end
    end
end
% ==============================================================================
function bool = isAxisPlotyy(h)
    % Check if handle is a plotyy axis

    switch getEnvironment()
        case 'Octave'
            % Cannot test hidden property with isfield(), is always false
            try
                get(h, '__plotyy_axes__');
                bool = true;
            catch
                bool = false;
            end

        case 'MATLAB'
            bool = ~isempty(getappdata(h, 'graphicsPlotyyPeer'));
    end
end
% ==============================================================================
function peer = getPlotyyPeer(axisHandle)
    % Get the other axis coupled in plotyy plots

    switch getEnvironment()
        case 'Octave'
            plotyyAxes = get(axisHandle, '__plotyy_axes__');
            peer       = setdiff(plotyyAxes, axisHandle);

        case 'MATLAB'
            peer = getappdata(axisHandle, 'graphicsPlotyyPeer');
    end
end
% ==============================================================================
function legendString = getLegendString(m2t, h)
    % Retrieve the legend string for the given handle
    str         = getOrDefault(h, 'displayname', '');
    interpreter = get(m2t.axes{end}.LegendHandle,'interpreter');

    % HG1: autogenerated legend strings, i.e. data1,..., dataN, do not populate
    % the 'displayname' property. Go through 'userdata'
    if isempty(str)
        ud  = get(m2t.axes{end}.LegendHandle,'userdata');
        idx = ismember(ud.handles, h);
        str = ud.lstrings{idx};
    end

    % split string to cell, if newline character '\n' (ASCII 10) is present
    delimeter    = sprintf('\n');
    str          = regexp(str, delimeter, 'split');
    str          = prettyPrint(m2t, str, interpreter);
    legendString = join(m2t, str, '\\');
end
% ==============================================================================
function [m2t, bool] = hasLegendEntry(m2t, h)
    % Check if the handle has a legend entry and track its legend status in m2t
    legendEntries = m2t.axes{end}.LegendEntries;
    if isnumeric(h)
        legendEntries = double(legendEntries);
    end

    % Should not have a legend reference
    bool = any(ismember(h, legendEntries)) && ~hasPlotyyReference(m2t,h);
    m2t.currentHandleHasLegend = bool;
end
% ==============================================================================
function bool = hasPlotyyReference(m2t,h)
    % Check if the handle has a legend reference
    plotyyReferences = m2t.axes{end}.PlotyyReferences;
    if isnumeric(h)
        plotyyReferences = double(plotyyReferences);
    end

    bool = any(ismember(h, plotyyReferences));
end
% ==============================================================================
function m2t = retrievePositionOfAxes(m2t, handle)
    % This retrieves the position of an axes and stores it into the m2t data
    % structure

    pos = getAxesPosition(m2t, handle, m2t.args.width, ...
                          m2t.args.height, m2t.axesBoundingBox);
    % set the width
    if (~m2t.args.noSize)
        % optionally prevents setting the width and height of the axis
        m2t = setDimensionOfAxes(m2t, 'width',  pos.w);
        m2t = setDimensionOfAxes(m2t, 'height', pos.h);

        m2t = m2t_addAxisOption(m2t, 'at', ...
                ['{(' formatDim(pos.x.value, pos.x.unit) ','...
                      formatDim(pos.y.value, pos.y.unit) ')}']);
        % the following is general MATLAB behavior:
        m2t = m2t_addAxisOption(m2t, 'scale only axis');
    end
end
% ==============================================================================
function m2t = setDimensionOfAxes(m2t, widthOrHeight, dimension)
    % sets the dimension "name" of the current axes to the struct "dim"
    m2t = m2t_addAxisOption(m2t, widthOrHeight, ...
            formatDim(dimension.value, dimension.unit));
end
% ==============================================================================
function m2t = addAspectRatioOptionsOfAxes(m2t, handle)
    % Set manual aspect ratio for current axes
    % TODO: deal with 'axis image', 'axis square', etc. (#540)
    if strcmpi(get(handle, 'DataAspectRatioMode'), 'manual') ||...
       strcmpi(get(handle, 'PlotBoxAspectRatioMode'), 'manual')
        % we need to set the plot box aspect ratio
        if m2t.axes{end}.is3D
            % Note: set 'plot box ratio' for 3D axes to avoid bug with
            % 'scale mode = uniformly' (see #560)
            aspectRatio = getPlotBoxAspectRatio(handle);
            m2t = m2t_addAxisOption(m2t, 'plot box ratio', ...
                              formatAspectRatio(m2t, aspectRatio));
        end
    end
end
% ==============================================================================
function m2t = drawBackgroundOfAxes(m2t, handle)
    % draw the background color of the current axes
    backgroundColor = get(handle, 'Color');
    if ~isNone(backgroundColor) && isVisible(handle)
        [m2t, col] = getColor(m2t, handle, backgroundColor, 'patch');
        m2t = m2t_addAxisOption(m2t, 'axis background/.style', sprintf('{fill=%s}', col));
    end
end
% ==============================================================================
function m2t = drawTitleOfAxes(m2t, handle)
    % processes the title of an axes object
    [m2t, m2t.axes{end}.options] = getTitle(m2t, handle, m2t.axes{end}.options);
end
% ==============================================================================
function [m2t, opts] = getTitle(m2t, handle, opts)
    % gets the title and its markup from an axes/colorbar/...
    [m2t, opts] = getTitleOrLabel_(m2t, handle, opts, 'Title');
end
function [m2t, opts] = getLabel(m2t, handle, opts, tikzKeyword)
    % gets the label and its markup from an axes/colorbar/...
    [m2t, opts] = getTitleOrLabel_(m2t, handle, opts, 'Label', tikzKeyword);
end
function [m2t, opts] = getAxisLabel(m2t, handle, axis, opts)
    % convert an {x,y,z} axis label to TikZ
    labelName = [upper(axis) 'Label'];
    [m2t, opts] = getTitleOrLabel_(m2t, handle, opts, labelName);
end
function [m2t, opts] = getTitleOrLabel_(m2t, handle, opts, labelKind, tikzKeyword)
    % gets a string element from an object
    if ~exist('tikzKeyword', 'var') || isempty(tikzKeyword)
        tikzKeyword = lower(labelKind);
    end
    object = get(handle, labelKind);

    str = get(object, 'String');
    if ~isempty(str)
        interpreter = get(object, 'Interpreter');
        str = prettyPrint(m2t, str, interpreter);
        [m2t, style] = getFontStyle(m2t, object);
        if length(str) > 1 %multiline
            style = opts_add(style, 'align', 'center');
        end
        if ~isempty(style)
            opts = opts_addSubOpts(opts, [tikzKeyword ' style'], style);
        end
        str = join(m2t, str, '\\[1ex]');
        opts =  opts_add(opts, tikzKeyword, sprintf('{%s}', str));
    end
end
% ==============================================================================
function m2t = drawBoxAndLineLocationsOfAxes(m2t, h)
    % draw the box and axis line location of an axes object
    isBoxOn       = isOn(get(h, 'box'));
    xLoc          = get(h, 'XAxisLocation');
    yLoc          = get(h, 'YAxisLocation');
    isXaxisBottom = strcmpi(xLoc, 'bottom');
    isYaxisLeft   = strcmpi(yLoc, 'left');

    if strcmpi(xLoc, 'origin')
        xLoc = 'middle';
    end
    if strcmpi(yLoc, 'origin')
        yLoc = 'middle';
    end
    
    % Only flip the labels to the other side if not at the default
    % left/bottom positions
    if isBoxOn
        if ~isXaxisBottom
            m2t = m2t_addAxisOption(m2t, 'xticklabel pos','right');
        end
        if ~isYaxisLeft
            m2t = m2t_addAxisOption(m2t, 'yticklabel pos','right');
        end

        % Position axes lines (strips the box)
    else
        m2t = m2t_addAxisOption(m2t, 'axis x line*', xLoc);
        m2t = m2t_addAxisOption(m2t, 'axis y line*', yLoc);
        if m2t.axes{end}.is3D
            % There's no such attribute as 'ZAxisLocation'.
            % Instead, the default seems to be 'left'.
            m2t = m2t_addAxisOption(m2t, 'axis z line*', 'left');
        end
    end
end
% ==============================================================================
function m2t = drawLegendOptionsOfAxes(m2t)
    legendHandle = m2t.axes{end}.LegendHandle;
    if isempty(legendHandle)
        return
    end

    [m2t, key, legendOpts] = getLegendOpts(m2t, legendHandle);
    m2t = m2t_addAxisOption(m2t, key, legendOpts);
end
% ==============================================================================
function m2t = handleColorbar(m2t, handle)
    if isempty(handle)
        return;
    end

    % Find the axes environment that this colorbar belongs to.
    parentAxesHandle = double(get(handle,'axes'));
    parentFound = false;
    for k = 1:length(m2t.axes)
        if m2t.axes{k}.handle == parentAxesHandle
            k0 = k;
            parentFound = true;
            break;
        end
    end
    if parentFound
        m2t.axes{k0}.options = opts_append(m2t.axes{k0}.options, ...
            matlab2pgfplotsColormap(m2t, m2t.current.colormap), []);
        % Append cell string.
        m2t.axes{k0}.options = cat(1, m2t.axes{k0}.options, ...
                                    getColorbarOptions(m2t, handle));
    else
        warning('matlab2tikz:parentAxesOfColorBarNotFound',...
                'Could not find parent axes for color bar. Skipping.');
    end
end
% ==============================================================================
function [m2t, options] = getAxisOptions(m2t, handle, axis)
    assertValidAxisSpecifier(axis);

    options = opts_new();
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % axis colors
    [color, isDfltColor] = getAndCheckDefault('Axes', handle, ...
                                              [upper(axis),'Color'], [ 0 0 0 ]);
    if ~isDfltColor || m2t.args.strict
        [m2t, col] = getColor(m2t, handle, color, 'patch');
        if isOn(get(handle, 'box'))
            % If the axes are arranged as a box, make sure that the individual
            % axes are drawn as four separate paths. This makes the alignment
            % at the box corners somewhat less nice, but allows for different
            % axis styles (e.g., colors).
            options = opts_add(options, 'separate axis lines');
        end
        % set color of axis lines
        options = ...
            opts_add(options, ...
            ['every outer ', axis, ' axis line/.append style'], ...
            ['{', col, '}']);
        % set color of tick labels
        options = ...
            opts_add(options, ...
            ['every ',axis,' tick label/.append style'], ...
            ['{font=\color{',col,'}}']);
        % set color of ticks
        options = ...
            opts_add(options, ...
            ['every ',axis,' tick/.append style'], ...
            ['{',col,'}']);
    end
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % handle the orientation
    isAxisReversed = strcmpi(get(handle,[upper(axis),'Dir']), 'reverse');
    m2t.([axis 'AxisReversed']) = isAxisReversed;
    if isAxisReversed
        options = opts_add(options, [axis, ' dir'], 'reverse');
    end
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    axisScale = getOrDefault(handle, [upper(axis) 'Scale'], 'lin');
    if strcmpi(axisScale, 'log');
        options = opts_add(options, [axis,'mode'], 'log');
    end
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % get axis limits
    [m2t, options] = setAxisLimits(m2t, handle, axis, options);
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % get ticks along with the labels
    [options] = getAxisTicks(m2t, handle, axis, options);
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % get axis label
    [m2t, options] = getAxisLabel(m2t, handle, axis, options);
end
% ==============================================================================
function [options] = getAxisTicks(m2t, handle, axis, options)
    % Return axis tick marks Pgfplots style. Nice: Tick lengths and such
    % details are taken care of by Pgfplots.
    assertValidAxisSpecifier(axis);

    keywordTickMode = [upper(axis), 'TickMode'];
    tickMode = get(handle, keywordTickMode);
    keywordTick = [upper(axis), 'Tick'];
    ticks = get(handle, keywordTick);

    % hidden properties are not caught by hasProperties
    isDatetimeTicks = isAxisTicksDateTime(handle, axis);
    if isDatetimeTicks
        ticks = datenum(ticks);
    end

    if isempty(ticks)
        % If no ticks are present, we need to enforce this in any case.
        pgfTicks = '\empty';
    elseif strcmpi(tickMode, 'auto') && ~m2t.args.strict && ~isDatetimeTicks
        % Let pgfplots decide if the tickmode is auto or conversion is not
        % strict and we are not dealing with datetime ticks
        pgfTicks = [];
    else % strcmpi(tickMode,'manual') || m2t.args.strict
        pgfTicks = join(m2t, cellstr(num2str(ticks(:))), ', ');
    end

    keywordTickLabelMode = [upper(axis), 'TickLabelMode'];
    tickLabelMode = get(handle, keywordTickLabelMode);
    if strcmpi(tickLabelMode, 'auto') && ~m2t.args.strict && ~isDatetimeTicks
        pgfTickLabels = [];
    else % strcmpi(tickLabelMode,'manual') || m2t.args.strict
        % HG2 allows to set 'TickLabelInterpreter'.
        % HG1 tacitly uses the interpreter 'none'.
        % See http://www.mathworks.com/matlabcentral/answers/102053#comment_300079
        fallback    = defaultTickLabelInterpreter(m2t);
        interpreter = getOrDefault(handle, 'TickLabelInterpreter', fallback);
        keywordTickLabel = [upper(axis), 'TickLabel'];
        tickLabels = cellstr(get(handle, keywordTickLabel));
        tickLabels = prettyPrint(m2t, tickLabels, interpreter);

        keywordScale = [upper(axis), 'Scale'];
        isAxisLog = strcmpi(getOrDefault(handle,keywordScale, 'lin'), 'log');
        [pgfTicks, pgfTickLabels] = ...
            matlabTicks2pgfplotsTicks(m2t, ticks, tickLabels, isAxisLog, tickLabelMode);
    end

    keywordMinorTick = [upper(axis), 'MinorTick'];
    hasMinorTicks = isOn(getOrDefault(handle, keywordMinorTick, 'off'));
    tickDirection = getOrDefault(handle, 'TickDir', 'in');

    options = setAxisTicks(m2t, options, axis, pgfTicks, pgfTickLabels, ...
        hasMinorTicks, tickDirection, isDatetimeTicks);

    options = setAxisTickLabelStyle(options, axis, handle);
end
% ==============================================================================
function options = setAxisTickLabelStyle(options, axis, handle)
    % determine the style of tick labels
    %TODO: translate the style of tick labels fully (font?, weight, ...)
    kwRotation = [upper(axis), 'TickLabelRotation'];
    rotation = getOrDefault(handle, kwRotation, 0);
    if rotation ~= 0
        options = opts_add(options, [axis, 'ticklabel style'], ...
                                    sprintf('{rotate=%d}', rotation));
    end
end
% ==============================================================================
function interpreter = defaultTickLabelInterpreter(m2t)
    % determines the default tick label interpreter
    % This is only relevant in HG1/Octave. In HG2, we use the interpreter
    % set in the object (not the global default).
    if m2t.args.interpretTickLabelsAsTex
        interpreter = 'tex';
    else
        interpreter = 'none';
    end
end
% ==============================================================================
function isDatetimeTicks = isAxisTicksDateTime(handle, axis)
    % returns true when the axis has DateTime ticks

    % If R2016b or above
    if isa(get(handle, [upper(axis), 'Tick']), 'datetime')
        isDatetimeTicks = true;
    else
        try
            % Get hidden properties of the datetime axes manager
            dtsManager = get(handle, 'DatetimeDurationPlotAxesListenersManager');
            oldState   = warning('off','MATLAB:structOnObject');
            dtsManager = struct(dtsManager);
            warning(oldState);

            isDatetimeTicks = dtsManager.([upper(axis) 'DateTicks']) == 1;
        catch
            isDatetimeTicks = false;
        end
    end
end
% ==============================================================================
function options = setAxisTicks(m2t, options, axis, ticks, tickLabels,hasMinorTicks, tickDir,isDatetimeTicks)
    % set ticks options

    % According to http://www.mathworks.com/help/techdoc/ref/axes_props.html,
    % the number of minor ticks is automatically determined by MATLAB(R) to
    % fit the size of the axis. Until we know how to extract this number, use
    % a reasonable default.
    matlabDefaultNumMinorTicks = 3;
    if ~isempty(ticks)
        options = opts_add(options, [axis,'tick'], sprintf('{%s}', ticks));
    end
    if ~isempty(tickLabels)
        options = opts_add(options, ...
            [axis,'ticklabels'], sprintf('{%s}', tickLabels));
    end
    if hasMinorTicks
        options = opts_add(options, [axis,'minorticks'], 'true');
        if m2t.args.strict
            options = opts_add(options, ...
                sprintf('minor %s tick num', axis), ...
                sprintf('{%d}', matlabDefaultNumMinorTicks));
        end
    end

    if strcmpi(tickDir,'out')
        options = opts_add(options, 'tick align', 'outside');
    elseif strcmpi(tickDir,'both')
        options = opts_add(options, 'tick align', 'center');
    end

    if isDatetimeTicks
        options = opts_add(options, ['scaled ' axis ' ticks'], 'false');
    end
end
% ==============================================================================
function assertValidAxisSpecifier(axis)
    % assert that axis is a valid axis specifier
    if ~ismember(axis, {'x','y','z'})
        error('matlab2tikz:illegalAxisSpecifier', ...
              'Illegal axis specifier "%s".', axis);
    end
end
% ==============================================================================
function assertRegularAxes(handle)
    % assert that the (axes) object specified by handle is a regular axes and not a
    % colorbar or a legend
    tag = lower(get(handle,'Tag'));
    if ismember(tag,{'colorbar','legend'})
        error('matlab2tikz:notARegularAxes', ...
              ['The object "%s" is not a regular axes object. ' ...
               'It cannot be handled with drawAxes!'], handle);
    end
end
% ==============================================================================
function [m2t, options] = setAxisLimits(m2t, handle, axis, options)
    % set the upper/lower limit of an axis (but not for categorical values)
    limits = get(handle, [upper(axis),'Lim']);
    if isa(limits,'datetime')
        limits = datenum(limits);
    end
    if ~isCategoricalType(limits(1)) && isfinite(limits(1))
        options = opts_add(options, [axis,'min'], sprintf(m2t.ff, limits(1)));
    end
    if ~isCategoricalType(limits(2)) && isfinite(limits(2))
        options = opts_add(options, [axis,'max'], sprintf(m2t.ff, limits(2)));
    end

    % Take care of categorical variables
    if isCategoricalType(limits(1))

        % Categorical variables do not need to specify limits, but symbolic values
        categories = get(get(handle, [upper(axis),'Axis']), 'Categories');
        symb_coords = sprintf('{%s}', join(m2t, cellstr(string(categories)), ','));
        m2t = m2t_addAxisOption(m2t, ['symbolic ',axis,' coords'], symb_coords);

        % Some extra space is needed, otherwise the bars will touch the boundary
        options = opts_add(options, ['enlarge ',axis,' limits'], '0.2');

        % Ticks only for data values, prevents randomly repeated labels
        options = opts_add(options, [axis,'tick'], 'data');
    end
end
% ==============================================================================
function bool = isVisibleContainer(axisHandle)
    if ~isVisible(axisHandle)
        % An invisible axes container *can* have visible children, so don't
        % immediately bail out here. Also it *can* have a visible title,
        % labels or children

        bool = false;
        for prop = {'Children', 'Title', 'XLabel', 'YLabel', 'ZLabel'}
            property = prop{1};
            if strcmpi(property, 'Children')
                children = allchild(axisHandle);
            elseif isprop(axisHandle, property)
                children = get(axisHandle, property);
            else
                continue; % don't check non-existent properties
            end
            for child = children(:)'
                if isVisible(child)
                    bool = true;
                    return;
                end
            end
        end
    else
        bool = true;
    end
end
% ==============================================================================
function [m2t, str] = drawLine(m2t, h, custom)
    % Returns the code for drawing a regular line and error bars.
    % This is an extremely common operation and takes place in most of the
    % not too fancy plots.
    str = '';
    if ~isLineVisible(h)
        return; % there is nothing to plot
    end

    % Color
    color         = get(h, 'Color');
    [m2t, xcolor] = getColor(m2t, h, color, 'patch');
    % Line and marker options
    [m2t, lineOptions]   = getLineOptions(m2t, h);
    [m2t, markerOptions] = getMarkerOptions(m2t, h);

    % Only marks for legend consistency
    hasNoLine = opts_has(lineOptions, 'draw') && isNone(opts_get(lineOptions, 'draw'));
    if ~isempty(markerOptions) && hasNoLine
        lineOptions = opts_remove(lineOptions, 'draw');
        lineOptions = opts_add(lineOptions, 'only marks', []);
    end

    drawOptions = opts_new();
    drawOptions = opts_add(drawOptions, 'color', xcolor);
    drawOptions = opts_merge(drawOptions, lineOptions, markerOptions);
    drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);

    % Check for "special" lines, e.g.:
    if strcmpi(get(h, 'Tag'), 'zplane_unitcircle')
        [m2t, str] = specialDrawZplaneUnitCircle(m2t, drawOptions);
        return
    end

    % build the data matrix
    data       = getXYZDataFromLine(m2t, h);
    xDeviation = getXDeviations(h);
    yDeviation = getYDeviations(h);
    
    errDir = '';
    if ~isempty(xDeviation)
        data = [data, xDeviation];
        errDir = [errDir, 'x'];
    end
    if ~isempty(yDeviation)
        data = [data, yDeviation];
        errDir = [errDir, 'y'];
    end

    % Check if any value is infinite/NaN. In that case, add appropriate option.
    m2t = jumpAtUnboundCoords(m2t, data);

    [m2t, dataString]  = writePlotData(m2t, data, drawOptions, errDir);
    [m2t, labelString] = addLabel(m2t, h);

    str = [dataString, labelString];
end
% ==============================================================================
function [m2t, str] = specialDrawZplaneUnitCircle(m2t, drawOptions)
    % Draw unit circle and axes.

    % TODO Don't hardcode "10", but extract from parent axes of |h|
    opts = opts_print(drawOptions);
    str  = [sprintf('\\draw[%s] (axis cs:0,0) circle[radius=1];\n',  opts), ...
            sprintf('\\draw[%s] (axis cs:-10,0)--(axis cs:10,0);\n', opts), ...
            sprintf('\\draw[%s] (axis cs:0,-10)--(axis cs:0,10);\n', opts)];
end
% ==============================================================================
function bool = isLineVisible(h)
    % check if a line object is actually visible (has markers and so on)

    lineStyle     = get(h, 'LineStyle');
    lineWidth     = get(h, 'LineWidth');
    marker        = getOrDefault(h, 'Marker','none');
    hasLines      = ~isNone(lineStyle) && lineWidth > 0;
    hasMarkers    = ~isNone(marker);
    hasDeviations = ~isempty(getYDeviations(h));

    bool = isVisible(h) && (hasLines || hasMarkers || hasDeviations);
end
% ==============================================================================
function [m2t, str] = writePlotData(m2t, data, drawOptions, errDir)
    % actually writes the plot data to file
    str = '';

    is3D = m2t.axes{end}.is3D;
    if is3D
        % Don't try to be smart in parametric 3d plots: Just plot all the data.
        [m2t, table, tableOptions] = makeTable(m2t, {'','',''}, data);

        % Print out
        drawOpts = opts_print(drawOptions);
        tabOpts  = opts_print(tableOptions);
        str      = sprintf('\\addplot3 [%s]\n table[%s] {%s};\n ', ...
                           drawOpts, tabOpts, table);
    else
        % split the data into logical chunks
        dataCell = splitLine(m2t, data);

        % plot them
        strPart = cell(1, length(dataCell));
        for k = 1:length(dataCell)
            %If drawOptions has error bar style options, then print these
            %options on the variable errorBarOpts and remove them from
            %drawOptions
            if (opts_has(drawOptions,'error bar style'))
                tmpOptions = opts_new();
                tmpOptions = opts_add(tmpOptions,'error bars/.cd','');
                % add x direction error bar style if necessary
                if contains(errDir, 'x')
                    tmpOptions = opts_add(tmpOptions,'x dir','both');
                    tmpOptions = opts_add(tmpOptions,'x explicit','');
                end
                % add y direction error bar style if necessary
                if contains(errDir, 'y')
                    tmpOptions = opts_add(tmpOptions,'y dir','both');
                    tmpOptions = opts_add(tmpOptions,'y explicit','');
                end
                tmpOptions = opts_copy(drawOptions, 'error bar style', tmpOptions);
                tmpOptions = opts_copy(drawOptions, 'error mark options', tmpOptions);

                errorBarOpts = opts_print(tmpOptions);

                drawOptions = opts_remove(drawOptions, 'error bar style', ...
                    'error mark options');

            else
                errorBarOpts = '';
            end

            % If the line has a legend string, make sure to only include a legend
            % entry for the *last* occurrence of the plot series.
            % Hence the condition k<length(xDataCell).
            %if ~isempty(m2t.legendHandles) && (~m2t.currentHandleHasLegend || k < length(dataCell))
            if ~m2t.currentHandleHasLegend || k < length(dataCell)
                % No legend entry found. Don't include plot in legend.
                hiddenDrawOptions = maybeShowInLegend(false, drawOptions);
                opts = opts_print(hiddenDrawOptions);
            else
                opts = opts_print(drawOptions);
            end

            [m2t, Part] = plotLine2d(m2t, opts, errorBarOpts, dataCell{k}, errDir);
            strPart{k} = Part;
        end
        strPart = join(m2t, strPart, '');
        str = [str, strPart];
    end
end
% ==============================================================================
function [data] = getXYZDataFromLine(m2t, h)
    % Retrieves the X, Y and Z (if appropriate) data from a Line object
    %
    % First put them all together in one multiarray.
    % This also implicitly makes sure that the lengths match.
    try
        xData = get(h, 'XData');
        yData = get(h, 'YData');
    catch
        % Line annotation
        xData = get(h, 'X');
        yData = get(h, 'Y');
    end
    if isa(xData,'datetime')
        xData = datenum(xData);
    end
    if isa(yData,'datetime')
        yData = datenum(yData);
    end
    is3D  = m2t.axes{end}.is3D;
    if ~is3D
        data = [xData(:), yData(:)];
    else
        zData = get(h, 'ZData');
        data = applyHgTransform(m2t, [xData(:), yData(:), zData(:)]);
    end
end
% ==============================================================================
function [m2t, labelCode] = addLabel(m2t, h)
    % conditionally add a LaTeX label after the current plot
    labelCode = '';

    if m2t.args.automaticLabels||m2t.args.addLabels
        lineTag = get(h,'Tag');
        if ~isempty(lineTag)
            labelName = sprintf('%s', lineTag);
        else
            [pathstr, name] = fileparts(m2t.args.filename); %#ok
            labelName = sprintf('addplot:%s%d', name, m2t.count.autolabel);
            [m2t] = incrementGlobalCounter(m2t, 'autolabel');
            % TODO: First increment the counter, then use it such that the
            % pattern is the same everywhere
        end
        labelCode = sprintf('\\label{%s}\n', labelName);
        userWarning(m2t, 'Automatically added label ''%s'' for line plot.', labelName);
    end
end
% ==============================================================================
function [m2t,str] = plotLine2d(m2t, opts, errorBarOpts, data, errDir)

    errorBar = '';
    if ~isempty(errDir)
        m2t      = needsPgfplotsVersion(m2t, [1,9]);
        errorBar = sprintf('plot [%s]\n', errorBarOpts);
    end

    % Convert to string array then cell to call sprintf once (and no loops).
    [m2t, table, tableOptions] = makeTable(m2t, repmat({''}, size(data,2)), data);
    switch errDir
        case 'x'
            tableOptions = opts_add(tableOptions, 'x error plus index', '2');
            tableOptions = opts_add(tableOptions, 'x error minus index', '3');
        case 'y'
            tableOptions = opts_add(tableOptions, 'y error plus index', '2');
            tableOptions = opts_add(tableOptions, 'y error minus index', '3');
        case 'xy'
            tableOptions = opts_add(tableOptions, 'x error plus index', '2');
            tableOptions = opts_add(tableOptions, 'x error minus index', '3');
            tableOptions = opts_add(tableOptions, 'y error plus index', '4');
            tableOptions = opts_add(tableOptions, 'y error minus index', '5');
    end

    % Print out
    tabOpts = opts_print(tableOptions);
    str     = sprintf('\\addplot [%s]\n %s table[%s]{%s};\n',...
                      opts, errorBar, tabOpts, table);
end
% ==============================================================================
function dataCell = splitLine(m2t, data)
    % TeX parses files line by line with a buffer of size buf_size. If the
    % plot has too many data points, pdfTeX's buffer size may be exceeded.
    % As a work-around, split the xData, yData into several chunks of data
    % for each of which an \addplot will be generated.

    % Get the length of the data array and the corresponding chung size
    %TODO: scale `maxChunkLength` with the number of columns in the data array
    len         = size(data, 1);
    chunkLength = m2t.args.maxChunkLength;
    chunks      = chunkLength * ones(ceil(len/chunkLength), 1);
    if mod(len, chunkLength) ~=0
        chunks(end) = mod(len, chunkLength);
    end

    % Cut the data into chunks
    dataCell = mat2cell(data, chunks);

    % Add an extra (overlap) point to the data stream otherwise the line
    % between two data chunks would be broken. Technically, this is only
    % needed when the plot has a line connecting the points, but the
    % additional cost when there is no line doesn't justify the added
    % complexity.
    for i=1:length(dataCell)-1
        dataCell{i}(end+1,:) = dataCell{i+1}(1,:);
    end
end
% ==============================================================================
function [m2t, lineOpts] = getLineOptions(m2t, h)
    % Gathers the line options.
    lineOpts = opts_new();

    % Get the options from the handle
    lineWidth = get(h, 'LineWidth');

    % Get the line style and check whether it is the default one
    [lineStyle, isDefaultLS] = getAndCheckDefault('Line', h, 'LineStyle', '-');

    if ~isDefaultLS && ~isNone(lineStyle) && (lineWidth > m2t.tol)
        lineOpts = opts_add(lineOpts, translateLineStyle(lineStyle));
    end

    % Take over the line width in any case when in strict mode. If not, don't add
    % anything in case of default line width and effectively take Pgfplots'
    % default.
    % Also apply the line width if no actual line is there; the markers make use
    % of this, too.
    matlabDefaultLineWidth = 0.5;
    if ~isempty(m2t.semantic.LineWidth)
        if ismember(lineWidth, [m2t.semantic.LineWidth{:,2}])
            semStrID = lineWidth == [m2t.semantic.LineWidth{:,2}];
            lineOpts = opts_add(lineOpts, m2t.semantic.LineWidth{semStrID,1});
        else
            warning('matlab2tikz:semanticLineWidthNotFound',...
                ['No semantic correspondance for lineWidth of ''%f'' found.'...
                'Falling back to explicit export in points.'], lineWidth);
            lineOpts = opts_add(lineOpts, 'line width', sprintf('%.1fpt', lineWidth));
        end
    elseif m2t.args.strict || ~abs(lineWidth-matlabDefaultLineWidth) <= m2t.tol
        lineOpts = opts_add(lineOpts, 'line width', sprintf('%.1fpt', lineWidth));
    end

    % print no lines
    if isNone(lineStyle) || lineWidth==0
        lineOpts = opts_add(lineOpts, 'draw', 'none');
        lineOpts = opts_add(lineOpts, 'only marks');
    end
end
% ==============================================================================
function list = configureSemanticLineWidths(semanticLineWidths)
    % Defines the default semantic options of pgfplots and updates it when applicable

    if isnan(semanticLineWidths)
        % Remove the list
        list = {};
        return;
    end

    % Pgf/TikZ defaults (see pgfmanual 3.0.1a section 15.3.1 / page 166)
    list = {'ultra thin',  0.1;
            'very thin',   0.2;
            'thin',        0.4;
            'semithick',   0.6;
            'thick',       0.8;
            'very thick',  1.2;
            'ultra thick', 1.6 };

    % Update defaults or append the user provided setting
    for ii = 1:size(semanticLineWidths, 1)
        % Check for redefinitions of defaults
        [isOverride, idx] = ismember(semanticLineWidths{ii, 1}, list{:, 1})
        if isOverride
            list{idx, 2} = semanticLineWidths{ii, 2};
        else
            list{end+1} = semanticLineWidths{ii, :};
        end
    end
end
% ==============================================================================
function [m2t, drawOptions] = getMarkerOptions(m2t, h)
    % Handles the marker properties of a line (or any other) plot.
    drawOptions = opts_new();

    marker = getOrDefault(h, 'Marker', 'none');

    if ~isNone(marker)
        markerSize = get(h, 'MarkerSize');
        lineStyle  = get(h, 'LineStyle');
        lineWidth  = get(h, 'LineWidth');

        [tikzMarkerSize, isDefault] = ...
            translateMarkerSize(m2t, marker, markerSize);

        % take over the marker size in any case when in strict mode;
        % if not, don't add anything in case of default marker size
        % and effectively take Pgfplots' default.
        if m2t.args.strict || ~isDefault
            drawOptions = opts_add(drawOptions, 'mark size', ...
                                   sprintf('%.1fpt', tikzMarkerSize));
        end

        markOptions = opts_new();
        % make sure that the markers get painted in solid (and not dashed)
        % if the 'lineStyle' is not solid (otherwise there is no problem)
        if ~strcmpi(lineStyle, 'solid')
            markOptions = opts_add(markOptions, 'solid');
        end

        % get the marker color right
        markerInfo = getMarkerInfo(m2t, h, markOptions);

        [m2t, markerInfo.options] = setColor(m2t, h, markerInfo.options, 'fill', markerInfo.FaceColor);

        if ~strcmpi(markerInfo.EdgeColor,'auto')
            [m2t, markerInfo.options] = setColor(m2t, h, markerInfo.options, 'draw', markerInfo.EdgeColor);
        else
            if isprop(h,'EdgeColor')
                color = get(h, 'EdgeColor');
            else
                color = get(h, 'Color');
            end
            [m2t, markerInfo.options] = setColor(m2t, h, markerInfo.options, '', color);
        end

        % add it all to drawOptions
        drawOptions = opts_add(drawOptions, 'mark', markerInfo.tikz);

        if ~isempty(markOptions)
            drawOptions = opts_addSubOpts(drawOptions, 'mark options', ...
                                       markerInfo.options);
        end
    end

    type = getOrDefault(h, 'Type', 'none');
    if strcmp(type, 'errorbar')
        %'capSize' -> errorbar marker size
        capSize = getOrDefault(h, 'CapSize',get(h,'MarkerSize'));
        lineWidth = get(h, 'LineWidth');

        errStyleOptions = opts_new();
        errStyleOptions = opts_add(errStyleOptions, 'line width',...
            sprintf('%.1fpt', lineWidth));

        drawOptions = opts_addSubOpts(drawOptions, ...
            'error bar style', errStyleOptions);

        errMarkOptions = opts_new();
        errMarkOptions = opts_add(errMarkOptions, 'line width',...
             sprintf('%.1fpt', lineWidth));
        errMarkOptions = opts_add(errMarkOptions, 'mark size',...
             sprintf('%.1fpt', capSize));
        errMarkOptions = opts_add(errMarkOptions, 'rotate', '90');

        drawOptions = opts_addSubOpts(drawOptions, ...
            'error mark options', errMarkOptions);
    end
end
% ==============================================================================
function [tikzMarkerSize, isDefault] = ...
    translateMarkerSize(m2t, matlabMarker, matlabMarkerSize)
    % The markersizes of Matlab and TikZ are related, but not equal. This
    % is because
    %
    %  1.) MATLAB uses the MarkerSize property to describe something like
    %      the diameter of the mark, while TikZ refers to the 'radius',
    %  2.) MATLAB and TikZ take different measures (e.g. the
    %      edge of a square vs. its diagonal).
    if(~ischar(matlabMarker))
        error('matlab2tikz:translateMarkerSize',                      ...
            'Variable matlabMarker is not a string.');
    end

    if(~isnumeric(matlabMarkerSize))
        error('matlab2tikz:translateMarkerSize',                      ...
            'Variable matlabMarkerSize is not a numeral.');
    end

    % 6pt is the default MATLAB marker size for all markers
    defaultMatlabMarkerSize = 6;
    isDefault = abs(matlabMarkerSize(1)-defaultMatlabMarkerSize)<m2t.tol;
    % matlabMarkerSize can be vector data, use first index to check the default
    % marker size. When the script also handles different markers together with
    % changing size and color, the test should be extended to a vector norm, e.g.
    % sqrt(e^T*e) < tol, where e=matlabMarkerSize-defaultMatlabMarkerSize

    switch (matlabMarker)
        case 'none'
            tikzMarkerSize = [];
        case {'+','o','x','*','p','pentagram','h','hexagram'}
            % In MATLAB, the marker size refers to the edge length of a
            % square (for example) (~diameter), whereas in TikZ the
            % distance of an edge to the center is the measure (~radius).
            % Hence divide by 2.
            tikzMarkerSize = matlabMarkerSize(:) / 2;
        case '.'
            % as documented on the Matlab help pages:
            %
            % Note that MATLAB draws the point marker (specified by the '.'
            % symbol) at one-third the specified size.
            % The point (.) marker type does not change size when the
            % specified value is less than 5.
            %
            tikzMarkerSize = matlabMarkerSize(:) / 2 / 3;
        case {'s','square'}
            % Matlab measures the diameter, TikZ half the edge length
            tikzMarkerSize = matlabMarkerSize(:) / 2 / sqrt(2);
        case {'d','diamond'}
            % MATLAB measures the width, TikZ the height of the diamond;
            % the acute angle (at the top and the bottom of the diamond)
            % is a manually measured 75 degrees (in TikZ, and MATLAB
            % probably very similar); use this as a base for calculations
            tikzMarkerSize = matlabMarkerSize(:) / 2 / atan(75/2 *pi/180);
        case {'^','v','<','>'}
            % for triangles, matlab takes the height
            % and tikz the circumcircle radius;
            % the triangles are always equiangular
            tikzMarkerSize = matlabMarkerSize(:) / 2 * (2/3);
        otherwise
            error('matlab2tikz:translateMarkerSize',                   ...
                'Unknown matlabMarker ''%s''.', matlabMarker);
    end
end
% ==============================================================================
function [tikzMarker, markOptions] = ...
    translateMarker(m2t, matlabMarker, markOptions, faceColorToggle)
    % Translates MATLAB markers to their Tikz equivalents
    % #COMPLEX: inherently large switch-case
    if ~ischar(matlabMarker)
        error('matlab2tikz:translateMarker:MarkerNotAString',...
            'matlabMarker is not a string.');
    end

    switch (matlabMarker)
        case 'none'
            tikzMarker = '';
        case '+'
            tikzMarker = '+';
        case 'o'
            if faceColorToggle
                tikzMarker = '*';
            else
                tikzMarker = 'o';
            end
        case '.'
            tikzMarker = '*';
        case 'x'
            tikzMarker = 'x';
        otherwise  % the following markers are only available with PGF's
            % plotmarks library
            signalDependency(m2t, 'tikzlibrary', 'plotmarks');
            hasFilledVariant = true;
            switch (matlabMarker)

                case '*'
                    tikzMarker = 'asterisk';
                    hasFilledVariant = false;

                case {'s','square'}
                    tikzMarker = 'square';

                case {'d','diamond'}
                    tikzMarker = 'diamond';

                case '^'
                    tikzMarker = 'triangle';

                case 'v'
                    tikzMarker = 'triangle';
                    markOptions = opts_add(markOptions, 'rotate', '180');

                case '<'
                    tikzMarker = 'triangle';
                    markOptions = opts_add(markOptions, 'rotate', '90');

                case '>'
                    tikzMarker = 'triangle';
                    markOptions = opts_add(markOptions, 'rotate', '270');

                case {'p','pentagram'}
                    tikzMarker = 'star';

                case {'h','hexagram'}
                    userWarning(m2t, 'MATLAB''s marker ''hexagram'' not available in TikZ. Replacing by ''star''.');
                    tikzMarker = 'star';

                otherwise
                    error('matlab2tikz:translateMarker:unknownMatlabMarker',...
                        'Unknown matlabMarker ''%s''.',matlabMarker);
            end
            if faceColorToggle && hasFilledVariant
                tikzMarker = [tikzMarker '*'];
            end
    end
end
% ==============================================================================
function [m2t, str] = drawConstantLine(m2t, h, custom)
    % Draws a 'constantline' object such as those produced by 'yline()'
    
    interceptaxis = get(h, 'InterceptAxis');
    value = get(h, 'Value');
    xLim = get(h.Parent, 'XLim');
    yLim = get(h.Parent, 'Ylim');
    
    % create line from object properties
    switch interceptaxis
        case 'x'
            xdata = [value value];
            ydata = yLim;
        case 'y'
            xdata = xLim;
            ydata = [value value];
        otherwise
            warning('ConstantLine: invalid InterceptAxis. Ignoring...')
            return
    end
    l = line(xdata, ydata);
    
    % Pass on color and line options
    l.Color = h.Color;
    l.LineStyle = h.LineStyle;
    l.LineWidth = h.LineWidth;
    
    [m2t, str] = drawLine(m2t, l, custom);
end
% ==============================================================================
function [m2t, str] = drawPatch(m2t, handle, custom)
    % Draws a 'patch' graphics object (as found in contourf plots, for example).
    %
    str = '';
    if ~isVisible(handle)
        return
    end

    % This is for a quirky workaround for stacked bar plots.
    m2t.axes{end}.nonbarPlotsPresent = true;

    % Each row of the faces matrix represents a distinct patch
    % NOTE: pgfplot uses zero-based indexing into vertices and interpolates
    % counter-clockwise
    Faces    = get(handle,'Faces')-1;
    Vertices = get(handle,'Vertices');

    % 3D vs 2D
    is3D = m2t.axes{end}.is3D;
    if is3D
        columnNames = {'x', 'y', 'z'};
        plotCmd     = 'addplot3';
        Vertices    = applyHgTransform(m2t, Vertices);
    else
        columnNames = {'x', 'y'};
        plotCmd     = 'addplot';
        Vertices    = Vertices(:,1:2);
    end

    % Process fill, edge colors and shader
    [m2t,patchOptions, s] = shaderOpts(m2t,handle,'patch');

    % Return empty axes if no face or edge colors
    if isNone(s.plotType)
        return
    end

    % -----------------------------------------------------------------------
    % gather the draw options
    % Make sure that legends are shown in area mode.
    drawOptions = opts_add(opts_new,'area legend');
    verticesTableOptions = opts_new();

    % Marker options
    [m2t, markerOptions] = getMarkerOptions(m2t, handle);
    drawOptions          = opts_merge(drawOptions, markerOptions);

    % Line options
    [m2t, lineOptions] = getLineOptions(m2t, handle);
    drawOptions = opts_merge(drawOptions, lineOptions);

    % If the line is not visible, set edgeColor to none. Otherwise pgfplots
    % draws it by default
    if ~isLineVisible(handle)
        s.edgeColor = 'none';
    end

    % No patch: if one patch and single face/edge color
    isFaceColorFlat = isempty(strfind(opts_get(patchOptions, 'shader'),'interp'));
    if size(Faces,1) == 1 && s.hasOneEdgeColor && isFaceColorFlat
        ptType = '';
        cycle  = conditionallyCyclePath(Vertices);

        [m2t, drawOptions] = setColor(m2t, handle, drawOptions, 'draw', ...
                                         s.edgeColor, 'none');
        [m2t, drawOptions] = setColor(m2t, handle, drawOptions, 'fill', ...
                                         s.faceColor);

        [drawOptions] = opts_copy(patchOptions, 'draw opacity', drawOptions);
        [drawOptions] = opts_copy(patchOptions, 'fill opacity', drawOptions);

    else % Multiple patches

        % Patch table type
        ptType      = 'patch table';
        cycle       = '';
        drawOptions = opts_add(drawOptions,'table/row sep','crcr');
        % TODO: is the above "crcr" compatible with pgfplots 1.12 ?
        % TODO: is a "patch table" externalizable?

        % Enforce 'patch' or cannot use 'patch table='
        if strcmpi(s.plotType,'mesh')
            drawOptions = opts_add(drawOptions,'patch');
        end
        drawOptions = opts_add(drawOptions,s.plotType); % Eventually add mesh, but after patch!

        drawOptions = getPatchShape(m2t, handle, drawOptions, patchOptions);

        [m2t, drawOptions, Vertices, Faces, verticesTableOptions, ptType, ...
         columnNames] = setColorsOfPatches(m2t, handle, drawOptions, ...
           Vertices, Faces, verticesTableOptions, ptType, columnNames, ...
           isFaceColorFlat, s);
    end

    drawOptions = maybeShowInLegend(m2t.currentHandleHasLegend, drawOptions);
    m2t = jumpAtUnboundCoords(m2t, Faces(:));

    % Add Faces table
    if ~isempty(ptType)
        [m2t, facesTable] = makeTable(m2t, repmat({''},1,size(Faces,2)), Faces);
        drawOptions = opts_add(drawOptions, ptType, sprintf('{%s}', facesTable));
    end

    % Plot the actual data.
    [m2t, verticesTable, tableOptions] = makeTable(m2t, columnNames, Vertices);
    tableOptions = opts_merge(tableOptions, verticesTableOptions);
    
    % Add custom options
    drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);

    % Print out
    drawOpts = opts_print(drawOptions);
    tabOpts  = opts_print(tableOptions);
    str = sprintf('\n\\%s[%s]\ntable[%s] {%s}%s;\n',...
                  plotCmd, drawOpts, tabOpts, verticesTable, cycle);
end
% ==============================================================================
function [m2t, drawOptions, Vertices, Faces, verticesTableOptions, ptType, ...
         columnNames] = setColorsOfPatches(m2t, handle, drawOptions, ...
           Vertices, Faces, verticesTableOptions, ptType, columnNames, isFaceColorFlat, s)
    % this behemoth does the color setting for patches

    % TODO: this function can probably be split further, just look at all those
    % parameters being passed.

    fvCData   = get(handle,'FaceVertexCData');
    rowsCData = size(fvCData,1);

    % We have CData for either all faces or vertices
    if rowsCData > 1

        % Add the color map
        m2t = m2t_addAxisOption(m2t, matlab2pgfplotsColormap(m2t, m2t.current.colormap));

        % Determine if mapping is direct or scaled
        CDataMapping = get(handle,'CDataMapping');
        if strcmpi(CDataMapping, 'direct')
            drawOptions = opts_add(drawOptions, 'colormap access','direct');
        end

        % Switch to face CData if not using interpolated shader
        isVerticesCData = rowsCData == size(Vertices,1);
        if isFaceColorFlat && isVerticesCData
            % Take first vertex color (see FaceColor in Patch Properties)
            fvCData         = fvCData(Faces(:,1)+ 1,:);
            rowsCData       = size(fvCData,1);
            isVerticesCData = false;
        end

        % Point meta as true color CData, i.e. RGB in [0,1]
        if size(fvCData,2) == 3
            % Create additional custom colormap
            m2t.axes{end}.options(end+1,:) = ...
                {matlab2pgfplotsColormap(m2t, fvCData, 'patchmap'), []};
            drawOptions = opts_append(drawOptions, 'colormap name','patchmap');

            % Index into custom colormap
            fvCData = (0:rowsCData-1)';
        end

        % Add pointmeta data to vertices or faces
        if isVerticesCData
            columnNames{end+1}   = 'c';
            verticesTableOptions = opts_add(verticesTableOptions, 'point meta','\thisrow{c}');
            Vertices             = [Vertices, fvCData];
        else
            ptType = 'patch table with point meta';
            Faces  = [Faces fvCData];
        end

    else
        % Scalar FaceVertexCData, i.e. one color mapping for all patches,
        % used e.g. by Octave in drawing barseries

        [m2t,xFaceColor] = getColor(m2t, handle, s.faceColor, 'patch');
        drawOptions      = opts_add(drawOptions, 'fill', xFaceColor);
    end
end
% ==============================================================================
function [drawOptions] = maybeShowInLegend(showInLegend, drawOptions)
    % sets the appropriate options to show/hide the plot in the legend
    if ~showInLegend
        % No legend entry found. Don't include plot in legend.
        drawOptions = opts_add(drawOptions, 'forget plot');
    end
end
% ==============================================================================
function [m2t, options] = setColor(m2t, handle, options, property, color, noneValue)
    % assigns the MATLAB color of the object identified by "handle" to the LaTeX
    % property stored in the options array. An optional "noneValue" can be provided
    % that is set when the color == 'none' (if it is omitted, the property will not
    % be set).
    % TODO: probably this should be integrated with getAndCheckDefault etc.
    if opts_has(options,property) && isNone(opts_get(options,property))
        return
    end
    if ~isNone(color)
        [m2t, xcolor] = getColor(m2t, handle, color, 'patch');
        if ~isempty(xcolor)
            % this may happen when color == 'flat' and CData is Nx3, e.g. in
            % scatter plot or in patches
            if isempty(property)
                options = opts_add(options, xcolor);
            else
                options = opts_add(options, property, xcolor);
            end
        end
    else
        if exist('noneValue','var')
            options = opts_add(options, property, noneValue);
        end
    end
end
% ==============================================================================
function drawOptions = getPatchShape(m2t, h, drawOptions, patchOptions)
    % Retrieves the shape options (i.e. number of vertices) of patch objects
    % Depending on the number of vertices, patches can be triangular, rectangular
    % or polygonal
    % See pgfplots 1.12 manual section 5.8.1 "Additional Patch Types" and the
    % patchplots library
    vertexCount = size(get(h, 'Faces'), 2);

    switch vertexCount
        case 3 % triangle (default)
            % do nothing special

        case 4 % rectangle
            drawOptions = opts_add(drawOptions,'patch type', 'rectangle');

        otherwise % generic polygon
            userInfo(m2t, '\nMake sure to load \\usepgfplotslibrary{patchplots} in the preamble.\n');

            % Default interpolated shader,not supported by polygon, to faceted
            isFaceColorFlat = isempty(strfind(opts_get(patchOptions, 'shader'),'interp'));
            if ~isFaceColorFlat
                % NOTE: check if pgfplots supports this (or specify version)
                userInfo(m2t, '\nPgfplots does not support interpolation for polygons.\n Use patches with at most 4 vertices.\n');
                patchOptions = opts_remove(patchOptions, 'shader');
                patchOptions = opts_add(patchOptions, 'shader', 'faceted');
            end

            % Add draw options
            drawOptions = opts_add(drawOptions, 'patch type', 'polygon');
            drawOptions = opts_add(drawOptions, 'vertex count', ...
                                                sprintf('%d', vertexCount));
    end

    drawOptions = opts_merge(drawOptions, patchOptions);
end
% ==============================================================================
function [cycle] = conditionallyCyclePath(data)
    % returns "--cycle" when the path should be cyclic in pgfplots
    % Mostly, this is the case UNLESS the data record starts or ends with a NaN
    % record (i.e. a break in the path)
    if any(~isfinite(data([1 end],:)))
        cycle = '';
    else
        cycle = '--cycle';
    end
end
% ==============================================================================
function m2t = jumpAtUnboundCoords(m2t, data)
    % signals the axis to allow discontinuities in the plot at unbounded
    % coordinates (i.e. Inf and NaN).
    % See also pgfplots 1.12 manual section 4.5.13 "Interrupted Plots".
    if any(~isfinite(data(:)))
        m2t = needsPgfplotsVersion(m2t, [1 4]);
        m2t = m2t_addAxisOption(m2t, 'unbounded coords', 'jump');
    end
end
% ==============================================================================
function [m2t, str] = drawImage(m2t, handle, custom)
    str = '';
    if ~isVisible(handle)
        return
    end

    % read x-, y-, and color-data
    xData = get(handle, 'XData');
    yData = get(handle, 'YData');
    cData = get(handle, 'CData');

    if isempty(cData)
      return
    end

    if (m2t.args.imagesAsPng)
        [m2t, str] = imageAsPNG(m2t, handle, xData, yData, cData, custom);
    else
        [m2t, str] = imageAsTikZ(m2t, handle, xData, yData, cData, custom);
    end

    % Make sure that the axes are still visible above the image.
    m2t = m2t_addAxisOption(m2t, 'axis on top');
end
% ==============================================================================
function [m2t, str] = imageAsPNG(m2t, handle, xData, yData, cData, custom)
    [m2t, fileNum] = incrementGlobalCounter(m2t, 'pngFile');
    % ------------------------------------------------------------------------
    % draw a png image
    [pngFileName, pngReferencePath] = externalFilename(m2t, fileNum, '.png');

    % Get color indices for indexed images and truecolor values otherwise
    if ndims(cData) == 2 %#ok don't use ismatrix (cfr. #143)
        [m2t, colorData] = cdata2colorindex(m2t, cData, handle);
    else
        colorData = cData;
    end

    m = size(cData, 1);
    n = size(cData, 2);

    alphaData = normalizedAlphaValues(m2t, get(handle,'AlphaData'), handle);
    if numel(alphaData) == 1
        alphaData = alphaData(ones(size(colorData(:,:,1))));
    end
    [colorData, alphaData] = flipImageIfAxesReversed(m2t, colorData, alphaData);

    % Write an indexed or a truecolor image
    hasAlpha = true;
    if isfloat(alphaData) && all(alphaData(:) == 1)
        alphaOpts = {};
        hasAlpha = false;
    else
        alphaOpts = {'Alpha', double(alphaData)};
    end
    if (ndims(colorData) == 2) %#ok don't use ismatrix (cfr. #143)
        if size(m2t.current.colormap, 1) <= 256 && ~hasAlpha
            % imwrite supports maximum 256 values in a colormap (i.e. 8 bit)
            % and no alpha channel for indexed PNG images.
            imwrite(colorData, m2t.current.colormap, ...
                pngFileName, 'png');
        else % use true-color instead
            imwrite(ind2rgb(colorData, m2t.current.colormap), ...
                pngFileName, 'png', alphaOpts{:});
        end
    else
        imwrite(colorData, pngFileName, 'png', alphaOpts{:});
    end
    % -----------------------------------------------------------------------
    % dimensions of a pixel in axes units
    if n == 1
        xLim = get(m2t.current.gca, 'XLim');
        xw = xLim(2) - xLim(1);
    else
        xw = (xData(end)-xData(1)) / (n-1);
    end
    if m == 1
        yLim = get(m2t.current.gca, 'YLim');
        yw = yLim(2) - yLim(1);
    else
        yw = (yData(end)-yData(1)) / (m-1);
    end

    opts = opts_new();
    opts = opts_add(opts, 'xmin', sprintf(m2t.ff, xData(1  ) - xw/2));
    opts = opts_add(opts, 'xmax', sprintf(m2t.ff, xData(end) + xw/2));
    opts = opts_add(opts, 'ymin', sprintf(m2t.ff, yData(1  ) - yw/2));
    opts = opts_add(opts, 'ymax', sprintf(m2t.ff, yData(end) + yw/2));

    opts = opts_append_userdefined(opts, custom.extraOptions);

    % Print out
    drawOpts = opts_print(opts);
    str      = sprintf('\\addplot [forget plot] graphics [%s] {%s};\n', ...
                       drawOpts, pngReferencePath);

    userInfo(m2t, ...
        ['\nA PNG file is stored at ''%s'' for which\n', ...
        'the TikZ file contains a reference to ''%s''.\n', ...
        'You may need to adapt this, depending on the relative\n', ...
        'locations of the master TeX file and the included TikZ file.\n'], ...
        pngFileName, pngReferencePath);
end
% ==============================================================================
function [m2t, str] = imageAsTikZ(m2t, handle, xData, yData, cData, custom)
    % writes an image as raw TikZ commands (STRONGLY DISCOURAGED)

    % set up cData
    if ndims(cData) == 3
        cData = cData(end:-1:1,:,:);
    else
        cData = cData(end:-1:1,:);
    end

    % Generate uniformly distributed X, Y, although xData and yData may be
    % non-uniform.
    % This is MATLAB(R) behavior.
    [X, hX] = constructUniformXYDataForImage(xData, size(cData, 2));
    [Y, hY] = constructUniformXYDataForImage(yData, size(cData, 1));
    [m2t, xcolor] = getColor(m2t, handle, cData, 'image');

    % The following section takes pretty long to execute, although in
    % principle it is discouraged to use TikZ for those; LaTeX will take
    % forever to compile.
    % Still, a bug has been filed on MathWorks to allow for one-line
    % sprintf'ing with (string+num) cells (Request ID: 1-9WHK4W);
    % <http://www.mathworks.de/support/service_requests/Service_Request_Detail.do?ID=183481&filter=&sort=&statusorder=0&dateorder=0>.
    % An alternative approach could be to use 'surf' or 'patch' of pgfplots
    % with inline tables.
    str = '';
    m = length(X);
    n = length(Y);
    imageString = cell(1, m);
    for i = 1:m
        subString = cell(1, n);
        for j = 1:n
            opts = opts_new();
            opts = opts_add(opts, xcolor{n-j+1, i});
            opts = opts_append_userdefined(opts, custom.extraOptions);
            subString{j} = sprintf(['\t\\fill [%s] ', ...
                                    '(axis cs:', m2t.ff, ',', m2t.ff, ') rectangle ', ...
                                    '(axis cs:', m2t.ff, ',', m2t.ff, ');\n'], ...
                                   opts_print(opts), ...
                                   X(i)-hX/2, Y(j)-hY/2, ...
                                   X(i)+hX/2, Y(j)+hY/2);
        end
        imageString{i} = join(m2t, subString, '');
    end
    str = join(m2t, [str, imageString], '');
end
function [XY, delta] = constructUniformXYDataForImage(XYData, expectedLength)
    % Generate uniformly distributed X, Y, although xData/yData may be
    % non-uniform. Dimension indicates the corresponding dimension in the cData matrix.
    switch length(XYData)
        case 2 % only the limits given; common for generic image plots
            delta = 1;
        case expectedLength % specific x/y-data is given
            delta = (XYData(end)-XYData(1)) / (length(XYData)-1);
        otherwise
            error('drawImage:arrayLengthMismatch', ...
                  'CData length (%d) does not match X/YData length (%d).', ...
                  expectedLength, length(XYData));
    end
    XY = XYData(1):delta:XYData(end);
end
% ==============================================================================
function [colorData, alphaData] = flipImageIfAxesReversed(m2t, colorData, alphaData)
    % flip the image if reversed
    if m2t.xAxisReversed
        colorData = colorData(:, end:-1:1, :);
        alphaData = alphaData(:, end:-1:1);
    end
    if ~m2t.yAxisReversed % y-axis direction is reversed normally for images, flip otherwise
        colorData = colorData(end:-1:1, :, :);
        alphaData = alphaData(end:-1:1, :);
    end
end
% ==============================================================================
function alpha = normalizedAlphaValues(m2t, alpha, handle)
    alphaDataMapping = getOrDefault(handle, 'AlphaDataMapping', 'none');
    switch lower(alphaDataMapping)
        case 'none'  % no rescaling needed
        case 'scaled'
            ALim = get(m2t.current.gca, 'ALim');
            AMax = ALim(2);
            AMin = ALim(1);
            if ~isfinite(AMax)
                AMax = max(alpha(:)); %NOTE: is this right?
            end
            alpha = (alpha - AMin)./(AMax - AMin);
        case 'direct'
            alpha = ind2rgb(alpha, get(m2t.current.gcf, 'Alphamap'));
        otherwise
            error('matlab2tikz:UnknownAlphaMapping', ...
                  'Unknown alpha mapping "%s"', alphaMapping);
    end

    if isfloat(alpha) %important, alpha data can have integer type which should not be scaled
        alpha = min(1,max(alpha,0)); % clip at range [0, 1]
    end
end
% ==============================================================================
function [m2t, str] = drawContour(m2t, h, custom)
    if isHG2()
        [m2t, str] = drawContourHG2(m2t, h, custom);
    else
        % Save legend state for the contour group
        hasLegend = m2t.currentHandleHasLegend;

        % Plot children patches
        children  = allchild(h);
        N         = numel(children);
        str       = cell(N,1);
        for ii = 1:N
            % Plot in reverse order
            child          = children(N-ii+1);
            isContourLabel = strcmpi(get(child,'type'),'text');
            if isContourLabel
                [m2t, str{ii}] = handleObject(m2t, child, @drawText);
            else
                [m2t, str{ii}] = handleObject(m2t, child, @drawPatch);
            end

            % Only first child can be in the legend
            m2t.currentHandleHasLegend = false;
        end
        str = strcat(str,sprintf('\n'));
        str = [str{:}];

        % Restore group's legend state
        m2t.currentHandleHasLegend = hasLegend;
    end
end
% ==============================================================================
function [m2t, str] = drawContourHG2(m2t, h, custom)
    str = '';
    if ~isVisible(h)
        return
    end

    % Retrieve ContourMatrix
    contours = get(h,'ContourMatrix')';
    [istart, nrows] = findStartOfContourData(contours);

    % Scale negative contours one level down (for proper coloring)
    Levels    = contours(istart,1);
    LevelList = get(h,'LevelList');
    ineg      = Levels < 0;
    if any(ineg) && min(LevelList) < min(Levels)
        [idx,pos] = ismember(Levels, LevelList);
        idx       = idx & ineg;
        contours(istart(idx)) = LevelList(pos(idx)-1);
    end

    % Draw a contour group (MATLAB R2014b and newer only)
    isFilled = isOn(get(h,'Fill'));
    if isFilled
        [m2t, str] = drawFilledContours(m2t, h, contours, istart, nrows, custom);
    else
        % Add colormap
        cmap = m2t.current.colormap;
        m2t = m2t_addAxisOption(m2t, matlab2pgfplotsColormap(m2t, cmap));

        % Contour table in Matlab format
        plotOptions = opts_new();
        plotOptions = opts_add(plotOptions,'contour prepared');
        plotOptions = opts_add(plotOptions,'contour prepared format','matlab');

        % Labels
        if isOff(get(h,'ShowText'))
            plotOptions = opts_add(plotOptions,'contour/labels','false');
        end

        % Get line properties
        [m2t, lineOptions] = getLineOptions(m2t, h);

        % Check for special color settings
        [lineColor, isDefaultColor] = getAndCheckDefault('contour', h, 'LineColor', 'flat');
        if ~isDefaultColor
            [m2t, lineOptions] = setColor(m2t, h, lineOptions, 'contour/draw color', lineColor, 'none');
        end

        % Merge the line options with the contour plot options
        plotOptions = opts_merge(plotOptions, lineOptions);
        plotOptions = opts_append_userdefined(plotOptions, custom.extraOptions);

        % Make contour table
        [m2t, table, tableOptions] = makeTable(m2t, {'',''}, contours);

        % Print out
        plotOpts = opts_print(plotOptions);
        tabOpts  = opts_print(tableOptions);
        str      = sprintf('\\addplot[%s] table[%s] {%%\n%s};\n', ...
                           plotOpts, tabOpts, table);
    end
end
% ==============================================================================
function [istart, nrows] = findStartOfContourData(contours)
    % Index beginning of contour data (see contourc.m for details)
    nrows  = size(contours,1);
    istart = false(nrows,1);
    pos    = 1;
    while pos < nrows
        istart(pos) = true;
        pos         = pos + contours(pos, 2) + 1;
    end
    istart = find(istart);
end
% ==============================================================================
function [m2t, str] = drawFilledContours(m2t, h, contours, istart, nrows, custom)
    % Loop each contour and plot a filled region
    %
    % NOTE:
    % - we cannot plot from inner to outer contour since the last
    % filled area will cover the inner regions. Therefore, we need to
    % invert the plotting order in those cases.
    % - we need to distinguish between contour groups. A group is
    % defined by inclusion, i.e. its members are contained within one
    % outer contour. The outer contours of two groups cannot include
    % each other.
    % FIXME: extract this logic to a function
    str = '';
    if ~isVisible(h)
        return
    end

    % Split contours in cell array
    cellcont = mat2cell(contours, diff([istart; nrows+1]));
    ncont    = numel(cellcont);

    % Determine contour groups and the plotting order.
    % The ContourMatrix lists the contours in ascending order by level.
    % Hence, if the lowest (first) contour contains any others, then the
    % group will be a peak. Otherwise, the group will be a valley, and
    % the contours will have to be plotted in reverse order, i.e. from
    % highest (largest) to lowest (narrowest).

    %FIXME: close the contours over the border of the domain, see #723.
    order = NaN(ncont,1);
    ifree = true(ncont,1);
    from  = 1;
    while any(ifree)
        % Select peer with lowest level among the free contours, i.e.
        % those which do not belong to any group yet
        pospeer = find(ifree,1,'first');
        peer    = cellcont{pospeer};
        igroup  = false(ncont,1);

        % Loop through all contours
        for ii = 1:numel(cellcont)
            if ~ifree(ii), continue, end

            curr = cellcont{ii};
            % Current contour contained in the peer
            if inpolygon(curr(2,1),curr(2,2), peer(2:end,1),peer(2:end,2))
                igroup(ii) = true;
                isinverse  = false;
                % Peer contained in the current
            elseif inpolygon(peer(2,1),peer(2,2),curr(2:end,1),curr(2:end,2))
                igroup(ii) = true;
                isinverse  = true;
            end
        end
        % Order members of group according to the inclusion principle
        nmembers = nnz(igroup ~= 0);
        if isinverse
            order(igroup) = nmembers+from-1:-1:from;
        else
            order(igroup) = from:nmembers+from-1;
        end

        % Continue numbering
        from  = from + nmembers;
        ifree = ifree & ~igroup;
    end

    % Reorder the contours
    cellcont(order,1) = cellcont;

    % Replace same level contours with hole, i.e. one level down
    Levels     = contours(istart,1);
    LevelList  = get(h,'LevelList');
    ireplace   = find([false; diff(Levels) == 0]);
    [idx, pos] = ismember(Levels(ireplace), LevelList);
    for ii = 1:numel(pos)
        cellcont{ireplace(ii)}(1) = LevelList(pos(ii)-1);
    end

    % Add "contourless" backdrop
    xdata     = get(h,'XData');
    ydata     = get(h,'YData');
    backdrop = [setdiff(LevelList, Levels), 4;
        min(xdata(:)), min(ydata(:));
        min(xdata(:)), max(ydata(:));
        max(xdata(:)), max(ydata(:));
        max(xdata(:)), min(ydata(:))];
    cellcont = [backdrop; cellcont];

    % Plot
    columnNames = {'x','y'};
    for ii = 1:ncont + 1
        drawOptions = opts_new();

        % Get fill color
        zval          = cellcont{ii}(1,1);
        [m2t, xcolor] = getColor(m2t,h,zval,'image');
        drawOptions   = opts_add(drawOptions,'fill',xcolor);

        % Get line properties
        lineColor = get(h, 'LineColor');

        [m2t, drawOptions] = setColor(m2t, h, drawOptions, 'draw', lineColor, 'none');

        [m2t, lineOptions] = getLineOptions(m2t, h);
        drawOptions = opts_merge(drawOptions, lineOptions);

        % Toggle legend entry
        hasLegend   = ii == 1 && m2t.currentHandleHasLegend;
        drawOptions = maybeShowInLegend(hasLegend, drawOptions);
        drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);

        % Print table
        [m2t, table, tableOptions] = makeTable(m2t, columnNames, cellcont{ii}(2:end,:));

        % Print out
        drawOpts = opts_print(drawOptions);
        tabOpts  = opts_print(tableOptions);
        str      = sprintf('%s\\addplot[%s] table[%s] {%%\n%s};\n', ...
                           str, drawOpts, tabOpts, table);
    end
end
% ==============================================================================
function [m2t, str] = drawHggroup(m2t, h, custom)
    % Continue according to the plot type. Since the function `handle` is
    % not available in Octave, the plot type will be guessed or the fallback type
    % 'unknown' used.
    % #COMPLEX: big switch-case
    warnDoesNotHandleCustomProperties(h, custom);
    switch getEnvironment()
        case 'MATLAB'
            cl = class(handle(h));

        case 'Octave'
            % Function `handle` is not yet implemented in Octave
            % Consequently the plot type needs to be guessed. See #645.
            cl = guessOctavePlotType(h);

        otherwise
            errorUnknownEnvironment();
    end

    switch(cl)
        case {'specgraph.barseries', 'matlab.graphics.chart.primitive.Bar'}
            % hist plots and friends
            [m2t, str] = handleObject(m2t, h, @drawBarseries);

        case {'specgraph.stemseries', 'matlab.graphics.chart.primitive.Stem'}
            % stem plots
            [m2t, str] = handleObject(m2t, h, @drawStemSeries);

        case {'specgraph.stairseries', 'matlab.graphics.chart.primitive.Stair'}
            % stair plots
            [m2t, str] = handleObject(m2t, h, @drawStairSeries);

        case {'specgraph.areaseries', 'matlab.graphics.chart.primitive.Area'}
            % scatter plots
            [m2t, str] = handleObject(m2t, h, @drawAreaSeries);

        case {'specgraph.quivergroup', 'matlab.graphics.chart.primitive.Quiver'}
            % quiver arrows
            [m2t, str] = handleObject(m2t, h, @drawQuiverGroup);

        case {'specgraph.errorbarseries', 'matlab.graphics.chart.primitive.ErrorBar'}
            % error bars
            [m2t, str] = handleObject(m2t, h, @drawErrorBars);

        case {'specgraph.scattergroup','matlab.graphics.chart.primitive.Scatter'}
            % scatter plots
            [m2t, str] = handleObject(m2t, h, @drawScatterPlot);

        case {'specgraph.contourgroup', 'matlab.graphics.chart.primitive.Contour'}
            [m2t, str] = handleObject(m2t, h, @drawContour);

        case {'hggroup', 'matlab.graphics.primitive.Group'}
            % handle all those the usual way
            [m2t, str] = handleAllChildren(m2t, h);

        case 'unknown'
            % Octave only: plot type could not be determined
            % Fall back to basic plotting
            [m2t, str] = handleAllChildren(m2t, h);

        otherwise
            userWarning(m2t, 'Don''t know class ''%s''. Default handling.', cl);
            try
                m2tBackup = m2t;
                [m2t, str] = handleAllChildren(m2t, h);
            catch ME
                userWarning(m2t, 'Default handling for ''%s'' failed. Continuing as if it did not occur. \n Original Message:\n %s', cl, getReport(ME));
                [m2t, str] = deal(m2tBackup, ''); % roll-back
            end
    end
end
% ==============================================================================
% Function `handle` is not yet implemented in Octave.
% Consequently the plot type needs to be guessed. See #645.
% If the type can not be determined reliably, 'unknown' will be set.
function cl = guessOctavePlotType(h)
    % scatter plots
    if hasProperties(h, {'marker','sizedata','cdata'}, {})
        cl = 'specgraph.scattergroup';

        % error bars
    elseif hasProperties(h, {'udata','ldata'}, {})
        cl = 'specgraph.errorbarseries';

        % quiver plots
    elseif hasProperties(h, {'udata','vdata'}, {'ldata'})
        cl = 'specgraph.quivergroup';

        % bar plots
    elseif hasProperties(h, {'bargroup','barwidth', 'barlayout'}, {})
        cl = 'specgraph.barseries';
        % unknown plot type
    else
        cl = 'unknown';
    end
end
% ==============================================================================
function bool = hasProperties(h, fieldsExpectedPresent, fieldsExpectedAbsent)
    % Check if object has all of the given properties (case-insensitive).
    % h                     handle to object (e.g. `gcf` or `gca`)
    % fieldsExpectedPresent cell array of strings with property names to be present
    % fieldsExpectedPresent cell array of strings with property names to be absent
    fields = lower(fieldnames(get(h)));
    present = all(ismember(lower(fieldsExpectedPresent), fields));
    absent = ~any(ismember(lower(fieldsExpectedAbsent), fields));
    bool = present && absent;
end
% ==============================================================================
function [m2t, str] = handleObject(m2t, h, actualHandler)
    % this function wraps the actual drawing handlers and allows the user
    % to specify custom options, code, comments or even handler via |m2tcustom|.
    assert(isa(actualHandler,'function_handle'));

    custom = m2tcustom(h); % retrieve custom settings
    if isfield(custom, 'customHandler')
        actualHandler = custom.customHandler;
    end
    
    [m2t, str] = feval(actualHandler, m2t, h, custom);
    str = [texcomment(custom.commentsBefore), custom.codeBefore, ...
           str, ...
           texcomment(custom.commentsAfter), custom.codeAfter];
end
% ==============================================================================
function warnDoesNotHandleCustomProperties(h, custom, unhandledProperties)
% warns the user about unhandled custom properties
    if ~exist('unhandledProperties','var')
        unhandledProperties = {'codeInsideFirst', 'codeInsideLast', 'extraOptions'};
    elseif ischar(unhandledProperties)
        unhandledProperties = {unhandledProperties};
    end

    customProps = fieldnames(custom);
    for iProperty = 1:numel(customProps)
        prop = customProps{iProperty};
        if ~isempty(custom.(prop)) && ismember(prop, unhandledProperties)
            warning('matlab2tikz:unhandledCustomProperty', ...
                    'Custom property "%s" of %s has not been handled.',...
                    prop, get(h, 'Type'));
        end
    end
end
% ==============================================================================
function [m2t, str] = drawNothing(m2t, h, custom) %#ok some params unused
    % draws nothing
    % While it may seem useless, this allows a user to add comments/code
    % for objects that matlab2tikz cannot translate (yet) thanks to the
    % |handleObject| function together with |m2tcustom|.
    str = '';
    warnDoesNotHandleCustomProperties(h, custom);
end
% ==============================================================================
function m2t = drawAnnotations(m2t)
    % Draws annotation in Matlab (Octave not supported).

    % In HG1 annotations are children of an invisible axis called scribeOverlay.
    % In HG2 annotations are children of annotationPane object which does not
    % have any axis properties. Hence, we cannot simply handle it with a
    % drawAxes() call.

    % Octave
    if strcmpi(getEnvironment,'Octave')
        return
    end

    % Get annotation handles
    if isHG2
        annotPanes   = findall(m2t.current.gcf,'Tag','scribeOverlay');
        children = allchild(annotPanes);
        %TODO: is this dead code?
        if iscell(children)
            children = [children{:}];
        end
        annotHandles = findall(children,'Visible','on');
    else
        annotHandles = findall(m2t.scribeLayer,'-depth',1,'Visible','on');
    end

    % There are no anotations
    if isempty(annotHandles)
        return
    end

    % Create fake simplified axes overlay (no children)
    warning('off', 'matlab2tikz:NoChildren')
    scribeLayer = axes('Units','Normalized','Position',[0,0,1,1],'Visible','off');
    m2t         = drawAxes(m2t, scribeLayer);
    warning('on', 'matlab2tikz:NoChildren')

    % Plot in reverse to preserve z-ordering and assign the converted
    % annotations to the converted fake overlay
    for ii = numel(annotHandles):-1:1
        m2t = drawAnnotationsHelper(m2t, annotHandles(ii));
    end

    % Delete fake overlay graphics object
    delete(scribeLayer)
end
% ==============================================================================
function m2t = drawAnnotationsHelper(m2t,h)
    % Get class name
    try
        cl = class(handle(h));
    catch
        cl = 'unknown';
    end

    switch cl

        % Line
        case {'scribe.line', 'matlab.graphics.shape.Line'}
            [m2t, str] = handleObject(m2t, h, @drawLine);

            % Ellipse
        case {'scribe.scribeellipse','matlab.graphics.shape.Ellipse'}
            [m2t, str] = handleObject(m2t, h, @drawEllipse);

            % Arrows
        case {'scribe.arrow', 'scribe.doublearrow'}%,...
              %'matlab.graphics.shape.Arrow', 'matlab.graphics.shape.DoubleEndArrow'}
            % Annotation: single and double Arrow, line
            % TODO:
            % - write a drawArrow(). Handle all info info directly
            %   without using handleAllChildren() since HG2 does not have
            %   children (so no shortcut).
            % - It would be good if drawArrow() was callable on a
            %   matlab.graphics.shape.TextArrow object to draw the arrow
            %   part.
            [m2t, str] = handleAllChildren(m2t, h);

            % Text box
        case {'scribe.textbox','matlab.graphics.shape.TextBox'}
            [m2t, str] = handleObject(m2t, h, @drawText);
            
            % Arrow
        case {'matlab.graphics.shape.Arrow','matlab.graphics.shape.DoubleEndArrow'}
            [m2t, str] = handleObject(m2t, h, @drawArrow);

            % Text arrow
        case {'scribe.textarrow'}%,'matlab.graphics.shape.TextArrow'}
            % TODO: rewrite drawTextarrow. Handle all info info directly
            %       without using handleAllChildren() since HG2 does not
            %       have children (so no shortcut) as used for
            %       scribe.textarrow.
            [m2t, str] = handleObject(m2t, h, @drawTextarrow);

            % Rectangle
        case {'scribe.scriberect', 'matlab.graphics.shape.Rectangle'}
            [m2t, str] = handleObject(m2t, h, @drawRectangle);
            
        case guitypes()
            % don't do anything for GUI objects and their children
            [m2t, str] = handleObject(m2t, h, @drawNothing);                

        otherwise
            userWarning(m2t, 'Don''t know annotation ''%s''.', cl);
            return
    end

    % Add annotation to scribe overlay
    m2t.axes{end} = addChildren(m2t.axes{end}, str);
end
% ==============================================================================
function [m2t, str] = drawArrow(m2t, handle, custom)
    str = '';
    if ~isLineVisible(handle)
        return; % there is nothing to plot
    end
    
    m2t = signalDependency(m2t, 'tikzlibrary', 'arrows.meta');
    % Arrow Style
    if isa(handle,'matlab.graphics.shape.DoubleEndArrow')
        arrowSpec = '{Stealth}-{Stealth}';
    else
        arrowSpec = '-{Stealth}';
    end
    
    % Color
    color         = get(handle, 'Color');
    [m2t, xcolor] = getColor(m2t, handle, color, 'patch');
    
    % Line options
    [m2t, lineOptions]   = getLineOptions(m2t, handle);
    
    drawOptions = opts_new();
    drawOptions = opts_add(drawOptions, arrowSpec);
    drawOptions = opts_add(drawOptions, 'color', xcolor);
    drawOptions = opts_merge(drawOptions, lineOptions);
    drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);

    [m2t, pos1, pos2] = getPositionOfArrow(m2t, handle);

    styleOpts = opts_print(drawOptions);
    str = sprintf('\\draw[%s] %s -- %s;\n', ...
        styleOpts, pos1, pos2);
end
% ==============================================================================
function [m2t, pos1, pos2] = getPositionOfArrow(m2t, h)
    % makes the tikz position string of an arrow
    posX   = get(h, 'X');
    posY   = get(h, 'Y');
    
    type    = 'axis cs:';
    fmtUnit = '';
    
    pos1{1} = formatDim(posX(1), fmtUnit);
    pos1{2} = formatDim(posY(1), fmtUnit);
    pos2{1} = formatDim(posX(2), fmtUnit);
    pos2{2} = formatDim(posY(2), fmtUnit);

    pos1 = sprintf('(%s%s)',type,join(m2t,pos1,','));
    pos2 = sprintf('(%s%s)',type,join(m2t,pos2,','));
end
% ==============================================================================
function [m2t, str] = drawSurface(m2t, h, custom)
    [m2t, opts, s] = shaderOpts(m2t, h,'surf');
    tableOptions = opts_new();

    % Allow for empty surf
    if isNone(s.plotType)
        str = '';
        return
    end

    [dx, dy, dz, numrows] = getXYZDataFromSurface(h);
    m2t = jumpAtUnboundCoords(m2t, [dx(:); dy(:); dz(:)]);

    [m2t, opts] = addZBufferOptions(m2t, h, opts);

    % Check if 3D
    is3D = m2t.axes{end}.is3D;
    if is3D
        columnNames = {'x','y','z','c'};
        plotCmd     = 'addplot3';
        data        = applyHgTransform(m2t, [dx(:), dy(:), dz(:)]);
    else
        columnNames = {'x','y','c'};
        plotCmd     = 'addplot';
        data        = [dx(:), dy(:)];
    end

    % There are several possibilities of how colors are specified for surface
    % plots:
    %    * explicitly by RGB-values,
    %    * implicitly through a color map with a point-meta coordinate,
    %    * implicitly through a color map with a given coordinate (e.g., z).
    %

    % Check if we need extra CData.
    CData = get(h, 'CData');
    if length(size(CData)) == 3 && size(CData, 3) == 3

        % Create additional custom colormap
        nrows = size(data,1);
        CData = reshape(CData, nrows,3);
        m2t.axes{end}.options(end+1,:) = ...
            {matlab2pgfplotsColormap(m2t, CData, 'patchmap'), []};

        % Index into custom colormap
        color = (0:nrows-1)';

        tableOptions = opts_add(tableOptions, 'colormap name','surfmap');
    else
        opts = opts_add(opts,matlab2pgfplotsColormap(m2t, m2t.current.colormap),'');
        % If NaNs are present in the color specifications, don't use them for
        % Pgfplots; they may be interpreted as strings there.
        % Note:
        % Pgfplots actually does a better job than MATLAB in determining what
        % colors to use for the patches. The circular test case on
        % http://www.mathworks.de/de/help/matlab/ref/pcolor.html, for example
        % yields a symmetric setup in Pgfplots (and doesn't in MATLAB).
        needsPointmeta = any(xor(isnan(dz(:)), isnan(CData(:)))) ...
                      || any(abs(CData(:) - dz(:)) > 1.0e-10);
        if needsPointmeta
            color = CData(:);
        else
            color = dz(:);      % Fallback on the z-values, especially if 2D view
        end
    end
    tableOptions = opts_add(tableOptions, 'point meta','\thisrow{c}');

    data = [data, color];

    % Add mesh/rows=<num rows> for specifying the row data instead of empty
    % lines in the data list below. This makes it possible to reduce the
    % data writing to one single sprintf() call.
    opts = opts_add(opts,'mesh/rows',sprintf('%d', numrows));
    opts = opts_append_userdefined(opts, custom.extraOptions);

    % Print the addplot options
    str = sprintf('\n\\%s[%%\n%s,\n%s]', plotCmd, s.plotType, opts_print(opts));

    % Print the data
    [m2t, table, tabOptsExtra] = makeTable(m2t, columnNames, data);
    tableOptions = opts_merge(tabOptsExtra, tableOptions);
    tabOpts = opts_print(tableOptions);

    % Here is where everything is put together
    str = sprintf('%s\ntable[%s] {%%\n%s};\n', ...
                  str, tabOpts, table);

    % TODO:
    % - remove grids in spectrogram by either removing grid command
    %   or adding: 'grid=none' from/in axis options
    % - handling of huge data amounts in LaTeX.

    [m2t, labelString] = addLabel(m2t, h);
    str = [str, labelString];
end
% ==============================================================================
function [m2t, opts] = addZBufferOptions(m2t, h, opts)
    % Enforce 'z buffer=sort' if shader is flat and is a 3D plot. It is to
    % avoid overlapping e.g. sphere plots and to properly mimic Matlab's
    % coloring of faces.
    % NOTE:
    % - 'z buffer=sort' is computationally more expensive for LaTeX, we
    %   could try to avoid it in some default situations, e.g. when dx and
    %   dy are rank-1-matrices.
    % - hist3D plots should not be z-sorted or the highest bars will cover
    %   the shortest one even if positioned in the back
    isShaderFlat = isempty(strfind(opts_get(opts, 'shader'), 'interp'));
    isHist3D     = strcmpi(get(h,'tag'), 'hist3');
    is3D         = m2t.axes{end}.is3D;
    if is3D && isShaderFlat && ~isHist3D
        opts = opts_add(opts, 'z buffer', 'sort');
        % Pgfplots 1.12 contains a bug fix that fixes legend entries when
        % 'z buffer=sort' has been set. So, it's  easier to always require that
        % version anyway. See #504 for more information.
        m2t = needsPgfplotsVersion(m2t, [1,12]);
    end
end
% ==============================================================================
function [dx, dy, dz, numrows] = getXYZDataFromSurface(h)
    % retrieves X, Y and Z data from a Surface plot. The data gets returned in a
    % wastefull format where the dimensions of these data vectors is equal, akin
    % to the format used by meshgrid.
    dx = get(h, 'XData');
    dy = get(h, 'YData');
    dz = get(h, 'ZData');
    [numcols, numrows] = size(dz);

    % If dx or dy are given as vectors, convert them to the (wasteful) matrix
    % representation first. This makes sure we can treat the data with one
    % single sprintf() command below.
    if isvector(dx)
        dx = ones(numcols,1) * dx(:)';
    end
    if isvector(dy)
        dy = dy(:) * ones(1,numrows);
    end
end
% ==============================================================================
function [m2t, str] = drawVisibleText(m2t, handle, custom)
    % Wrapper for drawText() that only draws visible text

    % There may be some text objects floating around a MATLAB figure which are
    % handled by other subfunctions (labels etc.) or don't need to be handled at
    % all.
    % The HandleVisibility says something about whether the text handle is
    % visible as a data structure or not. Typically, a handle is hidden if the
    % graphics aren't supposed to be altered, e.g., axis labels.  Most of those
    % entities are captured by matlab2tikz one way or another, but sometimes they
    % are not. This is the case, for example, with polar plots and the axis
    % descriptions therein.  Also, Matlab treats text objects with a NaN in the
    % position as invisible.
    if any(isnan(get(handle, 'Position')) | isnan(get(handle, 'Rotation'))) ...
            || isOff(get(handle, 'Visible')) ...
            || (isOff(get(handle, 'HandleVisibility')) && ...
                ~m2t.args.showHiddenStrings)

        str = '';
        return;
    end

    [m2t, str] = drawText(m2t, handle, custom);

end
% ==============================================================================
function [m2t, str] = drawText(m2t, handle, custom)
    % Adding text node anywhere in the axes environment.
    % Not that, in Pgfplots, long texts get cut off at the axes. This is
    % Different from the default MATLAB behavior. To fix this, one could use
    % /pgfplots/after end axis/.code.
    
    if ~exist('custom','var') || isempty(custom)
        custom = m2tcustom([]); %FIXME: adjust at all call sites instead
    end

    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % get required properties
    content     = get(handle, 'String');
    Interpreter = get(handle, 'Interpreter');
    content     = prettyPrint(m2t, content, Interpreter);
    % Concatenate multiple lines
    content = join(m2t, content, '\\');
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % translate them to pgf style
    style = opts_new();

    bgColor = get(handle,'BackgroundColor');
    [m2t, style] = setColor(m2t, handle, style, 'fill', bgColor);

    style = getXYAlignmentOfText(handle, style);

    style = getRotationOfText(m2t, handle, style);

    [m2t, fontStyle] = getFontStyle(m2t, handle);
    style = opts_merge(style, fontStyle);

    EdgeColor = get(handle, 'EdgeColor');
    [m2t, style] = setColor(m2t, handle, style, 'draw', EdgeColor);
    
    style = opts_append_userdefined(style, custom.extraOptions);
    
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % plot the thing
    [m2t, posString] = getPositionOfText(m2t, handle);

    styleOpts = opts_print(style);
    str       = sprintf('\\node[%s]\nat %s {%s};\n', ...
                        styleOpts, posString, content);
end
% ==============================================================================
function [style] = getXYAlignmentOfText(handle, style)
    % sets the horizontal and vertical alignment options of a text object
    VerticalAlignment   = get(handle, 'VerticalAlignment');
    HorizontalAlignment = get(handle, 'HorizontalAlignment');

    horizontal = '';
    vertical   = '';
    switch VerticalAlignment
        case {'top', 'cap'}
            vertical = 'below';
        case {'baseline', 'bottom'}
            vertical = 'above';
    end
    switch HorizontalAlignment
        case 'left'
            horizontal = 'right';
        case 'right'
            horizontal = 'left';
    end
    alignment = strtrim(sprintf('%s %s', vertical, horizontal));
    if strcmp(VerticalAlignment, 'middle') && strcmp(HorizontalAlignment, 'center')
        alignment = 'centered';
    end
    
    if ~isempty(alignment)
        style = opts_add(style, alignment);
    end

    % Set 'align' option that is needed for multiline text
    style = opts_add(style, 'align', HorizontalAlignment);
    
    style = opts_add(style, 'inner sep', '0');
end
% ==============================================================================
function [style] = getRotationOfText(m2t, handle, style)
    % Add rotation, if existing
    defaultRotation = 0.0;
    rot = getOrDefault(handle, 'Rotation', defaultRotation);
    if rot ~= defaultRotation
        style = opts_add(style, 'rotate', sprintf(m2t.ff, rot));
    end
end
% ==============================================================================
function [m2t,posString] = getPositionOfText(m2t, h)
    % makes the tikz position string of a text object
    pos   = get(h, 'Position');
    units = get(h, 'Units');
    is3D  = m2t.axes{end}.is3D;

    % Deduce if text or textbox
    type = get(h,'type');
    if isempty(type) || strcmpi(type,'hggroup')
        type = get(h,'ShapeType'); % Undocumented property valid from 2008a
    end

    switch type
        case 'text'
            if is3D
                pos  = applyHgTransform(m2t, pos);
                npos = 3;
            else
                pos  = pos(1:2);
                npos = 2;
            end
        case {'textbox','textboxshape'}
            % TODO:
            %   - size of the box (e.g. using node attributes minimum width / height)
            %   - Alignment of the resized box
            pos  = pos(1:2);
            npos = 2;

        otherwise
            error('matlab2tikz:drawText', 'Unrecognized text type: %s.', type);
    end

    % Format according to units
    switch units
        case 'normalized'
            type    = 'rel axis cs:';
            fmtUnit = '';
        case 'data'
            type    = 'axis cs:';
            fmtUnit = '';
            % Let Matlab do the conversion of any unit into cm
        otherwise
            type    = '';
            fmtUnit = 'cm';
            if ~strcmpi(units, 'centimeters')
                % Save old pos, set units to cm, query pos, reset
                % NOTE: cannot use copyobj since it is buggy in R2014a, see
                %       http://www.mathworks.com/support/bugreports/368385
                oldPos = get(h, 'Position');
                set(h,'Units','centimeters')
                pos    = get(h, 'Position');
                pos    = pos(1:npos);
                set(h,'Units',units,'Position',oldPos)
            end
    end
    posString = cell(1,npos);
    for ii = 1:npos
        posString{ii} = formatDim(pos(ii), fmtUnit);
    end

    posString = sprintf('(%s%s)',type,join(m2t,posString,','));
    m2t = disableClippingInCurrentAxes(m2t, pos);

end
% ==============================================================================
function m2t = disableClippingInCurrentAxes(m2t, pos)
    % Disables clipping in the current axes if the `pos` vector lies outside
    % the limits of the axes.
    xlim  = getOrDefault(m2t.current.gca, 'XLim',[-Inf +Inf]);
    ylim  = getOrDefault(m2t.current.gca, 'YLim',[-Inf +Inf]);
    zlim  = getOrDefault(m2t.current.gca, 'ZLim',[-Inf +Inf]);
    is3D  = m2t.axes{end}.is3D;

    xOutOfRange =          pos(1) < xlim(1) || pos(1) > xlim(2);
    yOutOfRange =          pos(2) < ylim(1) || pos(2) > ylim(2);
    zOutOfRange = is3D && (pos(3) < zlim(1) || pos(3) > zlim(2));
    if xOutOfRange || yOutOfRange || zOutOfRange
        m2t = m2t_addAxisOption(m2t, 'clip', 'false');
    end
end
% ==============================================================================
function [m2t, str] = drawRectangle(m2t, h, custom)
    str = '';

    % there may be some text objects floating around a Matlab figure which
    % are handled by other subfunctions (labels etc.) or don't need to be
    % handled at all
    if ~isVisible(h) || isOff(get(h, 'HandleVisibility'))
        return;
    end

    % TODO handle Curvature = [0.8 0.4]

    % Get draw options.
    [m2t, lineOptions] = getLineOptions(m2t, h);
    [m2t, lineOptions] = getRectangleFaceOptions(m2t, h, lineOptions);
    [m2t, lineOptions] = getRectangleEdgeOptions(m2t, h, lineOptions);
    lineOptions = opts_append_userdefined(lineOptions, custom.extraOptions);
    
    pos = pos2dims(get(h, 'Position'));
    % plot the thing
    lineOpts = opts_print(lineOptions);
    str = sprintf(['\\draw[%s] (axis cs:',m2t.ff,',',m2t.ff, ')', ...
                   ' rectangle (axis cs:',m2t.ff,',',m2t.ff,');\n'], ...
                   lineOpts, pos.left, pos.bottom, pos.right, pos.top);
end
% ==============================================================================
function [m2t, drawOptions] = getRectangleFaceOptions(m2t, h, drawOptions)
    % draws the face (i.e. fill) of a Rectangle
    faceColor    = get(h, 'FaceColor');
    isAnnotation = strcmpi(get(h,'type'),'rectangleshape') || ...
                   strcmpi(getOrDefault(h,'ShapeType',''),'rectangle');
    isFlatColor  = strcmpi(faceColor, 'flat');
    if ~(isNone(faceColor) || (isAnnotation && isFlatColor))
        [m2t, xFaceColor] = getColor(m2t, h, faceColor, 'patch');
        drawOptions = opts_add(drawOptions, 'fill', xFaceColor);
    end
end
% ==============================================================================
function [m2t, drawOptions] = getRectangleEdgeOptions(m2t, h, drawOptions)
    % draws the edges of a rectangle
    edgeColor = get(h, 'EdgeColor');
    lineStyle = get(h, 'LineStyle');
    if isNone(lineStyle) || isNone(edgeColor)
        drawOptions = opts_add(drawOptions, 'draw', 'none');
    else
        [m2t, drawOptions] = setColor(m2t, h, drawOptions, 'draw', edgeColor);
    end
end
% ==============================================================================
function [m2t,opts,s] = shaderOpts(m2t, handle, selectedType)
    % SHADEROPTS Returns the shader, fill and draw options for patches, surfs and meshes
    %
    %   SHADEROPTS(M2T, HANDLE, SELECTEDTYPE) Where SELECTEDTYPE should either
    %   be 'surf' or 'patch'
    %
    %
    %   [...,OPTS, S] = SHADEROPTS(...)
    %       OPTS is a M by 2 cell array with Key/Value pairs
    %       S is a struct with fields, e.g. 'faceColor', to be re-used by the
    %       caller

    % Initialize
    opts              = opts_new;
    s.hasOneEdgeColor = false;
    s.hasOneFaceColor = false;

    % Get relevant Face and Edge color properties
    s.faceColor = get(handle, 'FaceColor');
    s.edgeColor = get(handle, 'EdgeColor');

    if isNone(s.faceColor) && isNone(s.edgeColor)
        s.plotType        = 'none';
        s.hasOneEdgeColor = true;
    elseif isNone(s.faceColor)
        s.plotType        = 'mesh';
        s.hasOneFaceColor = true;
        [m2t, opts, s]    = shaderOptsMesh(m2t, handle, opts, s);
    else
        s.plotType     = selectedType;
        [m2t, opts, s] = shaderOptsSurfPatch(m2t, handle, opts, s);
    end
end
% ==============================================================================
function [m2t, opts, s] = shaderOptsMesh(m2t, handle, opts, s)

    % Edge 'interp'
    if strcmpi(s.edgeColor, 'interp')
        opts = opts_add(opts,'shader','flat');

        % Edge RGB
    else
        s.hasOneEdgeColor = true;
        [m2t, xEdgeColor] = getColor(m2t, handle, s.edgeColor, 'patch');
        opts              = opts_add(opts,'color',xEdgeColor);
    end
end
% ==============================================================================
function [m2t, opts, s] = shaderOptsSurfPatch(m2t, handle, opts, s)
    % gets the shader options for surface patches

    % Set opacity if FaceAlpha < 1 in MATLAB
    s.faceAlpha = get(handle, 'FaceAlpha');
    if isnumeric(s.faceAlpha) && s.faceAlpha ~= 1.0
        opts = opts_add(opts,'fill opacity',sprintf(m2t.ff,s.faceAlpha));
    end

    % Set opacity if EdgeAlpha < 1 in MATLAB
    s.edgeAlpha = get(handle, 'EdgeAlpha');
    if isnumeric(s.edgeAlpha) && s.edgeAlpha ~= 1.0
        opts = opts_add(opts,'draw opacity',sprintf(m2t.ff,s.edgeAlpha));
    end

    if isNone(s.edgeColor) || isNone(get(handle,'LineStyle')) % Edge 'none'
        [m2t, opts, s] = shaderOptsSurfPatchEdgeNone(m2t, handle, opts, s);

    elseif strcmpi(s.edgeColor, 'interp') % Edge 'interp'
        [m2t, opts, s] = shaderOptsSurfPatchEdgeInterp(m2t, handle, opts, s);

    elseif strcmpi(s.edgeColor, 'flat') % Edge 'flat'
        [m2t, opts, s] = shaderOptsSurfPatchEdgeFlat(m2t, handle, opts, s);

    else % Edge RGB
        [m2t, opts, s] = shaderOptsSurfPatchEdgeRGB(m2t, handle, opts, s);
    end
end
% ==============================================================================
function [m2t, opts, s] = shaderOptsSurfPatchEdgeNone(m2t, handle, opts, s)
    % gets the shader options for surface patches without edges
    s.hasOneEdgeColor = true; % consider void as true
    if strcmpi(s.faceColor, 'flat')
        opts = opts_add(opts,'shader','flat');
    elseif strcmpi(s.faceColor, 'interp')
        opts = opts_add(opts,'shader','interp');
    else
        s.hasOneFaceColor = true;
        [m2t,xFaceColor]  = getColor(m2t, handle, s.faceColor, 'patch');
        opts              = opts_add(opts,'fill',xFaceColor);
    end
end
function [m2t, opts, s] = shaderOptsSurfPatchEdgeInterp(m2t, handle, opts, s)
    % gets the shader options for surface patches with interpolated edge colors
    if strcmpi(s.faceColor, 'interp')
        opts = opts_add(opts,'shader','interp');
    elseif strcmpi(s.faceColor, 'flat')
        opts = opts_add(opts,'shader','faceted');
    else
        s.hasOneFaceColor = true;
        [m2t,xFaceColor]  = getColor(m2t, handle, s.faceColor, 'patch');
        opts              = opts_add(opts,'fill',xFaceColor);
    end
end
function [m2t, opts, s] = shaderOptsSurfPatchEdgeFlat(m2t, handle, opts, s)
    % gets the shader options for surface patches with flat edge colors, i.e. the
    % vertex color
    if strcmpi(s.faceColor, 'flat')
        opts = opts_add(opts,'shader','flat corner');
    elseif strcmpi(s.faceColor, 'interp')
        warnFacetedInterp(m2t);
        opts = opts_add(opts,'shader','faceted interp');
    else
        s.hasOneFaceColor = true;
        opts              = opts_add(opts,'shader','flat corner');
        [m2t,xFaceColor]  = getColor(m2t, handle, s.faceColor, 'patch');
        opts              = opts_add(opts,'fill',xFaceColor);
    end
end
function [m2t, opts, s] = shaderOptsSurfPatchEdgeRGB(m2t, handle, opts, s)
    % gets the shader options for surface patches with fixed (RGB) edge color
    s.hasOneEdgeColor = true;
    [m2t, xEdgeColor] = getColor(m2t, handle, s.edgeColor, 'patch');
    if isnumeric(s.faceColor)
        s.hasOneFaceColor = true;
        [m2t, xFaceColor] = getColor(m2t, handle, s.faceColor, 'patch');
        opts              = opts_add(opts,'fill',xFaceColor);
        opts              = opts_add(opts,'faceted color',xEdgeColor);
    elseif strcmpi(s.faceColor,'interp')
        warnFacetedInterp(m2t);
        opts = opts_add(opts,'shader','faceted interp');
        opts = opts_add(opts,'faceted color',xEdgeColor);
    else
        opts = opts_add(opts,'shader','flat corner');
        opts = opts_add(opts,'draw',xEdgeColor);
    end
end
% ==============================================================================
function warnFacetedInterp(m2t)
    % warn the user about the space implications of "shader=faceted interp"
    userWarning(m2t, ...
        ['A 3D plot with "shader = faceted interp" is being produced.\n', ...
        'This may produce big and sluggish PDF files.\n', ...
        'See %s and Section 4.6.6 of the pgfplots manual for workarounds.'], ...
        issueUrl(m2t, 693, true));
end
% ==============================================================================
function url = issueUrl(m2t, number, forOutput)
    % Produces the URL for an issue report in the GitHub repository.
    % When the `forOutput` flag is set, this format the URL for printing to the
    % MATLAB terminal.
    if ~exist('forOutput','var') || isempty(forOutput)
        forOutput = false;
    end
    url = sprintf('%s/%d', m2t.about.issues, number);
    if forOutput
        url = clickableUrl(url, sprintf('#%d', number));
    end
end
% ==============================================================================
function url = clickableUrl(url, title)
    % Produce a clickable URL for outputting to the MATLAB terminal
    if ~exist('title','var') || isempty(title)
        title = url;
    end
    switch getEnvironment()
        case 'MATLAB'
            url = sprintf('<a href="%s">%s</a>', url, title);
        case 'Octave'
            % just use the URL and discard the title since Octave doesn't
            % support HTML tags in its output.
        otherwise
            errorUnknownEnvironment();
    end
end
% ==============================================================================
function [m2t, str] = drawScatterPlot(m2t, h, custom)
    % DRAWSCATTERPLOT Draws a scatter plot
    %
    % A scatter plot is a plot containing only markers and where the
    % size and/or color of each marker can be changed independently.
    %
    % References for TikZ code:
    %  - http://tex.stackexchange.com/questions/197270/how-to-plot-scatter-points-using-pgfplots-with-color-defined-from-table-rgb-valu
    %  - http://tex.stackexchange.com/questions/98646/multiple-different-meta-for-marker-color-and-marker-size
    %
    % See also: scatter
    str = '';
    if ~isVisible(h)
        return; % there is nothing to plot
    end

    dataInfo   = getDataInfo(h, 'X','Y','Z','C','Size');
    markerInfo = getMarkerInfo(m2t, h);

    if isempty(dataInfo.C) && strcmpi(getEnvironment(), 'Octave')
        dataInfo.C = get(h, 'MarkerEdgeColor');
    end

    %TODO: check against getMarkerOptions() for duplicated code

    dataInfo.Size = tryToMakeScalar(dataInfo.Size, m2t.tol);

    % Rescale marker size (not definitive, follow discussion in #316)
    % Prescale marker size for octave
    if strcmpi(getEnvironment(), 'Octave')
        dataInfo.Size = dataInfo.Size.^2/2;
    end
    dataInfo.Size = translateMarkerSize(m2t, markerInfo.style, sqrt(dataInfo.Size)/2);

    drawOptions = opts_new();

    %% Determine if we are drawing an actual scatter plot
    hasDifferentSizes  = numel(dataInfo.Size) ~= 1;
    hasDifferentColors = numel(dataInfo.C)    ~= 3;
    isaScatter         = hasDifferentSizes || hasDifferentColors;
    if isaScatter
        drawOptions = opts_add(drawOptions, 'scatter');
    end
    %TODO: we need to set the scatter source
    drawOptions = opts_add(drawOptions, 'only marks');
    drawOptions = opts_add(drawOptions, 'mark', markerInfo.tikz);

    if length(dataInfo.C) == 3
        % gets options specific to scatter plots with a single color
        % No special treatment for the colors or markers are needed.
        % All markers have the same color.
        [m2t, xcolor, hasFaceColor] = getColorOfMarkers(m2t, h, 'MarkerFaceColor', dataInfo.C);
        [m2t, ecolor, hasEdgeColor] = getColorOfMarkers(m2t, h, 'MarkerEdgeColor', dataInfo.C);

        if length(dataInfo.Size) == 1;
            drawOptions = opts_addSubOpts(drawOptions, 'mark options', ...
                                       markerInfo.options);
            drawOptions = opts_add(drawOptions, 'mark size', ...
                sprintf('%.4fpt', dataInfo.Size)); % FIXME: investigate whether to use `m2t.ff`
            if hasEdgeColor
                drawOptions = opts_add(drawOptions, 'draw', ecolor);
            else
                drawOptions = opts_add(drawOptions, 'color', xcolor); %TODO: why do we even need this one?
            end
            if hasFaceColor
                drawOptions = opts_add(drawOptions, 'fill', xcolor);
            end
        else % if changing marker size but same color on all marks
            markerOptions = opts_new();
            markerOptions = opts_addSubOpts(markerOptions, 'mark options', ...
                                         markerInfo.options);
            if hasEdgeColor
                markerOptions = opts_add(markerOptions, 'draw', ecolor);
            else
                markerOptions = opts_add(markerOptions, 'draw', xcolor);
            end
            if hasFaceColor
                markerOptions = opts_add(markerOptions, 'fill', xcolor);
            end
            % for changing marker size, the 'scatter' option has to be added
            drawOptions = opts_add(drawOptions, 'color', xcolor);
            drawOptions = opts_addSubOpts(drawOptions, 'mark options', ...
                                       markerInfo.options);

            if ~hasFaceColor
                drawOptions = opts_add(drawOptions, ...
                    'scatter/use mapped color', xcolor);
            else
                drawOptions = opts_addSubOpts(drawOptions, ...
                    'scatter/use mapped color', markerOptions);
            end
        end
    elseif size(dataInfo.C,2) == 3
        % scatter plots with each marker a different RGB color (not yet supported!)
        userWarning(m2t, 'Pgfplots cannot handle RGB scatter plots yet.');
        % TODO Get this in order as soon as Pgfplots can do "scatter rgb".
        % See e.g. http://tex.stackexchange.com/questions/197270 and #433
    else
        % scatter plot where the colors are set using a color map
        markerOptions = opts_new();
        markerOptions = opts_addSubOpts(markerOptions, 'mark options', ...
                                     markerInfo.options);
        if markerInfo.hasEdgeColor && markerInfo.hasFaceColor
            [m2t, ecolor] = getColor(m2t, h, markerInfo.EdgeColor, 'patch');
            markerOptions = opts_add(markerOptions, 'draw', ecolor);
        else
            markerOptions = opts_add(markerOptions, 'draw', 'mapped color');
        end
        if markerInfo.hasFaceColor
            markerOptions = opts_add(markerOptions, 'fill', 'mapped color');
        end

        if numel(dataInfo.Size) == 1
            drawOptions = opts_add(drawOptions, 'mark size', ...
                sprintf('%.4fpt', dataInfo.Size)); % FIXME: investigate whether to use `m2t.ff` 
        else
            %TODO: warn the user about this. It is not currently supported.
        end

        drawOptions = opts_add(drawOptions, 'scatter src', 'explicit');
        drawOptions = opts_addSubOpts(drawOptions, 'scatter/use mapped color', ...
                                   markerOptions);
        % Add color map.
        m2t = m2t_addAxisOption(m2t, matlab2pgfplotsColormap(m2t, m2t.current.colormap), []);
    end
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % Plot the thing.
    [env, data, metaPart, columns] = organizeScatterData(m2t, dataInfo);

    if hasDifferentSizes
        drawOptions = opts_append(drawOptions, 'visualization depends on', ...
            '{\thisrow{size} \as \perpointmarksize}');
        drawOptions = opts_add(drawOptions, ...
            'scatter/@pre marker code/.append style', ...
            '{/tikz/mark size=\perpointmarksize}');
    end
    % Add forget plot
    drawOptions = maybeShowInLegend(m2t.currentHandleHasLegend, drawOptions);
    
    % The actual printing.
    [m2t, table, tableOptions] = makeTable(m2t, columns, data);
    tableOptions = opts_merge(tableOptions, metaPart);
    drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
    
    % Print
    drawOpts = opts_print(drawOptions);
    tabOpts  = opts_print(tableOptions);
    str      = sprintf('\\%s[%s] table[%s]{%s};\n',...
                       env, drawOpts, tabOpts, table);
end
% ==============================================================================
function dataInfo = getDataInfo(h, varargin)
    % retrieves the "*Data  fields from a HG object
    % When no names are specified, it assumes 'X','Y','Z' is requested
    if nargin == 1
        fields = {'X','Y','Z'};
    else
        fields = varargin;
    end
    dataInfo = struct();
    for iField = 1:numel(fields)
        name            = fields{iField};
        dataInfo.(name) = get(h, [name 'Data']);
    end
end
% ==============================================================================
function value = tryToMakeScalar(value, tolerance)
    % make a vector into a scalar when all its components are equal
    if ~exist('tolerance','var')
        tolerance = 0; % do everything perfectly
    end
    if all(abs(value - value(1)) <= tolerance)
        value = value(1);
    end
end
% ==============================================================================
function marker = getMarkerInfo(m2t, h, markOptions)
    % gets marker-related options as a struct
    if ~exist('markOptions','var') || isempty(markOptions)
        markOptions = opts_new();
    end
    marker                        = struct();
    marker.style                  = get(h, 'Marker');
    marker.FaceColor              = get(h, 'MarkerFaceColor');
    marker.EdgeColor              = get(h, 'MarkerEdgeColor');
    marker.hasFaceColor           = ~isNone(marker.FaceColor);
    marker.hasEdgeColor           = ~isNone(marker.EdgeColor);
    [marker.tikz, marker.options] = translateMarker(m2t, marker.style, ...
                                            markOptions, marker.hasFaceColor);
end
% ==============================================================================
function [env, data, metaOptions, columns] = organizeScatterData(m2t, dataInfo)
    % reorganizes the {X,Y,Z,S} data into a single matrix
    metaOptions = opts_new();


    xData = dataInfo.X;
    yData = dataInfo.Y;
    zData = dataInfo.Z;
    cData = dataInfo.C;
    sData = dataInfo.Size;

    % add the actual data
    if ~m2t.axes{end}.is3D
        env     = 'addplot';
        columns = {'x','y'};
        data    = [xData(:), yData(:)];
    else
        env     = 'addplot3';
        columns = {'x','y','z'};
        data    = applyHgTransform(m2t, [xData(:), yData(:), zData(:)]);
    end

    % add marker sizes
    if length(sData) ~= 1
        columns = [columns, {'size'}];
        data    = [data, sData(:)];
    end

    % add color data
    if length(cData) == 3
        % If size(cData,1)==1, then all the colors are the same and have
        % already been accounted for above.

    elseif size(cData,2) == 3
        %TODO Hm, can't deal with this?
        %[m2t, col] = rgb2colorliteral(m2t, cData(k,:));
        %str = strcat(str, sprintf(' [%s]\n', col));
        columns = [columns, {'R','G','B'}];
        data    = [data, cData(:,1), cData(:,2), cData(:,3)];
    else
        columns = [columns, {'color'}];
        metaOptions = opts_add(metaOptions, 'meta', 'color');
        data = [data, cData(:)];
    end
end
% ==============================================================================
function [m2t, xcolor, hasColor] = getColorOfMarkers(m2t, h, name, cData)
    color = get(h, name);
    hasColor = ~isNone(color);
    if hasColor && ~strcmpi(color,'flat');
        [m2t, xcolor] = getColor(m2t, h, color, 'patch');
    else
        [m2t, xcolor] = getColor(m2t, h, cData, 'patch');
    end
end
% ==============================================================================
function [m2t, str] = drawHistogram(m2t, h, custom)
    % Takes care of plots like the ones produced by MATLAB's histogram function.
    % The main pillar is Pgfplots's '{x,y}bar' plot.
    %
    % TODO Get rid of code duplication with 'drawAxes'.

    % Do nothing if plot is invisible
    str = '';
    if ~isVisible(h)
        return;
    end

    % Init drawOptions
    drawOptions = opts_new();

    % Data
    binEdges = get(h, 'BinEdges');
    binValue = get(h, 'Values');
    data     = [binEdges(:), [binValue(:); binValue(end)]];

    % Check for orientation of the bars
    isHorizontal = ~strcmpi(get(h, 'Orientation'), 'vertical');
    if isHorizontal
        drawOptions = opts_add(drawOptions, 'xbar interval');
        data        = fliplr(data);
    else
        drawOptions = opts_add(drawOptions, 'ybar interval');
    end

    % Get the draw options for the bars
    [m2t, drawOptions] = getPatchDrawOptions(m2t, h, drawOptions);
    drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
    
    % Make table
    [m2t, table, tableOptions] = makeTable(m2t, {'x','y'},data);

    % Print out
    drawOpts = opts_print(drawOptions);
    tabOpts  = opts_print(tableOptions);
    str      = sprintf('\\addplot[%s] table[%s] {%s};\n', ...
                       drawOpts, tabOpts, table);
end
% ==============================================================================
function [m2t, str] = drawBarseries(m2t, h, custom)
    % Takes care of plots like the ones produced by MATLAB's bar function.
    % The main pillar is Pgfplots's '{x,y}bar' plot.
    %
    % TODO Get rid of code duplication with 'drawAxes'.

    % Do nothing if plot is invisible
    str = '';
    if ~isVisible(h)
        return;
    end

    % Init drawOptions
    drawOptions = opts_new();

    % Check for orientation of the bars and their layout
    isHorizontal = isOn(get(h, 'Horizontal'));
    if isHorizontal
        barType = 'xbar';
    else
        barType = 'ybar';
    end

    % Get the draw options for the layout
    [m2t, drawOptions] = setBarLayoutOfBarSeries(m2t, h, barType, drawOptions);

    % Get the draw options for the bars
    [m2t, drawOptions] = getPatchDrawOptions(m2t, h, drawOptions);

    % Add 'log origin = infty' if BaseValue differs from zero (log origin=0 is
    % the default behaviour since Pgfplots v1.5).
    baseValue = get(h, 'BaseValue');
    if baseValue ~= 0.0
        m2t = m2t_addAxisOption(m2t, 'log origin', 'infty');
        %TODO: wait for pgfplots to implement other base values (see #438)
    end

    % Generate the tikz table
    xData = get(h, 'XData');
    yData = get(h, 'YData');
    if isHorizontal
        [yDataPlot, xDataPlot] = deal(xData, yData); % swap values
    else
        [xDataPlot, yDataPlot] = deal(xData, yData);
    end
    [m2t, table, tableOptions] = makeTable(m2t, '', xDataPlot, '', yDataPlot);

    drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
    % Print out
    drawOpts = opts_print(drawOptions);
    tabOpts  = opts_print(tableOptions);
    str      = sprintf('\\addplot[%s] table[%s] {%s};\n', ...
                       drawOpts, tabOpts, table);
    % Add a baseline if appropriate
    [m2t, baseline] = drawBaseline(m2t,h,isHorizontal);
    str             = [str, baseline];
end
% ==============================================================================
function BarWidth = getBarWidthInAbsolutUnits(h)
    % determines the width of a bar in a bar plot
    XData = get(h,'XData');
    BarWidth = get(h, 'BarWidth');
    if length(XData) > 1 && ~isCategoricalType(XData)
        BarWidth = min(diff(XData)) * BarWidth;
    end
end
% ==============================================================================
function [m2t, drawOptions] = setBarLayoutOfBarSeries(m2t, h, barType, drawOptions)
    % sets the options specific to a bar layour (grouped vs stacked)
    barlayout = get(h, 'BarLayout');

    switch barlayout
        case 'grouped'  % grouped bar plots

            % Get number of bars series and bar series id
            [numBarSeries, barSeriesId] = getNumBarAndId(h);

            % Maximum group width relative to the minimum distance between two
            % x-values. See <MATLAB>/toolbox/matlab/specgraph/makebars.m
            maxGroupWidth = 0.8;
            if numBarSeries == 1
                groupWidth = 1.0;
            else
                groupWidth = min(maxGroupWidth, numBarSeries/(numBarSeries+1.5));
            end

            % Calculate the width of each bar and the center point shift as in
            % makebars.m
            % Get the shifts of the bar centers.
            % In case of numBars==1, this returns 0,
            % In case of numBars==2, this returns [-1/4, 1/4],
            % In case of numBars==3, this returns [-1/3, 0, 1/3],
            % and so forth.
            % assumedBarWidth = groupWidth/numBarSeries; % assumption
            % barShift        = (barSeriesId - 0.5) * assumedBarWidth - groupWidth/2;
            % FIXME #785: The previous version of barshift lead to
            % regressions, as the bars were stacked.
            % Instead remove the calculation of barShift and add x/ybar to
            % the axis so that pgf determines it automatically.

            % From http://www.mathworks.com/help/techdoc/ref/bar.html:
            % bar(...,width) sets the relative bar width and controls the
            % separation of bars within a group. The default width is 0.8, so if
            % you do not specify X, the bars within a group have a slight
            % separation. If width is 1, the bars within a group touch one
            % another. The value of width must be a scalar.
            assumedBarWidth = groupWidth/numBarSeries; % assumption
            barWidth = getBarWidthInAbsolutUnits(h) * assumedBarWidth;

            % Bar type
            drawOptions = opts_add(drawOptions, barType);

            % Bar width
            % Relative width does not work with categorical data
            if ~isCategoricalType(get(h, 'XData'))
                drawOptions = opts_add(drawOptions, 'bar width', formatDim(barWidth, ''));
            end

            % The bar shift auto feature was introduced in pgfplots 1.13
            m2t = needsPgfplotsVersion(m2t, [1,13]);
            m2t = m2t_addAxisOption(m2t, 'bar shift auto');
        case 'stacked' % stacked plots
            % Pass option to parent axis & disallow anything but stacked plots
            % Make sure this happens exactly *once*.

            if ~m2t.axes{end}.barAddedAxisOption;
                barWidth = getBarWidthInAbsolutUnits(h);
                m2t = m2t_addAxisOption(m2t, 'bar width', formatDim(barWidth,''));
                m2t.axes{end}.barAddedAxisOption = true;
            end

            % Somewhere between pgfplots 1.5 and 1.8 and starting
            % again from 1.11, the option {x|y}bar stacked can be applied to
            % \addplot instead of the figure and thus allows to combine stacked
            % bar plots and other kinds of plots in the same axis.
            % Thus, it is advisable to use pgfplots 1.11. In older versions, the
            % plot will only contain a single bar series, but should compile fine.
            m2t = needsPgfplotsVersion(m2t, [1,11]);
            drawOptions = opts_add(drawOptions, [barType ' stacked']);
        otherwise
            error('matlab2tikz:drawBarseries', ...
                'Don''t know how to handle BarLayout ''%s''.', barlayout);
    end
end
% ==============================================================================
function [numBarSeries, barSeriesId] = getNumBarAndId(h)
    % Get number of bars series and bar series id
    prop         = switchMatOct('BarPeers', 'bargroup');
    bargroup     = get(h, prop);
    numBarSeries = numel(bargroup);

    if isHG2
        % In HG2, BarPeers are sorted in reverse order wrt HG1
        bargroup = bargroup(end:-1:1);

    elseif strcmpi(getEnvironment, 'MATLAB')
        % In HG1, h is a double but bargroup a graphic object. Cast h to a
        % graphic object
        h = handle(h);

    else
        % In Octave, the bargroup is a replicated cell array. Pick first
        if iscell(bargroup)
            bargroup = bargroup{1};
        end
    end

    % Get bar series Id
    [dummy, barSeriesId] = ismember(h, bargroup);
end
% ==============================================================================
function [m2t,str] = drawBaseline(m2t,hparent,isVertical)
    % DRAWBASELINE Draws baseline for bar and stem plots
    %
    % Notes:
    %   - In HG2, the baseline is a specific object child of a bar or stem
    %     plot. So, handleAllChildren() won't find a line in the axes to plot as
    %     the baseline.
    %   - The baseline is horizontal for vertical bar and stem plots and is
    %     vertical for horixontal barplots. The ISVERTICAL input refers to the
    %     baseline.
    %   - We do not plot baselines with a BaseValue different from 0 because
    %     pgfplots does not support shifts in the BaseValue, e.g. see #438.
    %     We either implement our own data shifting or wait for pgfplots.

    if ~exist('isVertical','var')
        isVertical = false;
    end

    str = '';
    baseValue = get(hparent, 'BaseValue');
    if isOff(get(hparent,'ShowBaseLine')) || ~isHG2() || baseValue ~= 0
        return
    end

    hBaseLine = get(hparent,'BaseLine');

    % Line options of the baseline
    [m2t, lineOptions] = getLineOptions(m2t, hparent);
    color              = get(hBaseLine, 'Color');
    [m2t, lineColor]   = getColor(m2t, hBaseLine, color, 'patch');

    drawOptions = opts_new();
    drawOptions = opts_add(drawOptions, 'forget plot');
    drawOptions = opts_add(drawOptions, 'color', lineColor);
    drawOptions = opts_merge(drawOptions, lineOptions);

    % Get data
    if isVertical
        xData = repmat(baseValue,1,2);
        yData = get(m2t.current.gca,'Ylim');
    else
        xData = get(m2t.current.gca,'Xlim');
        yData = repmat(baseValue,1,2);
    end

    [m2t, table, tableOptions] = makeTable(m2t, '', xData, '', yData);

    % Print out
    drawOpts = opts_print(drawOptions);
    tabOpts  = opts_print(tableOptions);
    str      = sprintf('\\addplot[%s] table[%s] {%s};\n', ...
                       drawOpts, tabOpts, table);
end
% ==============================================================================
function [m2t, str] = drawAreaSeries(m2t, h, custom)
    % Takes care of MATLAB's area plots.
    %
    % TODO Get rid of code duplication with 'drawAxes'.

    % Do nothing if plot is invisible
    str = '';
    if ~isVisible(h)
        return;
    end

    % Init drawOptions
    drawOptions = opts_new();

    % Get the draw options for the bars
    [m2t, drawOptions] = getPatchDrawOptions(m2t, h, drawOptions);

    if ~isfield(m2t, 'addedAreaOption') || isempty(m2t.addedAreaOption) || ~m2t.addedAreaOption
        % Add 'area style' to axes options.
        m2t = m2t_addAxisOption(m2t, 'area style');
        m2t = m2t_addAxisOption(m2t, 'stack plots', 'y');
        m2t.addedAreaOption = true;
    end

    % Toggle legend entry
    drawOptions = maybeShowInLegend(m2t.currentHandleHasLegend, drawOptions);

    % Generate the tikz table
    xData = get(h, 'XData');
    yData = get(h, 'YData');
    [m2t, table, tableOptions] = makeTable(m2t, '', xData, '', yData);

    drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
    
    % Print out
    drawOpts = opts_print(drawOptions);
    tabOpts  = opts_print(tableOptions);
    str      = sprintf('\\addplot[%s] table[%s]{%s}\n\\closedcycle;\n',...
                       drawOpts, tabOpts, table);
    %TODO: shouldn't this be "\addplot[] table[] {}" instead?
end
% ==============================================================================
function [m2t, str] = drawStemSeries(m2t, h, custom)
    [m2t, str] = drawStemOrStairSeries_(m2t, h, 'ycomb', custom);

    % TODO: handle baseplane with stem3()
    if m2t.axes{end}.is3D
        return
    end
    [m2t, baseline] = drawBaseline(m2t,h);
    str             = [str, baseline];
end
function [m2t, str] = drawStairSeries(m2t, h, custom)
    [m2t, str] = drawStemOrStairSeries_(m2t, h, 'const plot', custom);
end
function [m2t, str] = drawStemOrStairSeries_(m2t, h, plotType, custom)

    % Do nothing if plot is invisible
    str = '';
    if ~isLineVisible(h)
        return % nothing to plot!
    end

    % deal with draw options
    color = get(h, 'Color');
    [m2t, plotColor] = getColor(m2t, h, color, 'patch');

    [m2t, lineOptions]   = getLineOptions(m2t, h);
    [m2t, markerOptions] = getMarkerOptions(m2t, h);

    drawOptions = opts_new();
    drawOptions = opts_add(drawOptions, plotType);
    drawOptions = opts_add(drawOptions, 'color', plotColor);
    drawOptions = opts_merge(drawOptions, lineOptions, markerOptions);

    % Toggle legend entry
    drawOptions = maybeShowInLegend(m2t.currentHandleHasLegend, drawOptions);
    drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);

    drawOpts = opts_print(drawOptions);

    % Generate the tikz table
    xData = get(h, 'XData');
    yData = get(h, 'YData');
    if m2t.axes{end}.is3D
        % TODO: account for hgtransform
        zData = get(h, 'ZData');
        [m2t, table, tableOptions] = makeTable(m2t, '', xData, '', yData, '', zData);
        % Print out
        tabOpts  = opts_print(tableOptions);
        str = sprintf('\\addplot3 [%s]\n table[%s] {%s};\n ', ...
                         drawOpts, tabOpts, table);
    else
        [m2t, table, tableOptions] = makeTable(m2t, '', xData, '', yData);
        % Print out
        tabOpts  = opts_print(tableOptions);
        str = sprintf('\\addplot[%s] table[%s] {%s};\n', ...
                         drawOpts, tabOpts, table);
    end

end
% ==============================================================================
function [m2t, str] = drawQuiverGroup(m2t, h, custom)
    % Takes care of MATLAB's quiver plots.
    str = '';

    [x,y,z,u,v,w] = getAndRescaleQuivers(m2t,h);
    is3D = m2t.axes{end}.is3D;

    % prepare output
    if is3D
        name = 'addplot3';
    else % 2D plotting
        name = 'addplot';
    end

    variables = {'x', 'y', 'z', 'u', 'v', 'w'};
    data = NaN(numel(x),6);
    data(:,1) = x;
    data(:,2) = y;
    data(:,3) = z;
    data(:,4) = u;
    data(:,5) = v;
    data(:,6) = w;

    if ~is3D
        data(:,[3 6]) = []; % remove Z-direction
        variables([3 6]) = [];
    end

    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % gather the arrow options
    showArrowHead = get(h, 'ShowArrowHead');
    if ~isLineVisible(h)  && ~showArrowHead
        return
    end

    plotOptions = opts_new();
    if showArrowHead
        plotOptions = opts_add(plotOptions, '-Straight Barb');
        signalDependency(m2t, 'tikzlibrary', 'arrows.meta');
    else
        plotOptions = opts_add(plotOptions, '-');
    end

    % Append the arrow style to the TikZ options themselves.
    color = get(h, 'Color');
    [m2t, lineOptions] = getLineOptions(m2t, h);
    [m2t, arrowcolor] = getColor(m2t, h, color, 'patch');
    plotOptions = opts_add(plotOptions, 'color', arrowcolor);
    plotOptions = opts_merge(plotOptions, lineOptions);

    % Define the quiver settings
    quiverOptions = opts_new();
    quiverOptions = opts_add(quiverOptions, 'u', '\thisrow{u}');
    quiverOptions = opts_add(quiverOptions, 'v', '\thisrow{v}');
    if is3D
        quiverOptions = opts_add(quiverOptions, 'w', '\thisrow{w}');
        arrowLength = '{sqrt((\thisrow{u})^2+(\thisrow{v})^2+(\thisrow{w})^2)}';
    else
        arrowLength = '{sqrt((\thisrow{u})^2+(\thisrow{v})^2)}';
    end
    plotOptions = opts_add(plotOptions, 'point meta', arrowLength);
    plotOptions = opts_add(plotOptions, 'point meta min', '0');

    if showArrowHead
        arrowHeadOptions = opts_new();

        % In MATLAB (HG1), the arrow head is constructed to have an angle of
        % approximately 18.263 degrees in 2D as can be derived from the
        % |quiver| function.
        % In 3D, the angle is no longer constant but it is approximately
        % the same as for 2D quiver plots. So let's make our life easy.
        % |test/examples/example_quivers.m| covers the calculations.
        arrowHeadOptions = opts_add(arrowHeadOptions, 'angle''', '18.263');

        %TODO: scale the arrows more rigorously to match MATLAB behavior
        % Currently, this is quite hard to do, since the size of the arrows
        % is defined in pgfplots in absolute units (here we specify that those
        % should be scaled up/down according to the data), while the data itself
        % is in axis coordinates (or some scaled variant). I.e. we need the
        % physical dimensions of the axis to compute the correct scaling!
        %
        % There is a "MaxHeadSize" property that plays a role.
        % MaxHeadSize is said to be relative to the length of the quiver in the
        % MATLAB documentation. However, in practice, there seems to be a SQRT
        % involved somewhere (e.g. if u.^2 + v.^2 == 2, all MHS values >
        % 1/sqrt(2) are capped to 1/sqrt(2)).
        %
        % NOTE: `set(h, 'MaxHeadSize')` is bugged in HG1 (not in HG2 or Octave)
        % according to http://www.mathworks.com/matlabcentral/answers/96754

        userInfo(m2t, ['Please change the "arrowHeadSize" option', ...
            ' if the size of the arrows is incorrect.']);
        arrowHeadSize = sprintf(m2t.ff, abs(m2t.args.arrowHeadSize));

        % Write out the actual scaling for TikZ.
        % `\pgfplotspointsmetatransformed` is in the range [0, 1000], so
        % divide by this span (as is done in the pgfplots manual) to normalize
        % the arrow head size. First divide to avoid overflows.
        arrowHeadOptions = opts_add(arrowHeadOptions, 'scale', ...
            ['{' arrowHeadSize '/1000*\pgfplotspointmetatransformed}']);

        headStyle = ['-{Straight Barb[' opts_print(arrowHeadOptions) ']}'];
        quiverOptions = opts_add(quiverOptions, 'every arrow/.append style', ...
                                 ['{' headStyle '}']);
    end
    plotOptions = opts_addSubOpts(plotOptions, 'quiver', quiverOptions);
    plotOptions = opts_append_userdefined(plotOptions, custom.extraOptions);

    [m2t, table, tableOptions] = makeTable(m2t, variables, data);

    % Print out
    plotOpts = opts_print(plotOptions);
    tabOpts  = opts_print(tableOptions);
    str      = sprintf('\\%s[%s]\n table[%s] {%s};\n', ...
                       name, plotOpts, tabOpts, table);
end
% ==============================================================================
function [x,y,z,u,v,w] = getAndRescaleQuivers(m2t, h)
    % get and rescale the arrows from a quivergroup object
    x = get(h, 'XData');
    y = get(h, 'YData');
    z = getOrDefault(h, 'ZData', []);

    u = get(h, 'UData');
    v = get(h, 'VData');
    w = getOrDefault(h, 'WData', []);

    is3D = m2t.axes{end}.is3D;
    if ~is3D
        z = 0;
        w = 0;
    end

    % MATLAB uses a scaling algorithm to determine the size of the arrows.
    % Before R2014b, the processed coordinates were available. This is no longer
    % the case, so we have to re-implement it. In MATLAB it is implemented in
    % the |quiver3|  (and |quiver|) function.
    if any(size(x)==1)
        nX = sqrt(numel(x)); nY = nX;
    else
        [nY, nX] = size(x);
    end
    range  = @(xyzData)(max(xyzData(:)) - min(xyzData(:)));
    euclid = @(x,y,z)(sqrt(x.^2 + y.^2 + z.^2));
    dx = range(x)/nX;
    dy = range(y)/nY;
    dz = range(z)/max(nX,nY);
    dd = euclid(dx, dy, dz);
    if dd > 0
        vectorLength = euclid(u/dd,v/dd,w/dd);
        maxLength = max(vectorLength(:));
    else
        maxLength = 1;
    end
    if isOn(getOrDefault(h, 'AutoScale', 'on'))
        scaleFactor = getOrDefault(h,'AutoScaleFactor', 0.9) / maxLength;
    else
        scaleFactor = 1;
    end
    x = x(:).'; u = u(:).'*scaleFactor;
    y = y(:).'; v = v(:).'*scaleFactor;
    z = z(:).'; w = w(:).'*scaleFactor;
end
% ==============================================================================
function [m2t, str] = drawErrorBars(m2t, h, custom)
    % Takes care of MATLAB/Octave's error bar plots.
    [m2t, str] = drawLine(m2t, h, custom);
    % Even though this only calls |drawLine|, let's keep this wrapper
    % such that the code is easier to read where it is called.
end
% ==============================================================================
function [xDeviations] = getXDeviations(h)
    % Retrieves left/right uncertainty data

    rightDev = getOrDefault(h, 'XPositiveDelta', []);
    leftDev = getOrDefault(h, 'XNegativeDelta', []);

    xDeviations = [rightDev(:), leftDev(:)];
end
% ==============================================================================
function [yDeviations] = getYDeviations(h)
    % Retrieves upper/lower uncertainty data

    upDev = getOrDefault(h, 'YPositiveDelta', []);
    loDev = getOrDefault(h, 'YNegativeDelta', []);

    if isempty(upDev)
      upDev = getOrDefault(h, 'UData', []);
      loDev = getOrDefault(h, 'LData', []);
    end

    yDeviations = [upDev(:), loDev(:)];
end
% ==============================================================================
function [m2t, str] = drawEllipse(m2t, handle, custom)
    % Takes care of MATLAB's ellipse annotations.

    drawOptions = opts_new();

    p = get(handle,'position');
    radius = p([3 4]) / 2;
    center = p([1 2]) + radius;

    color = get(handle, 'Color');
    [m2t, xcolor] = getColor(m2t, handle, color, 'patch');
    [m2t, lineOptions] = getLineOptions(m2t, handle);

    filling = get(handle, 'FaceColor');

    % Has a filling?
    if isNone(filling)
        drawOptions = opts_add(drawOptions, xcolor);
        drawCommand = '\draw';
    else
        [m2t, xcolorF] = getColor(m2t, handle, filling, 'patch');
        drawOptions = opts_add(drawOptions, 'draw', xcolor);
        drawOptions = opts_add(drawOptions, 'fill', xcolorF);

        drawCommand = '\filldraw';
    end
    drawOptions = opts_merge(drawOptions, lineOptions);
    drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
    
    opt = opts_print(drawOptions);

    str = sprintf('%s [%s] (axis cs:%g,%g) ellipse [x radius=%g, y radius=%g];\n', ...
        drawCommand, opt, center, radius);
end
% ==============================================================================
function [m2t, str] = drawTextarrow(m2t, handle, custom)
    % Takes care of MATLAB's textarrow annotations.

    % handleAllChildren to draw the arrow
    [m2t, str] = handleAllChildren(m2t, handle);

    % handleAllChildren ignores the text, unless hidden strings are shown
    if ~m2t.args.showHiddenStrings
        child = findall(handle, 'type', 'text');
        [m2t, str{end+1}] = drawText(m2t, child, custom);
    end
end
% ==============================================================================
function [m2t, drawOptions] = getPatchDrawOptions(m2t, h, drawOptions)
    % Determines the reoccurring draw options usually applied when drawing
    % a patch/area/bar. These include EdgeColor, LineType, FaceColor/Alpha

    % Get object for color;
    if ~isempty(allchild(h))
        % quite oddly, before MATLAB R2014b this value is stored in a child
        % patch and not in the object itself
        obj = allchild(h);
    else % R2014b and newer
        obj = h;
    end

    % Get the object type
    type = get(h, 'Type');

    % Face Color (inside of area)
    faceColor          = get(obj, 'FaceColor');
    [m2t, drawOptions] = setColor(m2t, h, drawOptions, 'fill', faceColor, 'none');

    % FaceAlpha (Not applicable for MATLAB2014a/b)
    faceAlpha = getOrDefault(h, 'FaceAlpha', 'none');
    if ~isNone(faceColor) && isnumeric(faceAlpha) && faceAlpha ~= 1.0
        drawOptions = opts_add(drawOptions, 'fill opacity', sprintf(m2t.ff,faceAlpha));
    end

    % Define linestyle
    [lineStyle, isDefaultLS] = getAndCheckDefault(type, h, 'LineStyle', '-');
    if isNone(lineStyle)
        drawOptions = opts_add(drawOptions, 'draw', 'none');
    elseif ~isDefaultLS
        drawOptions = opts_add(drawOptions, translateLineStyle(lineStyle));
    end

    % Check for the edge color. Only plot it if it is different from the
    % face color and if there is a linestyle
    edgeColor = get(h, 'EdgeColor');
    if ~isNone(lineStyle) && ~isNone(edgeColor) && ~strcmpi(edgeColor,faceColor)
        [m2t, drawOptions] = setColor(m2t, h, drawOptions, 'draw', edgeColor, 'none');
    end

    % Add 'area legend' to the options as otherwise the legend indicators
    % will just highlight the edges.
    if strcmpi(type, 'bar') || strcmpi(type, 'histogram')
        drawOptions = opts_add(drawOptions, 'area legend');
    end
end
% ==============================================================================
function out = linearFunction(X, Y)
    % Return the linear function that goes through (X[1], Y[1]), (X[2], Y[2]).
    out = @(x) (Y(2,:)*(x-X(1)) + Y(1,:)*(X(2)-x)) / (X(2)-X(1));
end
% ==============================================================================
function matlabColormap = pgfplots2matlabColormap(points, rgb, numColors)
    % Translates a Pgfplots colormap to a MATLAB color map.
    matlabColormap = zeros(numColors, 3);
    % Point indices between which to interpolate.
    I = [1, 2];
    f = linearFunction(points(I), rgb(I,:));
    for k = 1:numColors
        x = (k-1)/(numColors-1) * points(end);
        if x > points(I(2))
            I = I + 1;
            f = linearFunction(points(I), rgb(I,:));
        end
        matlabColormap(k,:) = f(x);
    end
end
% ==============================================================================
function pgfplotsColormap = matlab2pgfplotsColormap(m2t, matlabColormap, name)
    % Translates a MATLAB color map into a Pgfplots colormap.

    if nargin < 3 || isempty(name), name = 'mymap'; end

    % First check if we could use a default Pgfplots color map.
    % Unfortunately, MATLAB and Pgfplots color maps will never exactly coincide
    % except to the most simple cases such as blackwhite. This is because of a
    % slight incompatibility of Pgfplots and MATLAB colormaps:
    % In MATLAB, indexing goes from 1 through 64, whereas in Pgfplots you can
    % specify any range, the default ones having something like
    % (0: red, 1: yellow, 2: blue).
    % To specify this exact color map in MATLAB, one would have to put 'red' at
    % 1, blue at 64, and yellow in the middle of the two, 32.5 that is.
    % Not really sure how MATLAB rounds here: 32, 33? Anyways, it will be
    % slightly off and hence not match the Pgfplots color map.
    % As a workaround, build the MATLAB-formatted colormaps of Pgfplots default
    % color maps, and check if matlabColormap is close to it. If yes, take it.

    % For now, comment out the color maps which haven't landed yet in Pgfplots.
    pgfmaps = { %struct('name', 'colormap/autumn', ...
        %       'points', [0,1], ...
        %       'values', [[1,0,0];[1,1,0]]), ...
        %struct('name', 'colormap/bled', ...
        %       'points', 0:6, ...
        %       'values', [[0,0,0];[43,43,0];[0,85,0];[0,128,128];[0,0,170];[213,0,213];[255,0,0]]/255), ...
        %struct('name', 'colormap/bright', ...
        %       'points', 0:7, ...
        %       'values', [[0,0,0];[78,3,100];[2,74,255];[255,21,181];[255,113,26];[147,213,114];[230,255,0];[255,255,255]]/255), ...
        %struct('name', 'colormap/bone', ...
        %       'points', [0,3,6,8], ...
        %       'values', [[0,0,0];[84,84,116];[167,199,199];[255,255,255]]/255), ...
        %struct('name', 'colormap/cold', ...
        %       'points', 0:3, ...
        %       'values', [[0,0,0];[0,0,1];[0,1,1];[1,1,1]]), ...
        %struct('name', 'colormap/copper', ...
        %       'points', [0,4,5], ...
        %       'values', [[0,0,0];[255,159,101];[255,199,127]]/255), ...
        %struct('name', 'colormap/copper2', ...
        %       'points', 0:4, ...
        %       'values', [[0,0,0];[68,62,63];[170,112,95];[207,194,138];[255,255,255]]/255), ...
        %struct('name', 'colormap/hsv', ...
        %       'points', 0:6, ...
        %       'values', [[1,0,0];[1,1,0];[0,1,0];[0,1,1];[0,0,1];[1,0,1];[1,0,0]]), ...
        struct('name', 'colormap/hot', ...
        'points', 0:3, ...
        'values', [[0,0,1];[1,1,0];[1,0.5,0];[1,0,0]]), ... % TODO check this
        struct('name', 'colormap/hot2', ...
        'points', [0,3,6,8], ...
        'values', [[0,0,0];[1,0,0];[1,1,0];[1,1,1]]), ...
        struct('name', 'colormap/jet', ...
        'points', [0,1,3,5,7,8], ...
        'values', [[0,0,128];[0,0,255];[0,255,255];[255,255,0];[255,0,0];[128,0,0]]/255), ...
        struct('name', 'colormap/blackwhite', ...
        'points', [0,1], ...
        'values', [[0,0,0];[1,1,1]]), ...
        struct('name', 'colormap/bluered', ...
        'points', 0:5, ...
        'values', [[0,0,180];[0,255,255];[100,255,0];[255,255,0];[255,0,0];[128,0,0]]/255), ...
        struct('name', 'colormap/cool', ...
        'points', [0,1,2], ...
        'values', [[255,255,255];[0,128,255];[255,0,255]]/255), ...
        struct('name', 'colormap/greenyellow', ...
        'points', [0,1], ...
        'values', [[0,128,0];[255,255,0]]/255), ...
        struct('name', 'colormap/redyellow', ...
        'points', [0,1], ...
        'values', [[255,0,0];[255,255,0]]/255), ...
        struct('name', 'colormap/violet', ...
        'points', [0,1,2], ...
        'values', [[25,25,122];[255,255,255];[238,140,238]]/255) ...
        };

    % The tolerance is a subjective matter of course.
    % Some figures:
    %    * The norm-distance between MATLAB's gray and bone is 6.8e-2.
    %    * The norm-distance between MATLAB's jet and Pgfplots's jet is 2.8e-2.
    %    * The norm-distance between MATLAB's hot and Pgfplots's hot2 is 2.1e-2.
    tol = 5.0e-2;

    for map = pgfmaps
        numColors = size(matlabColormap, 1);
        mmap = pgfplots2matlabColormap(map{1}.points, map{1}.values, numColors);
        alpha = norm(matlabColormap - mmap) / sqrt(numColors);
        if alpha < tol
            userInfo(m2t, 'Found %s to be a pretty good match for your color map (||diff||=%g).', ...
                map{1}.name, alpha);
            pgfplotsColormap = map{1}.name;
            return
        end
    end

    % Build a custom color map.
    % Loop over the data, stop at each spot where the linear
    % interpolation is interrupted, and set a color mark there.
    m = size(matlabColormap, 1);
    steps = [1, 2];
    % A colormap with a single color is valid in MATLAB but an error in
    % pgfplots. Repeating the color produces the desired effect in this
    % case.
    if m==1
        colors=[matlabColormap(1,:);matlabColormap(1,:)];
    else
        colors = [matlabColormap(1,:); matlabColormap(2,:)];
        f = linearFunction(steps, colors);
        k = 3;
        while k <= m
            if norm(matlabColormap(k,:) - f(k)) > 1.0e-10
                % Add the previous step to the color list
                steps(end) = k-1;
                colors(end,:) = matlabColormap(k-1,:);
                steps = [steps, k];
                colors = [colors; matlabColormap(k,:)];
                f = linearFunction(steps(end-1:end), colors(end-1:end,:));
            end
            k = k+1;
        end
        steps(end) = m;
        colors(end,:) = matlabColormap(m,:);
    end

    % Get it in Pgfplots-readable form.
    unit = 'pt';
    colSpecs = cell(length(steps), 1);
    for k = 1:length(steps)
        x = steps(k)-1;
        colSpecs{k} = sprintf('rgb(%d%s)=(%g,%g,%g)', x, unit, colors(k,:));
    end
    pgfplotsColormap = sprintf('colormap={%s}{[1%s] %s}',name, unit, join(m2t, colSpecs, '; '));
end
% ==============================================================================
function [m2t, fontStyle] = getFontStyle(m2t, handle)
    fontStyle = '';
    if strcmpi(get(handle, 'FontWeight'),'Bold')
        fontStyle = sprintf('%s\\bfseries',fontStyle);
    end
    if strcmpi(get(handle, 'FontAngle'), 'Italic')
        fontStyle = sprintf('%s\\itshape',fontStyle);
    end
    if ~all(get(handle, 'Color')==0)
        color = get(handle, 'Color');
        [m2t, col] = getColor(m2t, handle, color, 'patch');
        fontStyle = sprintf('%s\\color{%s}', fontStyle, col);
    end
    if m2t.args.strictFontSize
        fontSize  = get(handle,'FontSize');
        fontUnits = matlab2texUnits(get(handle,'FontUnits'), 'pt');
        fontStyle = sprintf('\\fontsize{%d%s}{1em}%s\\selectfont',fontSize,fontUnits,fontStyle);
    else
        % don't try to be smart and "translate" MATLAB font sizes to proper LaTeX
        % ones: it cannot be done. LaTeX uses semantic sizes (e.g. \small)
        % whose actual dimensions depend on the document style, context, ...
    end

    if ~isempty(fontStyle)
        fontStyle = opts_add(opts_new, 'font', fontStyle);
    else
        fontStyle = opts_new();
    end
end
% ==============================================================================
function axisOptions = getColorbarOptions(m2t, handle)

    % begin collecting axes options
    axisOptions = opts_new();
    cbarStyleOptions = opts_new();

    [cbarTemplate, cbarStyleOptions] = getColorbarPosOptions(handle, ...
                                                cbarStyleOptions);

    % axis label and direction
    if isHG2
        % VERSION: Starting from R2014b there is only one field `label`.
        % The colorbar's position determines, if it should be a x- or y-label.

        if strcmpi(cbarTemplate, 'horizontal')
            labelOption = 'xlabel';
        else
            labelOption = 'ylabel';
        end
        [m2t, cbarStyleOptions] = getLabel(m2t, handle, cbarStyleOptions, labelOption);

        % direction
        dirString = get(handle, 'Direction');
        if ~strcmpi(dirString, 'normal') % only if not 'normal'
            if strcmpi(cbarTemplate, 'horizontal')
                dirOption = 'x dir';
            else
                dirOption = 'y dir';
            end
            cbarStyleOptions = opts_add(cbarStyleOptions, dirOption, dirString);
        end

        % TODO HG2: colorbar ticks and colorbar tick labels

    else
        % VERSION: Up to MATLAB R2014a and OCTAVE
        [m2t, xo] = getAxisOptions(m2t, handle, 'x');
        [m2t, yo] = getAxisOptions(m2t, handle, 'y');
        xyo = opts_merge(xo, yo);
        xyo = opts_remove(xyo, 'xmin','xmax','xtick','ymin','ymax','ytick');

        cbarStyleOptions = opts_merge(cbarStyleOptions, xyo);
    end

    % title
    [m2t, cbarStyleOptions] = getTitle(m2t, handle, cbarStyleOptions);

    if m2t.args.strict
        % Sampled colors.
        numColors = size(m2t.current.colormap, 1);
        axisOptions = opts_add(axisOptions, 'colorbar sampled');
        cbarStyleOptions = opts_add(cbarStyleOptions, 'samples', ...
            sprintf('%d', numColors+1));

        if ~isempty(cbarTemplate)
            userWarning(m2t, ...
-               'Pgfplots cannot deal with more than one colorbar option yet.');
            %FIXME: can we get sampled horizontal color bars to work?
            %FIXME: sampled colorbars should be inferred, not by using strict!
        end
    end

    % Merge them together in axisOptions.
    axisOptions = opts_add(axisOptions, strtrim(['colorbar ', cbarTemplate]));

    if ~isempty(cbarStyleOptions)
        axisOptions = opts_addSubOpts(axisOptions, ...
                                   'colorbar style', cbarStyleOptions);
    end

    % do _not_ handle colorbar's children
end
% ==============================================================================
function [cbarTemplate, cbarStyleOptions] = getColorbarPosOptions(handle, cbarStyleOptions)
    % set position, ticks etc. of a colorbar
    loc = get(handle, 'Location');
    cbarTemplate = '';

    switch lower(loc) % case insensitive (MATLAB: CamelCase, Octave: lower case)
        case 'north'
            cbarTemplate = 'horizontal';
            cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
                '{(0.5,0.97)}');
            cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor',...
                'north');
            cbarStyleOptions = opts_add(cbarStyleOptions,...
                'xticklabel pos', 'lower');
            cbarStyleOptions = opts_add(cbarStyleOptions, 'width',...
                '0.97*\pgfkeysvalueof{/pgfplots/parent axis width}');
        case 'south'
            cbarTemplate = 'horizontal';
            cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
                '{(0.5,0.03)}');
            cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor', ...
                'south');
            cbarStyleOptions = opts_add(cbarStyleOptions, ...
                'xticklabel pos','upper');
            cbarStyleOptions = opts_add(cbarStyleOptions, 'width',...
                '0.97*\pgfkeysvalueof{/pgfplots/parent axis width}');
        case 'east'
            cbarTemplate = 'right';
            cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
                '{(0.97,0.5)}');
            cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor', ...
                'east');
            cbarStyleOptions = opts_add(cbarStyleOptions, ...
                'xticklabel pos','left');
            cbarStyleOptions = opts_add(cbarStyleOptions, 'width',...
                '0.97*\pgfkeysvalueof{/pgfplots/parent axis width}');
        case 'west'
            cbarTemplate = 'left';
            cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
                '{(0.03,0.5)}');
            cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor',...
                'west');
            cbarStyleOptions = opts_add(cbarStyleOptions,...
                'xticklabel pos', 'right');
            cbarStyleOptions = opts_add(cbarStyleOptions, 'width',...
                '0.97*\pgfkeysvalueof{/pgfplots/parent axis width}');
        case 'eastoutside'
            %cbarTemplate = 'right';
        case 'westoutside'
            cbarTemplate = 'left';
        case 'northoutside'
            % TODO move to top
            cbarTemplate = 'horizontal';
            cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
                '{(0.5,1.03)}');
            cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor',...
                'south');
            cbarStyleOptions = opts_add(cbarStyleOptions,...
                'xticklabel pos', 'upper');
        case 'southoutside'
            cbarTemplate = 'horizontal';
        case 'manual'
            origUnits = get(handle,'Units');
            assocAxes = get(handle,'Axes');
            origAxesUnits = get(assocAxes,'Units');
            set(handle,'Units','centimeters');        % Make sure we have
            set(assocAxes,'Units','centimeters');     % same units
            cbarDim = pos2dims(get(handle,'Position'));
            cbarAxesDim = pos2dims(get(assocAxes,'Position'));
            set(handle,'Units',origUnits);            % Restore original
            set(assocAxes,'Units',origAxesUnits);     % units

            center = @(dims) (dims.left + dims.right)/2;
            centerCbar = center(cbarDim);
            centerAxes = center(cbarAxesDim);

            % Cases of colorbar axis locations (in or out) depending on center
            % of colorbar relative to the center it's associated axes.
            % According to matlab manual (R2016a) colorbars with Location 'manual'
            % can only be vertical.
            axisLoc = getOrDefault(handle, 'AxisLocation', 'out');
            if centerCbar < centerAxes
                if strcmp(axisLoc,'in')
                    cbarTemplate = 'right';
                else
                    cbarTemplate = 'left';
                end
            else
                if strcmp(axisLoc,'in')
                    cbarTemplate = 'left';
                else
                    cbarTemplate = 'right';
                end
            end

            % Using positions relative to associated axes
            calcRelPos = @(pos1,pos2,ext2) (pos1-pos2)/ext2; 
            cbarRelPosX = calcRelPos(cbarDim.left,cbarAxesDim.left,cbarAxesDim.width);
            cbarRelPosY = calcRelPos(cbarDim.bottom,cbarAxesDim.bottom,cbarAxesDim.height);
            cbarRelHeight = cbarDim.height/cbarAxesDim.height;
            
            cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor',...
                'south west');
            cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
                ['{(' formatDim(cbarRelPosX) ','...
                      formatDim(cbarRelPosY) ')}']);
            cbarStyleOptions = opts_add(cbarStyleOptions, 'height',...
                [formatDim(cbarRelHeight),...
                '*\pgfkeysvalueof{/pgfplots/parent axis height}']);

        otherwise
            error('matlab2tikz:getColorOptions:unknownLocation',...
                'getColorbarOptions: Unknown ''Location'' %s.', loc)
    end
end
% ==============================================================================
function [m2t, xcolor] = getColor(m2t, handle, color, mode)
    % Handles MATLAB colors and makes them available to TikZ.
    % This includes translation of the color value as well as explicit
    % definition of the color if it is not available in TikZ by default.
    %
    % The variable 'mode' essentially determines what format 'color' can
    % have. Possible values are (as strings) 'patch' and 'image'.

    % check if the color is straight given in rgb
    % -- notice that we need the extra NaN test with respect to the QUIRK
    %    below
    if isRGBTuple(color)
        % everything alright: rgb color here
        [m2t, xcolor] = rgb2colorliteral(m2t, color);
    else
        switch lower(mode)
            case 'patch'
                [m2t, xcolor] = patchcolor2xcolor(m2t, color, handle);
            case 'image'

                m = size(color,1);
                n = size(color,2);
                xcolor = cell(m, n);

                if ndims(color) == 3
                    for i = 1:m
                        for j = 1:n
                            [m2t, xc] = rgb2colorliteral(m2t, color(i,j, :));
                            xcolor{i, j} = xc;
                        end
                    end
                elseif ndims(color) <= 2
                    [m2t, colorindex] = cdata2colorindex(m2t, color, handle);
                    for i = 1:m
                        for j = 1:n
                            [m2t, xc] = rgb2colorliteral(m2t, m2t.current.colormap(colorindex(i,j), :));
                            xcolor{i, j} = xc;
                        end
                    end
                else
                    error('matlab2tikz:getColor:image:colorDims',...
                        'Image color data cannot have more than 3 dimensions');
                end
            otherwise
                error(['matlab2tikz:getColor', ...
                    'Argument ''mode'' has illegal value ''%s''.'], ...
                    mode);
        end
    end
end
% ==============================================================================
function [m2t, xcolor] = patchcolor2xcolor(m2t, color, patchhandle)
    % Transforms a color of the edge or the face of a patch to an xcolor literal.
    if isnumeric(color)
        [m2t, xcolor] = rgb2colorliteral(m2t, color);
    elseif ischar(color)
        switch color
            case 'flat'
                cdata  = getCDataWithFallbacks(patchhandle);
                color1 = cdata(1,1);
                % RGB cdata
                if ndims(cdata) == 3 && all(size(cdata) == [1,1,3])
                    [m2t,xcolor] = rgb2colorliteral(m2t, cdata);
                    % All same color
                elseif all(isnan(cdata) | abs(cdata-color1)<1.0e-10)
                    [m2t, colorindex] = cdata2colorindex(m2t, color1, patchhandle);
                    [m2t, xcolor] = rgb2colorliteral(m2t, m2t.current.colormap(colorindex, :));
                else
                    % Don't return anything meaningful and count on the caller
                    % to make something of it.
                    xcolor = [];
                end

            case 'auto'
                try
                    color = get(patchhandle, 'Color');
                catch
                    % From R2014b use an undocumented property if Color is
                    % not present
                    color = get(patchhandle, 'AutoColor');
                end
                [m2t, xcolor] = rgb2colorliteral(m2t, color);

            case 'none'
                % Before, we used to throw an error here. However, probably this
                % is not necessary and actually harmful (#739).
                xcolor = 'none';

            otherwise
                error('matlab2tikz:anycolor2rgb:UnknownColorModel',...
                'Don''t know how to handle the color model ''%s''.',color);
        end
    else
        error('patchcolor2xcolor:illegalInput', ...
            'Input argument ''color'' not a string or numeric.');
    end
end
% ==============================================================================
function cdata = getCDataWithFallbacks(patchhandle)
    % Looks for CData at different places
    cdata = getOrDefault(patchhandle, 'CData', []);

    if isempty(cdata) || ~isnumeric(cdata)
        child = allchild(patchhandle);
        cdata = get(child, 'CData');
    end
    if isempty(cdata) || ~isnumeric(cdata)
        % R2014b+: CData is implicit by the ordering of the siblings
        siblings = allchild(get(patchhandle, 'Parent'));
        cdata = find(siblings(end:-1:1)==patchhandle);
    end
end
% ==============================================================================
function [m2t, colorindex] = cdata2colorindex(m2t, cdata, imagehandle)
    % Transforms a color in CData format to an index in the color map.
    % Only does something if CDataMapping is 'scaled', really.

    if ~isnumeric(cdata) && ~islogical(cdata)
        error('matlab2tikz:cdata2colorindex:unknownCDataType',...
            'Don''t know how to handle CData ''%s''.',cdata);
    end

    axeshandle = m2t.current.gca;

    % -----------------------------------------------------------------------
    % For the following, see, for example, the MATLAB help page for 'image',
    % section 'Image CDataMapping'.
    try
        mapping = get(imagehandle, 'CDataMapping');
    catch
        mapping = 'scaled';
    end
    switch mapping
        case 'scaled'
            % need to scale within clim
            % see MATLAB's manual page for caxis for details
            clim = get(axeshandle, 'clim');
            m = size(m2t.current.colormap, 1);
            colorindex = zeros(size(cdata));
            idx1 = cdata <= clim(1);
            idx2 = cdata >= clim(2);
            idx3 = ~idx1 & ~idx2;
            colorindex(idx1) = 1;
            colorindex(idx2) = m;
            % cdata may be of type uint8. Convert to double to avoid
            % getting binary indices
            colorindex(idx3) = fix(double(cdata(idx3)-clim(1)) / (clim(2)-clim(1)) *m) ...
                + 1;
        case 'direct'
            % direct index
            colorindex = cdata;

        otherwise
            error('matlab2tikz:anycolor2rgb:unknownCDataMapping',...
                'Unknown CDataMapping ''%s''.',cdatamapping);
    end
end
% ==============================================================================
function [m2t, key, legendOpts] = getLegendOpts(m2t, handle)
    lStyle = opts_new();

    lStyle = getLegendPosition(m2t, handle, lStyle);
    lStyle = getLegendOrientation(m2t, handle, lStyle);
    lStyle = getLegendEntryAlignment(m2t, handle, lStyle);

    % If the plot has 'legend boxoff', we have the 'not visible'
    % property, so turn off line and background fill.
    if ~isVisible(handle) || isOff(get(handle,'box'))
        lStyle = opts_add(lStyle, 'fill', 'none');
        lStyle = opts_add(lStyle, 'draw', 'none');
    else
        % handle colors
        [edgeColor, isDfltEdge] = getAndCheckDefault('Legend', handle, ...
                                                     'EdgeColor', [1 1 1]);
        if isNone(edgeColor)
            lStyle = opts_add(lStyle, 'draw', 'none');

        elseif ~isDfltEdge
            [m2t, col] = getColor(m2t, handle, edgeColor, 'patch');
            lStyle = opts_add(lStyle, 'draw', col);
        end

        [fillColor, isDfltFill] = getAndCheckDefault('Legend', handle, ...
                                                     'Color', [1 1 1]);
        if isNone(fillColor)
            lStyle = opts_add(lStyle, 'fill', 'none');

        elseif ~isDfltFill
            [m2t, col] = getColor(m2t, handle, fillColor, 'patch');
            lStyle = opts_add(lStyle, 'fill', col);
        end
    end
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    key = 'legend style';
    legendOpts = opts_print(lStyle);
    legendOpts = ['{', legendOpts, '}'];
    %TODO: just pass out the `lStyle` instead of `legendOpts`
end
% ==============================================================================
function [lStyle] = getLegendOrientation(m2t, handle, lStyle)
    % handle legend orientation
    ori = get(handle, 'Orientation');
    switch lower(ori)
        case 'horizontal'
            numLegendEntries = sprintf('%d',length(get(handle, 'String')));
            lStyle = opts_add(lStyle, 'legend columns', numLegendEntries);

        case 'vertical'
            % Use default.
        otherwise
            userWarning(m2t, [' Unknown legend orientation ''',ori,'''' ...
                '. Choosing default (vertical).']);
    end
end
% ==============================================================================
function [lStyle] = getLegendPosition(m2t, handle, lStyle)
    % handle legend location
    % #COMPLEX: just a big switch-case
    loc  = get(handle, 'Location');
    dist = 0.03;  % distance to to axes in normalized coordinates
    % MATLAB(R)'s keywords are camel cased (e.g., 'NorthOutside'), in Octave
    % small cased ('northoutside'). Hence, use lower() for uniformity.
    switch lower(loc)
        case 'northeast'
            return % don't do anything in this (default) case
        case 'northwest'
            position = [dist, 1-dist];
            anchor   = 'north west';
        case 'southwest'
            position = [dist, dist];
            anchor   = 'south west';
        case 'southeast'
            position = [1-dist, dist];
            anchor   = 'south east';
        case 'north'
            position = [0.5, 1-dist];
            anchor   = 'north';
        case 'east'
            position = [1-dist, 0.5];
            anchor   = 'east';
        case 'south'
            position = [0.5, dist];
            anchor   = 'south';
        case 'west'
            position = [dist, 0.5];
            anchor   = 'west';
        case 'northoutside'
            position = [0.5, 1+dist];
            anchor = 'south';
        case 'southoutside'
            position = [0.5, -dist];
            anchor = 'north';
        case 'eastoutside'
            position = [1+dist, 0.5];
            anchor = 'west';
        case 'westoutside'
            position = [-dist, 0.5];
            anchor = 'east';
        case 'northeastoutside'
            position = [1+dist, 1];
            anchor = 'north west';
        case 'northwestoutside'
            position = [-dist, 1];
            anchor = 'north east';
        case 'southeastoutside'
            position = [1+dist, 0];
            anchor = 'south west';
        case 'southwestoutside'
            position = [-dist, 0];
            anchor = 'south east';
        case 'none'
            legendPos = get(handle, 'Position');
            unit = get(handle, 'Units');
            if isequal(unit, 'normalized')
                position = legendPos(1:2);
            else
                % Calculate where the legend is located w.r.t. the axes.
                axesPos = get(m2t.current.gca, 'Position');
                axesUnit = get(m2t.current.gca, 'Units');
                % Convert to legend unit
                axesPos = convertUnits(axesPos, axesUnit, unit);
                % By default, the axes position is given w.r.t. to the figure,
                % and so is the legend.
                position = (legendPos(1:2)-axesPos(1:2)) ./ axesPos(3:4);
            end
            anchor = 'south west';
        case {'best','bestoutside'}
            % TODO: Implement these.
            % The position could be determined by means of 'Position' and/or
            % 'OuterPosition' of the legend handle; in fact, this could be made
            % a general principle for all legend placements.
            userWarning(m2t, [sprintf(' Option ''%s'' not yet implemented.',loc),         ...
                ' Choosing default.']);
            return % use defaults

        otherwise
            userWarning(m2t, [' Unknown legend location ''',loc,''''           ...
                '. Choosing default.']);
            return % use defaults
    end

    % set legend position
    %TODO: shouldn't this include units?
    lStyle = opts_add(lStyle, 'at',  sprintf('{(%s,%s)}', ...
                        formatDim(position(1)), formatDim(position(2))));
    lStyle = opts_add(lStyle, 'anchor', anchor);

end
% ==============================================================================
function [lStyle] = getLegendEntryAlignment(m2t, handle, lStyle)
    % determines the text and picture alignment inside a legend
    textalign = '';
    pictalign = '';
    switch getEnvironment
        case 'Octave'
            % Octave allows to change the alignment of legend text and
            % pictograms using legend('left') and legend('right')
            textpos = get(handle, 'textposition');
            switch lower(textpos)
                case 'left'
                    % pictogram right of flush right text
                    textalign = 'right';
                    pictalign = 'right';
                case 'right'
                    % pictogram left of flush left text (default)
                    textalign = 'left';
                    pictalign = 'left';
                otherwise
                    userWarning(m2t, ...
                        ['Unknown legend text position ''',...
                        textpos, '''. Choosing default.']);
            end
        case 'MATLAB'
            % does not specify text/pictogram alignment in legends
        otherwise
            errorUnknownEnvironment();
    end

    % set alignment of legend text and pictograms, if available
    if ~isempty(textalign) && ~isempty(pictalign)
        lStyle = opts_add(lStyle, 'legend cell align', textalign);
        lStyle = opts_add(lStyle, 'align', textalign);
        lStyle = opts_add(lStyle, 'legend plot pos', pictalign);
    else
        % Make sure the entries are flush left (default MATLAB behavior).
        % This is also import for multiline legend entries: Without alignment
        % specification, the TeX document won't compile.
        % 'legend plot pos' is not set explicitly, since 'left' is default.
        lStyle = opts_add(lStyle, 'legend cell align', 'left');
        lStyle = opts_add(lStyle, 'align', 'left');
    end
end
% ==============================================================================
function [pTicks, pTickLabels] = ...
    matlabTicks2pgfplotsTicks(m2t, ticks, tickLabels, isLogAxis, tickLabelMode)
    % Converts MATLAB style ticks and tick labels to pgfplots style (if needed)
    if isempty(ticks)
        pTicks      = '\empty';
        pTickLabels = [];
        return
    end

    % set ticks + labels
    pTicks = join(m2t, num2cell(ticks), ',');

    % if there's no specific labels, return empty
    if isempty(tickLabels) || (length(tickLabels)==1 && isempty(tickLabels{1}))
        pTickLabels = '\empty';
        return
    end

    % sometimes tickLabels are cells, sometimes plain arrays
    % -- unify this to cells
    if ischar(tickLabels)
        tickLabels = strtrim(mat2cell(tickLabels,                  ...
            ones(size(tickLabels,1), 1), ...
            size(tickLabels, 2)          ...
            ) ...
            );
    end

    ticks = removeSuperfluousTicks(ticks, tickLabels);

    isNeeded = isTickLabelsNecessary(m2t, ticks, tickLabels, isLogAxis);

    pTickLabels = formatPgfTickLabels(m2t, isNeeded, tickLabels, ...
        isLogAxis, tickLabelMode);
end
% ==============================================================================
function bool = isTickLabelsNecessary(m2t, ticks, tickLabels, isLogAxis)
    % Check if tickLabels are really necessary (and not already covered by
    % the tick values themselves).
    bool = false;

    k = find(ticks ~= 0.0, 1); % get an index with non-zero tick value
    if isLogAxis || isempty(k) % only a 0-tick
        scalingFactor = 1;
    else
        % When plotting axis, MATLAB might scale the axes by a factor of ten,
        % say 10^n, and plot a 'x 10^k' next to the respective axis. This is
        % common practice when the tick marks are really large or small
        % numbers.
        % Unfortunately, MATLAB doesn't contain the information about the
        % scaling anywhere in the plot, and at the same time the {x,y}TickLabels
        % are given as t*10^k, thus no longer corresponding to the actual
        % value t.
        % Try to find the scaling factor here. This is then used to check
        % whether or not explicit {x,y}TickLabels are really necessary.
        s = str2double(tickLabels{k});
        scalingFactor = ticks(k)/s;
        % check if the factor is indeed a power of 10
        S = log10(scalingFactor);
        if abs(round(S)-S) > m2t.tol
            scalingFactor = 1.0;
        end
    end

    for k = 1:min(length(ticks),length(tickLabels))
        % Don't use str2num here as then, literal strings as 'pi' get
        % legally transformed into 3.14... and the need for an explicit
        % label will not be recognized. str2double returns a NaN for 'pi'.
        if isLogAxis
            s = 10^(str2double(tickLabels{k}));
        else
            s = str2double(tickLabels{k});
        end
        if isnan(s)  ||  abs(ticks(k)-s*scalingFactor) > m2t.tol
            bool = true;
            return;
        end
    end
end
% ==============================================================================
function pTickLabels = formatPgfTickLabels(m2t, plotLabelsNecessary, ...
        tickLabels, isLogAxis, tickLabelMode)
    % formats the tick labels for pgfplots
    if plotLabelsNecessary
        for k = 1:length(tickLabels)
            % Turn tickLabels from cells containing a cell into
            % cells containing strings
            if isnumeric(tickLabels{k})
                tickLabels(k) = num2str(tickLabels{k});
            elseif iscell(tickLabels{k})
                tickLabels(k) = tickLabels{k};
            end
            % If the axis is logscaled, MATLAB does not store the labels,
            % but the exponents to 10
            if isLogAxis && strcmpi(tickLabelMode,'auto')
                tickLabels{k} = sprintf('$10^{%s}$', str);
            end
        end
        tickLabels = cellfun(@(l)(sprintf('{%s}',l)), tickLabels, ...
            'UniformOutput', false);
        pTickLabels = join(m2t, tickLabels, ',');
    else
        pTickLabels = [];
    end
end
% ==============================================================================
function ticks = removeSuperfluousTicks(ticks, tickLabels)
    % What MATLAB does when the number of ticks and tick labels is not the same,
    % is somewhat unclear. Cut of the first entries to fix bug
    %     https://github.com/matlab2tikz/matlab2tikz/issues/161,
    m = length(ticks);
    n = length(tickLabels);
    if n < m
        ticks = ticks(m-n+1:end);
    end
end
% ==============================================================================
function tikzLineStyle = translateLineStyle(matlabLineStyle)
    if(~ischar(matlabLineStyle))
        error('matlab2tikz:translateLineStyle:NotAString',...
            'Variable matlabLineStyle is not a string.');
    end

    switch (matlabLineStyle)
        case 'none'
            tikzLineStyle = '';
        case '-'
            tikzLineStyle = 'solid';
        case '--'
            tikzLineStyle = 'dashed';
        case ':'
            tikzLineStyle = 'dotted';
        case '-.'
            tikzLineStyle = 'dashdotted';
        otherwise
            error('matlab2tikz:translateLineStyle:UnknownLineStyle',...
                'Unknown matlabLineStyle ''%s''.', matlabLineStyle);
    end
end
% ==============================================================================
function [m2t, table, opts] = makeTable(m2t, varargin)
    %   [m2t,table,opts] = makeTable(m2t, 'name1', data1, 'name2', data2, ...)
    %   [m2t,table,opts] = makeTable(m2t, {'name1','name2',...}, {data1, data2, ...})
    %   [m2t,table,opts] = makeTable(m2t, {'name1','name2',...}, [data1(:), data2(:), ...])
    %
    %  Returns m2t structure, formatted table and table options.
    %  When all the names are empty, no header is printed
    [variables, data] = parseInputsForTable_(varargin{:});
    opts = opts_new();

    COLSEP = sprintf('\t');
    if m2t.args.externalData
        ROWSEP = sprintf('\n');
    else
        ROWSEP = sprintf('\\\\\n');
        opts = opts_add(opts, 'row sep','crcr');
    end

    nColumns = numel(data);
    nRows    = cellfun(@numel, data);
    if ~all(nRows==nRows(1))
        error('matlab2tikz:makeTableDifferentNumberOfRows',...
            'Different data lengths [%s].', num2str(nRows));
    end
    nRows = nRows(1);

    FORMAT = repmat({m2t.ff}, 1, nColumns);
    FORMAT(cellfun(@isCellOrChar, data)) = {'%s'};
    FORMAT(cellfun(@isCategoricalType, data)) = {'%s'};
    FORMAT = join(m2t, FORMAT, COLSEP);
    if all(cellfun(@isempty, variables))
        header = {};
    else
        header = {join(m2t, variables, COLSEP)};
    end

    table = cell(nRows,1);
    for iRow = 1:nRows
        thisData = cell(1,nColumns);
        for jCol = 1:nColumns
            thisData{1,jCol} = data{jCol}(iRow);
        end
        table{iRow} = sprintf(FORMAT, thisData{:});
    end
    % convert NaN and Inf to lower case for TikZ
    table = regexprep(table, '\<NaN\>', 'nan');
    table = regexprep(table, '\<Inf\>', 'inf');
    table = [join(m2t, [header;table], ROWSEP) ROWSEP];

    if m2t.args.externalData
        % output data to external file
        [m2t, fileNum] = incrementGlobalCounter(m2t, 'tsvFile');
        [filename, latexFilename] = externalFilename(m2t, fileNum, '.tsv');

        % write the data table to an external file
        fid = fileOpenForWrite(m2t, filename);
        finally_fclose_fid = onCleanup(@() fclose(fid));

        fprintf(fid, '%s', table);

        % put the filename in the TikZ output
        table = latexFilename;
    else
        % output data with "%newline" prepended for formatting consistency
        % do NOT prepend another newline in the output: LaTeX will crash.
        table = sprintf('%%\n%s', table);
    end
end
% ==============================================================================
function [variables, data] = parseInputsForTable_(varargin)
    % parse input arguments for |makeTable|
    if numel(varargin) == 2 % cell syntax
        variables = varargin{1};
        data      = varargin{2};
        if ischar(variables)
            % one variable, one data vector -> (cell, cell)
            variables = {variables};
            data      = {data};
        elseif iscellstr(variables) && ~iscell(data)
            % multiple variables, one data matrix -> (cell, cell) by column
            data = num2cell(data, 1);
        end
    else % key-value syntax
        variables = varargin(1:2:end-1);
        data      = varargin(2:2:end);
    end
end
% ==============================================================================
function [path, texpath] = externalFilename(m2t, counter, extension)
    % generates a file name for an external data file and its relative TeX path

    [dummy, name] = fileparts(m2t.tikzFileName); %#ok
    baseFilename  = [name '-' num2str(counter) extension];
    path    = fullfile(m2t.dataPath, baseFilename);
    texpath = TeXpath(fullfile(m2t.relativeDataPath, baseFilename));
end
% ==============================================================================
function [names,definitions] = dealColorDefinitions(mergedColorDefs)
    if isempty(mergedColorDefs)
        mergedColorDefs = {};
    end
    [names,definitions] = cellfun(@(x)(deal(x{:})),  mergedColorDefs, ...
        'UniformOutput', false);
end
% ==============================================================================
function [m2t, colorLiteral] = rgb2colorliteral(m2t, rgb)
    % Translates an rgb value to an xcolor literal
    %
    % Possible outputs:
    %  - xcolor literal color, e.g. 'blue'
    %  - mixture of 2 previously defined colors, e.g. 'red!70!green'
    %  - a newly defined color, e.g. 'mycolor10'

    % Take a look at xcolor.sty for the color definitions.
    % In xcolor.sty some colors are defined in CMYK space and approximated
    % crudely for RGB color space. So it is better to redefine those colors
    % instead of using xcolor's:
    %    'cyan' , 'magenta', 'yellow', 'olive'
    %    [0,1,1], [1,0,1]  , [1,1,0] , [0.5,0.5,0]

    xcolColorNames = {'white', 'black', 'red', 'green', 'blue', ...
                      'brown', 'lime', 'orange', 'pink', ...
                      'purple', 'teal', 'violet', ...
                      'darkgray', 'gray', 'lightgray'};
    xcolColorSpecs = {[1,1,1], [0,0,0], [1,0,0], [0,1,0], [0,0,1], ...
                      [0.75,0.5,0.25], [0.75,1,0], [1,0.5,0], [1,0.75,0.75], ...
                      [0.75,0,0.25], [0,0.5,0.5], [0.5,0,0.5], ...
                      [0.25,0.25,0.25], [0.5,0.5,0.5], [0.75,0.75,0.75]};

    colorNames = [xcolColorNames, m2t.color.extraNames];
    colorSpecs = [xcolColorSpecs, m2t.color.extraSpecs];

    %% check if rgb is a predefined color
    for kColor = 1:length(colorSpecs)
        Ck = colorSpecs{kColor}(:);
        if max(abs(Ck - rgb(:))) < m2t.color.precision
            colorLiteral = colorNames{kColor};
            return % exact color was predefined
        end
    end

    %% check if the color is a linear combination of two already defined colors
    for iColor = 1:length(colorSpecs)
        for jColor = iColor+1:length(colorSpecs)
            Ci = colorSpecs{iColor}(:);
            Cj = colorSpecs{jColor}(:);

            % solve color mixing equation `Ck = p * Ci + (1-p) * Cj` for p
            p  = (Ci-Cj) \ (rgb(:)-Cj);
            p  = round(100*p)/100;  % round to a percentage
            Ck = p * Ci + (1-p)*Cj; % approximated mixed color

            if p <= 1 && p >= 0 && max(abs(Ck(:) - rgb(:))) < m2t.color.precision
                colorLiteral = sprintf('%s!%d!%s', colorNames{iColor}, round(p*100), ...
                    colorNames{jColor});
                return % linear combination found
            end
        end
    end

    %% Define colors that are not a linear combination of two known colors
    colorLiteral = sprintf('mycolor%d', length(m2t.color.extraNames)+1);
    m2t.color.extraNames{end+1} = colorLiteral;
    m2t.color.extraSpecs{end+1} = rgb;
end
% ==============================================================================
function newstr = join(m2t, cellstr, delimiter)
    % This function joins a cell of strings to a single string (with a
    % given delimiter in between two strings, if desired).
    %
    % Example of usage:
    %              join(m2t, cellstr, ',')
    newstr = m2tstrjoin(cellstr, delimiter, m2t.ff);
end
% ==============================================================================
function [width, height, unit] = getNaturalFigureDimension(m2t)
    % Returns the size of figure (in inch)
    % To stay compatible with getNaturalAxesDimensions, the unit 'in' is
    % also returned.

    % Get current figure size
    figuresize = get(m2t.current.gcf, 'Position');
    figuresize = figuresize([3 4]);
    figureunit = get(m2t.current.gcf, 'Units');

    % Convert Figure Size
    unit = 'in';
    figuresize = convertUnits(figuresize, figureunit, unit);

    % Split size into width and height
    width  = figuresize(1);
    height = figuresize(2);

end
% ==============================================================================
function dimension = getFigureDimensions(m2t, widthString, heightString)
    % Returns the physical dimension of the figure.

    [width, height, unit] = getNaturalFigureDimension(m2t);

    % get the natural width-height ration of the plot
    axesWidthHeightRatio = width / height;
    % check matlab2tikz arguments
    if ~isempty(widthString)
        width = extractValueUnit(widthString);
    end
    if ~isempty(heightString)
        height = extractValueUnit(heightString);
    end

    % prepare the output
    if ~isempty(widthString) && ~isempty(heightString)
        dimension.x.unit  = width.unit;
        dimension.x.value = width.value;
        dimension.y.unit  = height.unit;
        dimension.y.value = height.value;
    elseif ~isempty(widthString)
        dimension.x.unit  = width.unit;
        dimension.x.value = width.value;
        dimension.y.unit  = width.unit;
        dimension.y.value = width.value / axesWidthHeightRatio;
    elseif ~isempty(heightString)
        dimension.y.unit  = height.unit;
        dimension.y.value = height.value;
        dimension.x.unit  = height.unit;
        dimension.x.value = height.value * axesWidthHeightRatio;
    else % neither width nor height given
        dimension.x.unit  = unit;
        dimension.x.value = width;
        dimension.y.unit  = unit;
        dimension.y.value = height;
    end
end
% ==============================================================================
function position = getAxesPosition(m2t, handle, widthString, heightString, axesBoundingBox)
    % Returns the physical position of the axes. This includes - in difference
    % to the Dimension - also an offset to shift the axes inside the figure
    % An optional bounding box can be used to omit empty borders.

    % Deal with optional parameter
    if nargin < 4
        axesBoundingBox = [0 0 1 1];
    end

    % First get the whole figures size
    figDim = getFigureDimensions(m2t, widthString, heightString);

    % Get the relative position of the axis
    relPos = getRelativeAxesPosition(m2t, handle, axesBoundingBox);

    position.x.value = relPos(1) * figDim.x.value;
    position.x.unit  = figDim.x.unit;
    position.y.value = relPos(2) * figDim.y.value;
    position.y.unit  = figDim.y.unit;
    position.w.value = relPos(3) * figDim.x.value;
    position.w.unit  = figDim.x.unit;
    position.h.value = relPos(4) * figDim.y.value;
    position.h.unit  = figDim.y.unit;
end
% ==============================================================================
function [position] = getRelativeAxesPosition(m2t, axesHandles, axesBoundingBox)
    % Returns the relative position of axes within the figure.
    % Position is an (n,4) matrix with [minX, minY, width, height] for each
    % handle. All these values are relative to the figure size, which means
    % that [0, 0, 1, 1] covers the whole figure.
    % It is possible to add a second parameter with the relative coordinates of
    % a bounding box around all axes of the figure (see getRelevantAxes()). In
    % this case, relative positions are rescaled so that the bounding box is
    % [0, 0, 1, 1]

    % Get Figure Dimension
    [figWidth, figHeight, figUnits] = getNaturalFigureDimension(m2t);

    % Initialize position
    position = zeros(numel(axesHandles), 4);
    % Iterate over all handles
    for i = 1:numel(axesHandles)
        axesHandle = axesHandles(i);
        axesPos = get(axesHandle, 'Position');
        axesUnits = get(axesHandle, 'Units');
        if isequal(lower(axesUnits), 'normalized')
            % Position is already relative
            position(i,:) = axesPos;
        else
            % Convert figure size into axes units
            figureSize = convertUnits([figWidth, figHeight], figUnits, axesUnits);
            % Figure size into axes units to get the relative size
            position(i,:) = axesPos ./ [figureSize, figureSize];

        end

        if strcmpi(get(axesHandle, 'DataAspectRatioMode'), 'manual') ...
                || strcmpi(get(axesHandle, 'PlotBoxAspectRatioMode'), 'manual')

            if strcmpi(get(axesHandle,'Projection'),'Perspective')
                userWarning(m2t,'Perspective projections are not currently supported')
            end

            % project vertices of 3d plot box (this results in 2d coordinates in
            % an absolute coordinate system that is scaled proportionally by
            % Matlab to fit the axes position box)
            switch getEnvironment()
                case 'MATLAB'
                    projection = view(axesHandle);

                case 'Octave'
                    % Unfortunately, Octave does not have the full `view`
                    % interface implemented, but the projection matrices are
                    % available: http://octave.1599824.n4.nabble.com/Implementing-view-td3032041.html

                    projection = get(axesHandle, 'x_viewtransform');

                otherwise
                    errorUnknownEnvironment();
            end


            vertices = projection * [0, 1, 0, 0, 1, 1, 0, 1;
                0, 0, 1, 0, 1, 0, 1, 1;
                0, 0, 0, 1, 0, 1, 1, 1;
                1, 1, 1, 1, 1, 1, 1, 1];

            % each of the columns of vertices represents a vertex of the 3D axes
            % but we only need their XY coordinates
            verticesXY = vertices([1 2], :);

            % the size of the projected plot box is limited by the long diagonals
            % The matrix A determines the connectivity, e.g. the first diagonal runs from vertices(:,3) -> vertices(:,4)
            A = [ 0,  0,  0, -1, +1,  0,  0,  0;
                0,  0, -1,  0,  0, +1,  0,  0;
                0, -1,  0,  0,  0,  0, +1,  0;
                -1,  0,  0,  0,  0,  0,  0, +1];
            diagonals = verticesXY * A';
            % each of the columns of this matrix contains a the X and Y distance of a diagonal
            dimensions = max(abs(diagonals), [], 2);

            % find limiting dimension and adjust position
            aspectRatio = dimensions(2) * figWidth / (dimensions(1) * figHeight);
            axesAspectRatio = position(i,4) / position(i,3);
            if aspectRatio > axesAspectRatio
                newWidth = position(i,4) / aspectRatio;
                % Center Axis
                offset = (position(i,3) - newWidth) / 2;
                position(i,1) = position(i,1) + offset;
                % Store new width
                position(i,3) = newWidth;
            else
                newHeight = position(i,3) * aspectRatio;
                offset = (position(i,4) - newHeight) / 2;
                position(i,2) = position(i,2) + offset;
                % Store new height
                position(i,4) = newHeight;
            end
        end
    end

    %% Rescale if axesBoundingBox is given
    if exist('axesBoundingBox','var')
        % shift position so that [0, 0] is the lower left corner of the
        % bounding box
        position(:,1) = position(:,1) - axesBoundingBox(1);
        position(:,2) = position(:,2) - axesBoundingBox(2);
        % Recale
        position(:,[1 3]) = position(:,[1 3]) / max(axesBoundingBox([3 4]));
        position(:,[2 4]) = position(:,[2 4]) / max(axesBoundingBox([3 4]));
    end
end
% ==============================================================================
function aspectRatio = getPlotBoxAspectRatio(axesHandle)
    limits = axis(axesHandle);
    if any(isinf(limits))
        aspectRatio = get(axesHandle,'PlotBoxAspectRatio');
    else
        % DataAspectRatio has priority
        dataAspectRatio = get(axesHandle,'DataAspectRatio');
        nlimits         = length(limits)/2;
        limits          = reshape(limits, 2, nlimits);
        aspectRatio     = abs(limits(2,:) - limits(1,:))./dataAspectRatio(1:nlimits);
        aspectRatio     = aspectRatio/min(aspectRatio);
    end
end
% ==============================================================================
function texUnits = matlab2texUnits(matlabUnits, fallbackValue)
    switch matlabUnits
        case 'pixels'
            texUnits = 'px'; % only in pdfTex/LuaTeX
        case 'centimeters'
            texUnits = 'cm';
        case 'characters'
            texUnits = 'em';
        case 'points'
            texUnits = 'pt';
        case 'inches'
            texUnits = 'in';
        otherwise
            texUnits = fallbackValue;
    end
end
% ==============================================================================
function dstValue = convertUnits(srcValue, srcUnit, dstUnit)
    % Converts values between different units.
    %   srcValue stores a length (or vector of lengths) in srcUnit.
    % The resulting dstValue is the converted length into dstUnit.
    %
    % Currently supported units are: in, cm, px, pt

    % Use tex units, if possible (to make things simple)
    srcUnit = matlab2texUnits(lower(srcUnit),lower(srcUnit));
    dstUnit = matlab2texUnits(lower(dstUnit),lower(dstUnit));

    if isequal(srcUnit, dstUnit)
        dstValue = srcValue;
        return % conversion to the same unit => factor = 1
    end

    units  = {srcUnit, dstUnit};
    factor = ones(1,2);
    for ii = 1:numel(factor) % Same code for srcUnit and dstUnit
        % Use inches as intermediate unit
        % Compute the factor to convert an inch into another unit
        switch units{ii}
            case 'cm'
                factor(ii) = 2.54;
            case 'px'
                factor(ii) = get(0, 'ScreenPixelsPerInch');
            case 'in'
                factor(ii) = 1;
            case 'pt'
                factor(ii) = 72;
            otherwise
                warning('MATLAB2TIKZ:UnknownPhysicalUnit',...
                'Can not convert unit ''%s''. Using conversion factor 1.', units{ii});
        end
    end

    dstValue = srcValue * factor(2) / factor(1);
end
% ==============================================================================
function out = extractValueUnit(str)
    % Decompose m2t.args.width into value and unit.

    % Regular expression to match '4.12cm', '\figurewidth', ...
    fp_regex = '[-+]?\d*\.?\d*(?:e[-+]?\d+)?';
    pattern = strcat('(', fp_regex, ')?', '(\\?[a-zA-Z]+)');

    [dummy,dummy,dummy,dummy,t,dummy] = regexp(str, pattern, 'match'); %#ok

    if length(t)~=1
        error('getAxesDimensions:illegalLength', ...
            'The width string ''%s'' could not be decomposed into value-unit pair.', str);
    end

    if length(t{1}) == 1
        out.value = 1.0; % such as in '1.0\figurewidth'
        out.unit  = strtrim(t{1}{1});
    elseif length(t{1}) == 2 && isempty(t{1}{1})
        % MATLAB(R) does this:
        % length(t{1})==2 always, but the first field may be empty.
        out.value = 1.0;
        out.unit  = strtrim(t{1}{2});
    elseif length(t{1}) == 2
        out.value = str2double(t{1}{1});
        out.unit  = strtrim(t{1}{2});
    else
        error('getAxesDimensions:illegalLength', ...
            'The width string ''%s'' could not be decomposed into value-unit pair.', str);
    end
end
% ==============================================================================
function str = escapeCharacters(str)
    % Replaces "%" and "\" with respectively "%%" and "\\"
    str = strrep(str, '%' , '%%');
    str = strrep(str, '\' , '\\');
end
% ==============================================================================
function bool = isNone(value)
    % Checks whether a value is 'none'
    bool = strcmpi(value, 'none');
end
% ==============================================================================
function bool = isOn(value)
    % Checks whether a value is 'on'
    bool = strcmpi(value, 'on');
end
% ==============================================================================
function bool = isOff(value)
    % Checks whether a value is 'off'.
    % Note that some options are not be solely an on/off boolean, such that `isOn`
    % and isOff don't always return the complement of each other and such that we
    % need both functions to check the value.
    % E.g. `set(0, 'HandleVisibility')` allows the value 'callback'.
    bool = strcmpi(value, 'off');
end
% ==============================================================================
function val = getOrDefault(handle, key, default)
    % gets the value or returns the default value if no such property exists
    if all(isprop(handle, key))
        val = get(handle, key);
    else
        val = default;
    end
end
% ==============================================================================
function val = getFactoryOrDefault(type, key, fallback)
    % get factory default value for a certain type of HG object
    % this CANNOT be done using |getOrDefault| as |isprop| doesn't work for
    % factory/default settings. Hence, we use a more expensive try-catch instead.
    try
        groot = 0;
        val = get(groot, ['Factory' type key]);
    catch
        val = fallback;
    end
end
% ==============================================================================
function [val, isDefault] = getAndCheckDefault(type, handle, key, default)
    % gets the value from a handle of certain type and check the default values
    default   = getFactoryOrDefault(type, key, default);
    val       = getOrDefault(handle, key, default);
    isDefault = isequal(val, default);
end
% ==============================================================================
function bool = isVisible(handles)
    % Determines whether an object is actually visible or not.
    bool = isOn(get(handles,'Visible'));
    % There's another handle property, 'HandleVisibility', that is unrelated
    % to the "physical" visibility of an object. Rather, it sets whether an
    % object should be visitable by |findobj|. Hence, it is often switched off
    % for non-data objects such as custom axes/grid objects.
end
% ==============================================================================
function [m2t, axesBoundingBox] = getRelevantAxes(m2t, axesHandles)
    % Returns relevant axes. These are defines as visible axes that are no
    % colorbars. Function 'findPlotAxes()' ensures that 'axesHandles' does not
    % contain colorbars. In addition, a bounding box around all relevant Axes is
    % computed. This can be used to avoid undesired borders.
    % This function is the remaining code of alignSubPlots() in the alternative
    % positioning system.

    % List only visible axes
    N   = numel(axesHandles);
    idx = false(N,1);
    for ii = 1:N
        idx(ii) = isVisibleContainer(axesHandles(ii));
    end
    % Store the relevant axes in m2t to simplify querying e.g. positions
    % of subplots
    m2t.relevantAxesHandles = axesHandles(idx);

    % Compute the bounding box if width or height of the figure are set by
    % parameter
    if ~isempty(m2t.args.width) || ~isempty(m2t.args.height)
        % TODO: check if relevant Axes or all Axes are better.
        axesBoundingBox = getRelativeAxesPosition(m2t, m2t.relevantAxesHandles);
        % Compute second corner from width and height for each axes
        axesBoundingBox(:,[3 4]) = axesBoundingBox(:,[1 2]) + axesBoundingBox(:,[3 4]);
        % Combine axes corners to get the bounding box
        axesBoundingBox = [min(axesBoundingBox(:,[1 2]),[],1), max(axesBoundingBox(:,[3 4]), [], 1)];
        % Compute width and height of the bounding box
        axesBoundingBox(:,[3 4]) = axesBoundingBox(:,[3 4]) - axesBoundingBox(:,[1 2]);
    else
        % Otherwise take the whole figure as bounding box => lengths are
        % not changed in tikz
        axesBoundingBox = [0, 0, 1, 1];
    end
end
% ==============================================================================
function userInfo(m2t, message, varargin)
    % Display usage information.
    if m2t.args.showInfo
        mess = sprintf(message, varargin{:});

        mess = strrep(mess, sprintf('\n'), sprintf('\n *** '));
        fprintf(' *** %s\n', mess);
    end
end
% ==============================================================================
function userWarning(m2t, message, varargin)
    % Drop-in replacement for warning().
    if m2t.args.showWarnings
        warning('matlab2tikz:userWarning', message, varargin{:});
    end
end
% ==============================================================================
function m2t = signalDependency(m2t, dependencyType, name)
    % Signals an (optional) dependency to the user
    switch lower(dependencyType)
        case 'tikzlibrary'
            message = 'Make sure to add "\\usetikzlibrary{%s}" to the preamble.';
        otherwise
            message = 'Please make sure to load the "%s" dependency';
    end
    userInfo(m2t, message, name);
end
% ==============================================================================
function warnAboutParameter(m2t, parameter, isActive, message)
    % warn the user about the use of a dangerous parameter
    line = ['\n' repmat('=',1,80) '\n'];
    if isActive(m2t.args.(parameter))
        userWarning(m2t, [line, 'You are using the "%s" parameter.\n', ...
                          message line], parameter);
    end
end
% ==============================================================================
function parent = addChildren(parent, children)
    if isempty(children)
        return;
    elseif iscell(children)
        for k = 1:length(children)
            parent = addChildren(parent, children{k});
        end
    else
        if isempty(parent.children)
            parent.children = {children};
        else
            parent.children = [parent.children children];
        end
    end
end
% ==============================================================================
function printAll(m2t, env, fid)
    if isfield(env, 'colors') && ~isempty(env.colors)
        fprintf(fid, '%s', env.colors);
    end

    if isempty(env.options)
        fprintf(fid, '\\begin{%s}\n', env.name);
    else
        fprintf(fid, '\\begin{%s}[%%\n%s\n]\n', env.name, ...
                opts_print(env.options, sprintf(',\n')));
    end

    for item = env.content
        fprintf(fid, '%s', char(item));
    end

    for k = 1:length(env.children)
        if ischar(env.children{k})
            fprintf(fid, escapeCharacters(env.children{k}));
        else
            fprintf(fid, '\n');
            printAll(m2t, env.children{k}, fid);
        end
    end

    % End the tikzpicture environment with an empty comment and no newline
    % so no additional space is generated after the tikzpicture in TeX.
    if strcmp(env.name, 'tikzpicture') % LaTeX is case sensitive
        fprintf(fid, '\\end{%s}%%', env.name);
    else
        fprintf(fid, '\\end{%s}\n', env.name);
    end
end
% ==============================================================================
function c = prettyPrint(m2t, strings, interpreter)
    % Some resources on how MATLAB handles rich (TeX) markup:
    % http://www.mathworks.com/help/techdoc/ref/text_props.html#String
    % http://www.mathworks.com/help/techdoc/creating_plots/f0-4741.html#f0-28104
    % http://www.mathworks.com/help/techdoc/ref/text_props.html#Interpreter
    % http://www.mathworks.com/help/techdoc/ref/text.html#f68-481120

    % If the user set the matlab2tikz parameter 'parseStrings' to false, no
    % parsing of strings takes place, thus making the user 100% responsible.
    if ~m2t.args.parseStrings
        % If strings is an actual string (labels etc) we need to return a
        % cell containing the string
        c = cellstr(strings);
        return
    end

    % Make sure we have a valid interpreter set up
    if ~any(strcmpi(interpreter, {'latex', 'tex', 'none'}))
        userWarning(m2t, 'Don''t know interpreter ''%s''. Default handling.', interpreter);
        interpreter = 'tex';
    end

    strings = cellstrOneLinePerCell(strings);

    % Now loop over the strings and return them pretty-printed in c.
    c = cell(1, length(strings));
    for k = 1:length(strings)
        % linear indexing for independence of cell array dimensions
        s = strings{k};

        % The interpreter property of the text element defines how the string
        % is parsed
        switch lower(interpreter)
            case 'latex' % Basic subset of the LaTeX markup language

                % Replace $$...$$ with $...$ for groups, but otherwise leave
                % untouched.
                % Displaymath \[...\] seems to be unsupported by TikZ/PGF.
                % If this changes, use '\\[$2\\]' as replacement below.
                % Do not escape dollar in replacement string (e.g., "\$$2\$"),
                % since this is not properly handled by octave 3.8.2.
                string = regexprep(s, '(\$\$)(.*?)(\$\$)', '$$2$');

            case 'tex' % Subset of plain TeX markup language

                % Deal with UTF8 characters.
                string = s;

                % degree symbol following "^" or "_" needs to be escaped
                string = regexprep(string, '([\^\_])°', '$1{{}^\\circ}');
                string = strrep(string, '°', '^\circ');
                string = strrep(string, '∞', '\infty');

                % Parse string piece-wise in a separate function.
                string = parseTexString(m2t, string);

            case 'none' % Literal characters
                % Make special characters TeX compatible

                string = strrep(s, '\', '\textbackslash{}');
                % Note: '{' and '}' can't be converted to '\{' and '\}',
                %       respectively, via strrep(...) as this would lead to
                %       backslashes converted to '\textbackslash\{\}' because
                %       the backslash was converted to '\textbackslash{}' in
                %       the previous step. Using regular expressions with
                %       negative look-behind makes sure any braces in 'string'
                %       were not introduced by escaped backslashes.
                %       Also keep in mind that escaping braces before backslashes
                %       would not remedy the issue -- in that case 'string' would
                %       contain backslashes introduced by brace escaping that are
                %       not supposed to be printable characters.
                repl = switchMatOct('\\{', '\{');
                string = regexprep(string, '(?<!\\textbackslash){', repl);
                repl = switchMatOct('\\}', '\}');
                string = regexprep(string, '(?<!\\textbackslash{)}', repl);
                string = strrep(string, '$', '\$');
                string = strrep(string, '%', '\%');
                string = strrep(string, '_', '\_');
                string = strrep(string, '^', '\textasciicircum{}');
                string = strrep(string, '#', '\#');
                string = strrep(string, '&', '\&');
                string = strrep(string, '~', '\textasciitilde{}'); % or '\~{}'
                % Clean up: remove superfluous '{}' if it's followed by a backslash
                string = strrep(string, '{}\', '\');
                % Clean up: remove superfluous '{}' at the end of 'string'
                string = regexprep(string, '\{\}$', '');

                % Make sure to return a string and not a cellstr.
                if iscellstr(string)
                    string = string{1};
                end
            otherwise
                error('matlab2tikz:prettyPrint', 'Unknown interpreter');
        end
        c{k} = string;
    end
end
% ==============================================================================
function strings = cellstrOneLinePerCell(strings)
    % convert to cellstr that contains only one-line strings
    if ischar(strings)
        strings = cellstr(strings);
    elseif iscellstr(strings)
        cs = cell(1, length(strings));
        for s = 1:length(strings)
            tmp = cellstr(strings{s});
            cs{s} = tmp;
        end
        strings = cs;
    else
        error('matlab2tikz:cellstrOneLinePerCell', ...
            'Data type not understood.');
    end
end
% ==============================================================================
function parsed = parseTexString(m2t, string)
    if iscellstr(string)
        % Convert cell string to regular string, otherwise MATLAB complains
        string = string{:};
    end

    % Get the position of all braces
    bracesPos = regexp(string, '\{|\}');

    % Exclude braces that are part of any of these MATLAB-supported TeX commands:
    % \color{...}  \color[...]{...}  \fontname{...}  \fontsize{...}
    [sCmd, eCmd] = regexp(string, '\\(color(\[[^\]]*\])?|fontname|fontsize)\{[^}]*\}');
    for i = 1:length(sCmd)
        bracesPos(bracesPos >= sCmd(i) & bracesPos <= eCmd(i)) = [];
    end

    % Exclude braces that are preceded by an odd number of backslashes which
    % means the brace is escaped and thus to be printed, not a grouping brace
    expr = '(?<!\\)(\\\\)*\\(\{|\})';
    escaped = regexp(string, expr, 'end');
    % It's necessary to go over 'string' with the same RegEx again to catch
    % overlapping matches, e.g. string == '\{\}'. In such a case the simple
    % regexp(...) above only finds the first brace. What we have to do is look
    % only at the part of 'string' that starts with the first brace but doesn't
    % encompass its escaping backslash. Iterating over all previously found
    % matches makes sure all overlapping matches are found, too. That way even
    % cases like string == '\{\} \{\}' are handled correctly.
    % The call to unique(...) is not necessary to get the behavior described, but
    % by removing duplicates in 'escaped' it's cleaner than without.
    for i = escaped
        escaped = unique([escaped, regexp(string(i:end), expr, 'end') + i-1]);
    end
    % Now do the actual removal of escaped braces
    for i = 1:length(escaped)
        bracesPos(bracesPos == escaped(i)) = [];
    end

    parsed = '';
    % Have a virtual brace one character left of where the actual string
    % begins (remember, MATLAB strings start counting at 1, not 0). This is
    % to make sure substrings left of the first brace get parsed, too.
    prevBracePos = 0;
    % Iterate over all the brace positions in order to split up 'string'
    % at those positions and then parse the substrings. A virtual brace is
    % added right of where the actual string ends to make sure substrings
    % right of the right-most brace get parsed as well.
    for currBracePos = [bracesPos, length(string)+1]
        if (prevBracePos + 1) < currBracePos
            % Parse the substring between (but not including) prevBracePos
            % and currBracePos, i.e. between the previous brace and the
            % current one (but only if there actually is a non-empty
            % substring). Then append it to the output string.
            substring = string(prevBracePos+1 : currBracePos-1);
            parsed = [parsed, parseTexSubstring(m2t, substring)];
        end
        if currBracePos <= length(string)
            % Append the brace itself to the output string, but only if the
            % current brace position is within the limits of the string, i.e.
            % don't append anything for the last, virtual brace that is only
            % there to enable parsing of substrings beyond the right-most
            % actual brace.
            brace = string(currBracePos);
            parsed = [parsed, brace];
        end
        % The current brace position will be next iteration's previous one
        prevBracePos = currBracePos;
    end

    % Enclose everything in $...$ to use math mode
    parsed = ['$' parsed '$'];
    % ...except when everything is text
    parsed = regexprep(parsed, '^\$\\text\{([^}]*)\}\$$', '$1');
    % start-> $ \text {(non-}) } $<-end
    % ...or when the parsed string is empty
    parsed = regexprep(parsed, '^\$\$$', '');

    % Ensure math mode for pipe symbol (issue #587)
    parsed = strrep(parsed, '|', '$|$');
end
% ==============================================================================
function string = parseTexSubstring(m2t, string)
    origstr = string; % keep this for warning messages

    % Font families (italic, bold, etc.) get a trailing '{}' because they may be
    % followed by a letter which would produce an error in (La)TeX.
    for i = {'it', 'bf', 'rm', 'sl'}
        string = strrep(string, ['\' i{:}], ['\' i{:} '{}']);
    end

    % The same holds true for special characters like \alpha
    % The list of MATLAB-supported TeX characters was taken from
    % http://www.mathworks.com/help/techdoc/ref/text_props.html#String
    named = {'alpha', 'angle', 'ast', 'beta', 'gamma', 'delta',     ...
        'epsilon', 'zeta', 'eta', 'theta', 'vartheta', 'iota', ...
        'kappa', 'lambda', 'mu', 'nu', 'xi', 'pi', 'rho',      ...
        'sigma', 'varsigma', 'tau', 'equiv', 'Im', 'otimes',   ...
        'cap', 'int', 'rfloor', 'lfloor', 'perp', 'wedge',     ...
        'rceil', 'vee', 'langle', 'upsilon', 'phi', 'chi',     ...
        'psi', 'omega', 'Gamma', 'Delta', 'Theta', 'Lambda',   ...
        'Xi', 'Pi', 'Sigma', 'Upsilon', 'Phi', 'Psi', 'Omega', ...
        'forall', 'exists', 'ni', 'cong', 'approx', 'Re',      ...
        'oplus', 'cup', 'subseteq', 'lceil', 'cdot', 'neg',    ...
        'times', 'surd', 'varpi', 'rangle', 'sim', 'leq',      ...
        'infty', 'clubsuit', 'diamondsuit', 'heartsuit',       ...
        'spadesuit', 'leftrightarrow', 'leftarrow',            ...
        'Leftarrow', 'uparrow', 'rightarrow', 'Rightarrow',    ...
        'downarrow', 'circ', 'pm', 'geq', 'propto', 'partial', ...
        'bullet', 'div', 'neq', 'aleph', 'wp', 'oslash',       ...
        'supseteq', 'nabla', 'ldots', 'prime', '0', 'mid',     ...
        'copyright'                                            };
    for i = named
        string = strrep(string, ['\' i{:}], ['\' i{:} '{}']);
        % FIXME: Only append '{}' if there's an odd number of backslashes
        %        in front of the items from 'named'. If it's an even
        %        number instead, that means there's an escaped (printable)
        %        backslash and some text like "alpha" after that.
    end
    % Some special characters' names are subsets of others, e.g. '\o' is
    % a subset of '\omega'. This would produce undesired double-escapes.
    % For example if '\o' was converted to '\o{}' after '\omega' has been
    % converted to '\omega{}' this would result in '\o{}mega{}' instead of
    % '\omega{}'. Had '\o' been converted to '\o{}' _before_ '\omega' is
    % converted then the result would be '\o{}mega' and thus also wrong.
    % To circumvent the problem all those special character names that are
    % subsets of others are now converted using a regular expression that
    % uses negative lookahead. The special handling of the backslash is
    % required for MATLAB/Octave compatibility.
    string = regexprep(string, '(\\)o(?!mega|times|plus|slash)', '$1o{}');
    string = regexprep(string, '(\\)in(?!t|fty)', '$1in{}');
    string = regexprep(string, '(\\)subset(?!eq)', '$1subset{}');
    string = regexprep(string, '(\\)supset(?!eq)', '$1supset{}');

    % Convert '\0{}' (TeX text mode) to '\emptyset{}' (TeX math mode)
    string = strrep(string, '\0{}', '\emptyset{}');

    % Add skip to \fontsize
    % This is required for a successful LaTeX run on the output as in contrast
    % to MATLAB/Octave it requires the skip parameter (even if it's zero)
    string = regexprep(string, '(\\fontsize\{[^}]*\})', '$1{0}');

    % Put '\o{}' inside \text{...} as it is a text mode symbol that does not
    % exist in math mode (and LaTeX gives a warning if you use it in math mode)
    string = strrep(string, '\o{}', '\text{\o{}}');

    % Put everything that isn't a TeX command inside \text{...}
    expr = '(\\[a-zA-Z]+(\[[^\]]*\])?(\{[^}]*\}){1,2})';
    % |( \cmd  )( [...]?  )( {...}{1,2} )|
    % (              subset $1               )
    repl = '}$1\\text{';
    string = regexprep(string, expr, repl);
    % ...\alpha{}... -> ...}\alpha{}\text{...
    string = ['\text{' string '}'];
    % ...}\alpha{}\text{... -> \text{...}\alpha{}\text{...}

    % '_' has to be in math mode so long as it's not escaped as '\_' in which
    % case it remains as-is. Extra care has to be taken to make sure any
    % backslashes in front of the underscore are not themselves escaped and
    % thus printable backslashes. This is the case if there's an even number
    % of backslashes in a row.
    repl = '$1}_\\text{';
    string = regexprep(string, '(?<!\\)((\\\\)*)_', repl);

    % '^' has to be in math mode so long as it's not escaped as '\^' in which
    % case it is expressed as '\textasciicircum{}' for compatibility with
    % regular TeX. Same thing here regarding even/odd number of backslashes
    % as in the case of underscores above.
    repl = '$1\\textasciicircum{}';
    string = regexprep(string, '(?<!\\)((\\\\)*)\\\^', repl);
    repl = '$1}^\\text{';
    string = regexprep(string, '(?<!\\)((\\\\)*)\^', repl);

    % '<' and '>' has to be either in math mode or needs to be typeset as
    % '\textless' and '\textgreater' in textmode
    % This is handled better, if 'parseStringsAsMath' is activated
    if m2t.args.parseStringsAsMath == 0
        string = regexprep(string, '<', '\\textless{}');
        string = regexprep(string, '>', '\\textgreater{}');
    end

    % Move font styles like \bf into the \text{} command.
    expr = '(\\bf|\\it|\\rm|\\fontname)({\w*})+(\\text{)';
    while regexp(string, expr)
        string = regexprep(string, expr, '$3$1$2');
    end

    % Replace Fontnames
    [dummy, dummy, dummy, dummy, fonts, dummy, subStrings] = regexp(string, '\\fontname{(\w*)}'); %#ok
    fonts = fonts2tex(fonts);
    subStrings = [subStrings; fonts, {''}];
    string = cell2mat(subStrings(:)');

    % Merge adjacent \text fields:
    string = mergeAdjacentTexCmds(string, '\text');

    % '\\' has to be escaped to '\textbackslash{}'
    % This cannot be done with strrep(...) as it would replace e.g. 4 backslashes
    % with three times the replacement string because it finds overlapping matches
    % (see http://www.mathworks.de/help/techdoc/ref/strrep.html)
    % Note: Octave's backslash handling is broken. Even though its output does
    % not resemble MATLAB's, the same m2t code is used for either software. That
    % way MATLAB-compatible code produces the same matlab2tikz output no matter
    % which software it's executed in. So long as this MATLAB incompatibility
    % remains in Octave you're probably better off not using backslashes in TeX
    % text anyway.
    string = regexprep(string, '(\\)\\', '$1textbackslash{}');

    % '_', '^', '{', and '}' are already escaped properly, even in MATLAB's TeX
    % dialect (and if they're not, that's intentional)

    % Escape "$", "%", and "#" to make them compatible to true TeX while in
    % MATLAB/Octave they are not escaped
    string = strrep(string, '$', '\$');
    string = strrep(string, '%', '\%');
    string = strrep(string, '#', '\#');

    % Escape "§" as "\S" since it can give UTF-8 problems otherwise.
    % The TeX string 'a_§' in particular lead to problems in Octave 3.6.0.
    % m2t transcoded that string into '$\text{a}_\text{*}\text{#}$' with
    % * = 0xC2 and # = 0xA7 which corresponds with the two-byte UTF-8
    % encoding. Even though this looks like an Octave bug that shows
    % during the '..._\text{abc}' to '..._\text{a}\text{bc}' conversion,
    % it's best to include the workaround here.
    string = strrep(string, '§', '\S{}');

    string = escapeAmpersands(m2t, string, origstr);
    string = escapeTildes(m2t, string, origstr);

    % Convert '..._\text{abc}' and '...^\text{abc}' to '..._\text{a}\text{bc}'
    % and '...^\text{a}\text{bc}', respectively.
    % Things get a little more complicated if instead of 'a' it's e.g. '$'. The
    % latter has been converted to '\$' by now and simply extracting the first
    % character from '\text{\$bc}' would result in '\text{$}\text{$bc}' which
    % is syntactically wrong. Instead the whole command '\$' has to be moved in
    % front of the \text{...} block, e.g. '..._\text{\$bc}' -> '..._\$\text{bc}'.
    % Note that the problem does not occur for the majority of special characters
    % like '\alpha' because they use math mode and therefore are never inside a
    % \text{...} block to begin with. This means that the number of special
    % characters affected by this issue is actually quite small:
    %   $ # % & _ { } \o § ~ \ ^
    expr = ['(_|\^)(\\text)\{([^}\\]|\\\$|\\#|\\%|\\&|\\_|\\\{|\\\}|', ...
        ... %   (_/^)(\text) {(non-}\| \$ | \#| \%| \&| \_| \{ | \} |
        ... %   ($1)( $2 )  (                 $3                      ->
        '\\o\{\}|\\S\{\}|\\textasciitilde\{\}|\\textbackslash\{\}|', ...
        ... %    \o{}  | \S{}  | \textasciitilde{}  | \textbackslash{}  |
        ... %  <-                         $3                                 ->
        '\\textasciicircum\{\})'];
    %    \textasciicircum{} )
    %  <-      $3           )
    string = regexprep(string, expr, '$1$2{$3}$2{');

    string = parseStringsAsMath(m2t, string);

    % Clean up: remove empty \text{}
    string = strrep(string, '\text{}', '');
    % \text{}\alpha{}\text{...} -> \alpha{}\text{...}

    % Clean up: convert '{}\' to '\' unless it's prefixed by a backslash which
    % means the opening brace is escaped and thus a printable character instead
    % of a grouping brace.
    string = regexprep(string, '(?<!\\)\{\}(\\)', '$1');
    % \alpha{}\text{...} -> \alpha\text{...}

    % Clean up: convert '{}}' to '}' unless it's prefixed by a backslash
    string = regexprep(string, '(?<!\\)\{\}\}', '}');

    % Clean up: remove '{}' at the end of 'string' unless it's prefixed by a
    % backslash
    string = regexprep(string, '(?<!\\)\{\}$', '');
end
% ==============================================================================
function string = escapeTildes(m2t, string, origstr)
    % Escape plain "~" in MATLAB and replace escaped "\~" in Octave with a proper
    % escape sequence. An un-escaped "~" produces weird output in Octave, thus
    % give a warning in that case
    switch getEnvironment
        case 'MATLAB'
            string = strrep(string, '~', '\textasciitilde{}'); % or '\~{}'
        case 'Octave'
            string = strrep(string, '\~', '\textasciitilde{}'); % ditto
            if regexp(string, '(?<!\\)~')
                userWarning(m2t,                                     ...
                    ['TeX string ''%s'' contains un-escaped ''~''. ' ...
                    'For proper display in Octave you probably '     ...
                    'want to escape it even though that''s '         ...
                    'incompatible with MATLAB. '                     ...
                    'In the matlab2tikz output it will have its '    ...
                    'usual TeX function as a non-breaking space.'],  ...
                    origstr)
            end
        otherwise
            errorUnknownEnvironment();
    end
end
% ==============================================================================
function string = escapeAmpersands(m2t, string, origstr)
    % Escape plain "&" in MATLAB and replace it and the following character with
    % a space in Octave unless the "&" is already escaped
    switch getEnvironment
        case 'MATLAB'
            string = strrep(string, '&', '\&');
        case 'Octave'
            % Ampersands should already be escaped in Octave.
            % Octave (tested with 3.6.0) handles un-escaped ampersands a little
            % funny in that it removes the following character, if there is one:
            % 'abc&def'      -> 'abc ef'
            % 'abc&\deltaef' -> 'abc ef'
            % 'abc&$ef'      -> 'abc ef'
            % 'abcdef&'      -> 'abcdef'
            % Don't remove closing brace after '&' as this would result in
            % unbalanced braces
            string = regexprep(string, '(?<!\\)&(?!})', ' ');
            string = regexprep(string, '(?<!\\)&}', '}');
            if regexp(string, '(?<!\\)&\\')
                % If there's a backslash after the ampersand, that means not only
                % the backslash should be removed but the whole escape sequence,
                % e.g. '\delta' or '\$'. Actually the '\delta' case is the
                % trickier one since by now 'string' would have been turned from
                % 'abc&\deltaef' into '\text{abc&}\delta{}\text{ef}', i.e. after
                % the ampersand first comes a closing brace and then '\delta';
                % the latter as well as the ampersand itself should be removed
                % while the brace must remain in place to avoid unbalanced braces.
                userWarning(m2t,                                                ...
                    ['TeX string ''%s'' contains a special character '  ...
                    'after an un-escaped ''&''. The output generated ' ...
                    'by matlab2tikz will not precisely match that '    ...
                    'which you see in Octave itself in that the '      ...
                    'special character and the preceding ''&'' is '    ...
                    'not replaced with a space.'], origstr)
            end
        otherwise
            errorUnknownEnvironment();
    end
end
% ==============================================================================
function [string] = parseStringsAsMath(m2t, string)
    % Some further processing makes the output behave more like TeX math mode,
    % but only if the matlab2tikz parameter parseStringsAsMath=true.
    if m2t.args.parseStringsAsMath

        % Some characters should be in math mode: =-+/,.()<>0-9
        expr = '(\\text)\{([^}=\-+/,.()<>0-9]*)([=\-+/,.()<>0-9]+)([^}]*)\}';
        %    \text  {(any non-"x"/'}'char)( any "x" char  )(non-}) }
        %  ( $1 )  (       $2        )(      $3       )( $4)
        while regexp(string, expr)
            % Iterating is necessary to catch all occurrences. See above.
            string = regexprep(string, expr, '$1{$2}$3$1{$4}');
        end

        % \text{ } should be a math-mode space
        string = regexprep(string, '\\text\{(\s+)}', '$1');

        % '<<' probably means 'much smaller than', i.e. '\ll'
        repl = switchMatOct('$1\\ll{}$2', '$1\ll{}$2');
        string = regexprep(string, '([^<])<<([^<])', repl);

        % '>>' probably means 'much greater than', i.e. '\gg'
        repl = switchMatOct('$1\\gg{}$2', '$1\gg{}$2');
        string = regexprep(string, '([^>])>>([^>])', repl);

        % Single letters are most likely variables and thus should be in math mode
        string = regexprep(string, '\\text\{([a-zA-Z])\}', '$1');

    end
end
% ==============================================================================
function tex = fonts2tex(fonts)
    % Returns a tex command for each fontname in the cell array fonts.
    if ~iscell(fonts)
        error('matlab2tikz:fonts2tex', ...
                 'Expecting a cell array as input.');
    end
    tex = cell(size(fonts));

    for ii = 1:numel(fonts)
        font = fonts{ii}{1};

        % List of known fonts.
        switch lower(font)
            case 'courier'
                tex{ii} = '\ttfamily{}';
            case 'times'
                tex{ii} = '\rmfamily{}';
            case {'arial', 'helvetica'}
                tex{ii} = '\sffamily{}';
            otherwise
                warning('matlab2tikz:fonts2tex', ...
                    'Unknown font ''%s''. Using tex default font.',font);
                % Unknown font -> Switch to standard font.
                tex{ii} = '\rm{}';
        end
    end
end
% ==============================================================================
function string = mergeAdjacentTexCmds(string, cmd)
    % Merges adjacent tex commands like \text into one command
    % If necessary, add a backslash
    if cmd(1) ~= '\'
        cmd = ['\' cmd];
    end
    % Link each bracket to the corresponding bracket
    link = zeros(size(string));
    pos = [regexp([' ' string], '([^\\]{)'), ...
        regexp([' ' string], '([^\\]})')];
    pos = sort(pos);
    ii = 1;
    while ii <= numel(pos)
        if string(pos(ii)) == '}'
            link(pos(ii-1)) = pos(ii);
            link(pos(ii)) = pos(ii - 1);
            pos([ii-1, ii]) = [];
            ii = ii - 1;
        else
            ii = ii + 1;
        end
    end
    % Find dispensable commands
    pos = regexp(string, ['}\' cmd '{']);
    delete = zeros(0,1);
    len = numel(cmd);
    for p = pos
        l = link(p);
        if l > len && isequal(string(l-len:l-1), cmd)
            delete(end+1,1) = p;
        end
    end
    %   3. Remove these commands (starting from the back
    delete = repmat(delete, 1, len+2) + repmat(0:len+1,numel(delete), 1);
    string(delete(:)) = [];
end
function dims = pos2dims(pos)
    % Position quadruplet [left, bottom, width, height] to dimension structure
    dims = struct('left' , pos(1), 'bottom', pos(2));
    if numel(pos) == 4
        dims.width  = pos(3);
        dims.height = pos(4);
        dims.right  = dims.left   + dims.width;
        dims.top    = dims.bottom + dims.height;
    end
end
% OPTION ARRAYS ================================================================
function opts = opts_new()
    % create a new options array
    opts = cell(0,2);
end
function opts = opts_add(opts, key, value)
    % add a key-value pair to an options array (with duplication check)
    if ~exist('value','var')
        value = '';
    end
    value = char(value);

    % Check if the key already exists.
    if opts_has(opts, key)
        oldValue = opts_get(opts, key);
        if isequal(value, oldValue)
            return; % no action needed: value already present
        else
            error('matlab2tikz:opts_add', ...
                 ['Trying to add (%s, %s) to options, but it already ' ...
                  'contains the conflicting key-value pair (%s, %s).'], ...
                  key, value, key, oldValue);
        end
    end
    opts = opts_append(opts, key, value);
end
function opts = opts_addSubOpts(opts, key, subOpts)
    % add a key={Opts} pair to an options array
    formatted = ['{' opts_print(subOpts) '}'];
    opts      = opts_add(opts, key, formatted);
end
function bool = opts_has(opts, key)
    % returns true if the options array contains the key
    bool = ~isempty(opts) && ismember(key, opts(:,1));
end
function value = opts_get(opts, key)
    % returns the value(s) stored for a key in an options array
    idx = find(ismember(opts(:,1), key));
    switch numel(idx)
        case 1
            value = opts{idx,2}; % just the value
        otherwise
            value = opts(idx,2); % as cell array
    end
end
function opts = opts_append(opts, key, value)
    % append a key-value pair to an options array (duplicate keys allowed)
    if ~exist('value','var')
        value = '';
    end
    value = char(value);
    if ~(opts_has(opts, key) && isequal(opts_get(opts, key), value))
        opts = cat(1, opts, {key, value});
    end
end
function opts = opts_append_userdefined(opts, userDefined)
    % appends user-defined options to an options array
    % the userDefined options can come either as a single string or a cellstr that
    % is already TikZ-formatted. The internal 2D cell format is NOT supported.
    if ~isempty(userDefined)
        if ischar(userDefined)
            userDefined = {userDefined};
        end
        for k = 1:length(userDefined)
            opts = opts_append(opts, userDefined{k});
        end
    end
end
function opts = opts_copy(opts_from, name_from, opts, name_to)
    % copies an option (if it exists) from one option array to another one
    if ~exist('name_to', 'var') || isempty(name_to)
        name_to = name_from;
    end
    if opts_has(opts_from, name_from)
        value = opts_get(opts_from, name_from);
        opts = opts_append(opts, name_to, value);
    end
end
function opts = opts_remove(opts, varargin)
    % remove some key-value pairs from an options array
    keysToDelete = varargin;
    idxToDelete = ismember(opts(:,1), keysToDelete);
    opts(idxToDelete, :) = [];
end
function opts = opts_merge(opts, varargin)
    % merge multiple options arrays
    for jArg = 1:numel(varargin)
        opts2 = varargin{jArg};
        for k = 1:size(opts2, 1)
            opts = opts_append(opts, opts2{k,1}, opts2{k,2});
        end
    end
end
function str = opts_print(opts, sep)
    % pretty print an options array
    if ~exist('sep','var') || ~ischar(sep)
        sep = ', ';
    end
    nOpts = size(opts,1);
    c = cell(1,nOpts);
    for k = 1:nOpts
        if isempty(opts{k,2})
            c{k} = sprintf('%s', opts{k,1});
        else
            c{k} = sprintf('%s=%s', opts{k,1}, opts{k,2});
        end
    end
    str = m2tstrjoin(c, sep);
end
% ==============================================================================
function m2t = m2t_addAxisOption(m2t, key, value)
    % Adds an option to the last axesContainer
    if ~exist('value','var')
        value = '';
    end
    m2t.axes{end}.options = opts_add(m2t.axes{end}.options, key, value);
end
% ==============================================================================
function bool = isHG2()
    % Checks if graphics system is HG2 (true) or HG1 (false).
    % HG1 : MATLAB up to R2014a and currently all OCTAVE versions
    % HG2 : MATLAB starting from R2014b (version 8.4)
    [env, envVersion] = getEnvironment();
    bool = strcmpi(env,'MATLAB') && ~isVersionBelow(envVersion, [8,4]);
end
% ==============================================================================
function str = formatAspectRatio(m2t, values)
    % format the aspect ratio. Behind the scenes, formatDim is used
    strs = arrayfun(@formatDim, values, 'UniformOutput', false);
    str = join(m2t, strs, ' ');
end
% ==============================================================================
function str = formatDim(value, unit)
    % format the value for use as a TeX dimension
    if ~exist('unit','var') || isempty(unit)
        unit = '';
    end
    tolerance = 1e-7;
    value  = round(value/tolerance)*tolerance;
    if value == 1 && ~isempty(unit) && unit(1) == '\'
        str = unit; % just use the unit
    else
        % LaTeX has support for single precision (about 6.5 decimal places),
        % but such accuracy is overkill for positioning. We clip to three
        % decimals to overcome numerical rounding issues that tend to be very
        % platform and version dependent. See also #604.
        str = sprintf('%.3f', value);
        str = regexprep(str, '(\d*\.\d*?)0+$', '$1'); % remove trailing zeros
        str = regexprep(str, '\.$', ''); % remove trailing period
        str = [str unit];
    end
end
% ==============================================================================
function [retval] = switchMatOct(matlabValue, octaveValue)
    % Returns a different value for MATLAB and Octave
    switch getEnvironment
        case 'MATLAB'
            retval = matlabValue;
        case 'Octave'
            retval = octaveValue;
        otherwise
            errorUnknownEnvironment();
    end
end
% ==============================================================================
function checkDeprecatedEnvironment(minimalVersions)
    [env, envVersion] = getEnvironment();
    if isfield(minimalVersions, env)
        minVersion = minimalVersions.(env);
        envWithVersion = sprintf('%s %s', env, minVersion.name);

        if isVersionBelow(envVersion, minVersion.num)
            ID = 'matlab2tikz:deprecatedEnvironment';

            warningMessage = ['\n', repmat('=',1,80), '\n\n', ...
                '  matlab2tikz is tested and developed on   %s   and newer.\n', ...
                '  This script may still be able to handle your plots, but if you\n', ...
                '  hit a bug, please consider upgrading your environment first.\n', ...
                '  Type "warning off %s" to suppress this warning.\n', ...
                '\n', repmat('=',1,80), ];
            warning(ID, warningMessage, envWithVersion, ID);

        end
    else
        errorUnknownEnvironment();
    end
end
% ==============================================================================
function m2t = needsPgfplotsVersion(m2t, minVersion)
    if isVersionBelow(m2t.pgfplotsVersion, minVersion)
        m2t.pgfplotsVersion = minVersion;
    end
end
% ==============================================================================
function str = formatPgfplotsVersion(version)
    version = versionArray(version);
    if all(isfinite(version))
        str = sprintf('%d.',version);
        str = str(1:end-1); % remove the last period
    else
        str = 'newest';
    end
end
% ==============================================================================
function [formatted,treeish] = VersionControlIdentifier()
    % This function gives the (git) commit ID of matlab2tikz
    %
    % This assumes the standard directory structure as used by Nico's master branch:
    %     SOMEPATH/src/matlab2tikz.m with a .git directory in SOMEPATH.
    %
    % The HEAD of that repository is determined from file system information only
    % by following dynamic references (e.g. ref:refs/heds/master) in branch files
    % until an absolute commit hash (e.g. 1a3c9d1...) is found.
    % NOTE: Packed branch references are NOT supported by this approach
    MAXITER     = 10; % stop following dynamic references after a while
    formatted   = '';
    REFPREFIX   = 'ref:';
    isReference = @(treeish)(any(strfind(treeish, REFPREFIX)));
    treeish     = [REFPREFIX 'HEAD'];
    try
        % get the matlab2tikz directory
        m2tDir = fileparts(mfilename('fullpath'));
        gitDir = fullfile(m2tDir,'..','.git');

        nIter = 1;
        while isReference(treeish)
            refName    = treeish(numel(REFPREFIX)+1:end);
            branchFile = fullfile(gitDir, refName);

            if exist(branchFile, 'file') && nIter < MAXITER
                % The FID is reused in every iteration, so `onCleanup` cannot
                % be used to `fclose(fid)`. But since there is very little that
                % can go wrong in a single `fscanf`, it's probably best to leave
                % this part as it is for the time being.
                fid     = fopen(branchFile,'r');
                treeish = fscanf(fid,'%s');
                fclose(fid);
                nIter   = nIter + 1;
            else % no branch file or iteration limit reached
                treeish = '';
                return;
            end
        end
    catch
        treeish = '';
    end
    if ~isempty(treeish)
        formatted = sprintf('(commit %s)',treeish);
    end
end
% ==============================================================================
function bool = isCategoricalType(data)
    % This is a wrapper function for iscategorical(), which (as February 2018) 
    % is not available on GNU Octave.
    switch getEnvironment()
        case 'MATLAB'
            bool = iscategorical(data);
        case 'Octave'
            bool = false;
        otherwise
            errorUnknownEnvironment();
    end
end
% ==============================================================================
