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 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)'} % 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); % . % 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= 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('%s', 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 /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'); table = regexprep(table, '\', '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, '(?= 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 = '(? $ \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, '(?' 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, '(? \alpha\text{...} % Clean up: convert '{}}' to '}' unless it's prefixed by a backslash string = regexprep(string, '(? '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, '(?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 % ==============================================================================