| 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. | |
| % | |
| % 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 do that by default. | |
| % (default: false) | |
| % | |
| % MATLAB2TIKZ('tikzFileComment',CHAR,...) adds a custom comment to the header | |
| % of the output file. (default: '') | |
| % | |
| % MATLAB2TIKZ('automaticLabels',BOOL,...) determines whether to automatically | |
| % add labels to plots (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) | |
| % | |
| % Example | |
| % x = -pi:pi/10:pi; | |
| % y = tan(sin(x)) - sin(tan(x)); | |
| % plot(x,y,'--rs'); | |
| % matlab2tikz('myfile.tex'); | |
| % | |
| % Copyright (c) 2008--2015, Nico Schlömer <nico.schloemer@gmail.com> | |
| % All rights reserved. | |
| % | |
| % Redistribution and use in source and binary forms, with or without | |
| % modification, are permitted provided that the following conditions are | |
| % met: | |
| % | |
| % * Redistributions of source code must retain the above copyright | |
| % notice, this list of conditions and the following disclaimer. | |
| % * Redistributions in binary form must reproduce the above copyright | |
| % notice, this list of conditions and the following disclaimer in | |
| % the documentation and/or other materials provided with the distribution | |
| % | |
| % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
| % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| % POSSIBILITY OF SUCH DAMAGE. | |
| % Note: | |
| % This program was based on Paul Wagenaars' Matfig2PGF that can be | |
| % found on http://www.mathworks.com/matlabcentral/fileexchange/12962 . | |
| %% 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.cmdOpts = []; | |
| m2t.currentHandles = []; | |
| m2t.transform = []; % For hgtransform groups | |
| m2t.pgfplotsVersion = [1,3]; | |
| m2t.name = 'matlab2tikz'; | |
| m2t.version = '1.0.0'; | |
| m2t.author = 'Nico Schlömer'; | |
| m2t.authorEmail = 'nico.schloemer@gmail.com'; | |
| m2t.years = '2008--2015'; | |
| m2t.website = 'http://www.mathworks.com/matlabcentral/fileexchange/22022-matlab2tikz-matlab2tikz'; | |
| VCID = VersionControlIdentifier(); | |
| m2t.versionFull = strtrim(sprintf('v%s %s', m2t.version, VCID)); | |
| m2t.tol = 1.0e-15; % numerical tolerance (e.g. used to test equality of doubles) | |
| m2t.imageAsPngNo = 0; | |
| m2t.dataFileNo = 0; | |
| m2t.quiverId = 0; % identification flag for quiver plot styles | |
| m2t.automaticLabelIndex = 0; | |
| % definition of color depth | |
| m2t.colorDepth = 48; %[bit] RGB color depth (typical values: 24, 30, 48) | |
| m2t.colorPrecision = 2^(-m2t.colorDepth/3); | |
| m2t.colorFormat = sprintf('%%0.%df',ceil(-log10(m2t.colorPrecision))); | |
| % 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', ... | |
| '\\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', true, @islogical); | |
| 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, '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); | |
| % 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'); | |
| %% Finally parse all the arguments | |
| ipp = ipp.parse(ipp, varargin{:}); | |
| m2t.cmdOpts = ipp; % store the input parser 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.']); | |
| % The following color RGB-values which will need to be defined. | |
| % 'extraRgbColorNames' contains their designated names, 'extraRgbColorSpecs' | |
| % their specifications. | |
| [m2t.extraRgbColorNames, m2t.extraRgbColorSpecs] = ... | |
| dealColorDefinitions(m2t.cmdOpts.Results.extraColors); | |
| %% shortcut | |
| m2t.ff = m2t.cmdOpts.Results.floatFormat; | |
| %% add global elements | |
| if isempty(m2t.cmdOpts.Results.figurehandle) | |
| error('matlab2tikz:figureNotFound','MATLAB figure not found.'); | |
| end | |
| m2t.currentHandles.gcf = m2t.cmdOpts.Results.figurehandle; | |
| if m2t.cmdOpts.Results.colormap | |
| m2t.currentHandles.colormap = m2t.cmdOpts.Results.colormap; | |
| else | |
| m2t.currentHandles.colormap = get(m2t.currentHandles.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.cmdOpts.Results.relativeDataPath) | |
| if ~isempty(m2t.cmdOpts.Results.relativePngPath) | |
| %NOTE: eventually break backwards compatibility of relative PNG path | |
| m2t.relativeDataPath = m2t.cmdOpts.Results.relativePngPath; | |
| userWarning(m2t, ['Using "relativePngPath" for "relativeDataPath".', ... | |
| ' This will stop working in a future release.']); | |
| else | |
| m2t.relativeDataPath = m2t.cmdOpts.Results.relativeDataPath; | |
| end | |
| else | |
| m2t.relativeDataPath = m2t.cmdOpts.Results.relativeDataPath; | |
| end | |
| if isempty(m2t.cmdOpts.Results.dataPath) | |
| m2t.dataPath = fileparts(m2t.tikzFileName); | |
| else | |
| m2t.dataPath = m2t.cmdOpts.Results.dataPath; | |
| end | |
| 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.name, m2t.versionFull) | |
| %% Check for a new matlab2tikz version outside version control | |
| if m2t.cmdOpts.Results.checkForUpdates && isempty(VCID) | |
| isUpdateInstalled = m2tUpdater(... | |
| m2t.name, ... | |
| m2t.website, ... | |
| m2t.version, ... | |
| m2t.cmdOpts.Results.showInfo, ... | |
| getEnvironment... | |
| ); | |
| % Terminate conversion if update was successful (the user is notified | |
| % by the updater) | |
| if isUpdateInstalled, return, end | |
| end | |
| %% print some version info to the screen | |
| versionInfo = ['The latest updates can be retrieved from\n' ,... | |
| ' %s\n' ,... | |
| 'where you can also make suggestions and rate %s.\n' ,... | |
| 'For usage instructions, bug reports, the latest ' ,... | |
| 'development versions and more, see\n' ,... | |
| ' https://github.com/matlab2tikz/matlab2tikz,\n' ,... | |
| ' https://github.com/matlab2tikz/matlab2tikz/wiki,\n' ,... | |
| ' https://github.com/matlab2tikz/matlab2tikz/issues.\n']; | |
| userInfo(m2t, versionInfo, m2t.website, m2t.name); | |
| %% Save the figure as TikZ to file | |
| saveToFile(m2t, fid, fileWasOpen); | |
| end | |
| % ============================================================================== | |
| function [m2t, fid, fileWasOpen] = openFileForOutput(m2t) | |
| % opens the output file and/or show a dialog to select one | |
| if ~isempty(m2t.cmdOpts.Results.filehandle) | |
| fid = m2t.cmdOpts.Results.filehandle; | |
| fileWasOpen = true; | |
| if ~isempty(m2t.cmdOpts.Results.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.cmdOpts.Results.filename) | |
| filename = m2t.cmdOpts.Results.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 fid = fileOpenForWrite(m2t, filename) | |
| encoding = switchMatOct({'native', m2t.cmdOpts.Results.encoding}, {}); | |
| fid = fopen(filename, 'w', encoding{:}); | |
| 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. | |
| % It is important to turn hidden handles on, as visible lines (such as the | |
| % axes in polar plots, for example), are otherwise hidden from their | |
| % parental handles (and can hence not be discovered by matlab2tikz). | |
| % With ShowHiddenHandles 'on', there is no escape. :) | |
| set(0, 'ShowHiddenHandles', 'on'); | |
| % get all axes handles | |
| [m2t, axesHandles] = findPlotAxes(m2t, m2t.currentHandles.gcf); | |
| % Turn around the handles vector to make sure that plots that appeared | |
| % first also appear first in the vector. This has effects on the alignment | |
| % and the order in which the plots appear in the final TikZ file. | |
| % In fact, this is not really important but makes things more 'natural'. | |
| axesHandles = axesHandles(end:-1:1); | |
| % Alternative Positioning of axes. | |
| % Select relevant Axes and draw them. | |
| [m2t, axesBoundingBox] = getRelevantAxes(m2t, axesHandles); | |
| m2t.axesBoundingBox = axesBoundingBox; | |
| m2t.axesContainers = {}; | |
| 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.axesContainers | |
| m2t.content = addChildren(m2t.content, axesContainer); | |
| end | |
| set(0, 'ShowHiddenHandles', 'off'); | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % actually print the stuff | |
| minimalPgfplotsVersion = formatPgfplotsVersion(m2t.pgfplotsVersion); | |
| m2t.content.comment = sprintf('This file was created by %s.\n', m2t.name); | |
| if m2t.cmdOpts.Results.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.website, m2t.name ) ... | |
| ]; | |
| end | |
| userInfo(m2t, 'You will need pgfplots version %s or newer to compile the TikZ output.',... | |
| minimalPgfplotsVersion); | |
| % Add custom comment. | |
| if ~isempty(m2t.cmdOpts.Results.tikzFileComment) | |
| m2t.content.comment = [m2t.content.comment, ... | |
| sprintf('\n%s\n', m2t.cmdOpts.Results.tikzFileComment) | |
| ]; | |
| end | |
| m2t.content.name = 'tikzpicture'; | |
| % Add custom TikZ options if any given. | |
| m2t.content.options = opts_append_userdefined(m2t.content.options, ... | |
| m2t.cmdOpts.Results.extraTikzpictureOptions); | |
| m2t.content.colors = generateColorDefinitions(m2t.extraRgbColorNames, ... | |
| m2t.extraRgbColorSpecs, m2t.colorFormat); | |
| % 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 | |
| addComments(fid, m2t.content.comment); | |
| addStandalone(m2t, fid, 'preamble'); | |
| addCustomCode(fid, '', m2t.cmdOpts.Results.extraCode, ''); | |
| addStandalone(m2t, fid, 'begin'); | |
| printAll(m2t, m2t.content, fid); % actual plotting happens here | |
| addCustomCode(fid, '\n', m2t.cmdOpts.Results.extraCodeAtEnd, ''); | |
| addStandalone(m2t, fid, 'end'); | |
| end | |
| % ============================================================================== | |
| function addStandalone(m2t, fid, part) | |
| % writes a part of a standalone LaTeX file definition | |
| if m2t.cmdOpts.Results.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(names, specs, colorFormat) | |
| % output the color definitions to LaTeX | |
| str = ''; | |
| ff = colorFormat; | |
| if ~isempty(names) | |
| m2t.content.colors = sprintf('%%\n%% defining custom colors\n'); | |
| for k = 1:length(names) | |
| % make sure to append with '%' to avoid spacing woes | |
| str = [str, ... | |
| sprintf(['\\definecolor{%s}{rgb}{', ff, ',', ff, ',', ff,'}%%\n'], ... | |
| names{k}', specs{k})]; | |
| end | |
| str = [str 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 = findobj(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 = findobj(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 = findobj(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 = findobj(axesHandles, 'Tag','scribeOverlay'); | |
| idx = ~ismember(axesHandles, m2t.scribeLayer); | |
| axesHandles = axesHandles(idx); | |
| end | |
| % ============================================================================== | |
| function addComments(fid, comment) | |
| % prints TeX comments to file stream |fid| | |
| if ~isempty(comment) | |
| newline = sprintf('\n'); | |
| newlineTeX = sprintf('\n%%'); | |
| fprintf(fid, '%% %s\n', strrep(comment, newline, newlineTeX)); | |
| 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 = get(h, 'Children'); | |
| % prepare cell array of pgfEnvironments | |
| pgfEnvironments = cell(0); | |
| % 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)' | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| [m2t, legendString, interpreter] = findLegendInformation(m2t, child); | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| switch char(get(child, 'Type')) | |
| % 'axes' environments are treated separately. | |
| case 'line' | |
| [m2t, str] = drawLine(m2t, child); | |
| case 'patch' | |
| [m2t, str] = drawPatch(m2t, child); | |
| case 'image' | |
| [m2t, str] = drawImage(m2t, child); | |
| case {'hggroup', 'matlab.graphics.primitive.Group', ... | |
| 'scatter', 'bar', 'stair', 'stem' ,'errorbar', 'area', ... | |
| 'quiver','contour'} | |
| [m2t, str] = drawHggroup(m2t, child); | |
| 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] = drawSurface(m2t, child); | |
| case 'text' | |
| [m2t, str] = drawVisibleText(m2t, child); | |
| case 'rectangle' | |
| [m2t, str] = drawRectangle(m2t, child); | |
| case 'histogram' | |
| [m2t, str] = drawHistogram(m2t, child); | |
| case {'uitoolbar', 'uimenu', 'uicontextmenu', 'uitoggletool',... | |
| 'uitogglesplittool', 'uipushtool', 'hgjavacomponent'} | |
| % don't to anything for these handles and its children | |
| str = ''; | |
| case '' | |
| warning('matlab2tikz:NoChildren',... | |
| ['No children found for handle %d. ',... | |
| 'Carrying on as if nothing happened'], double(h)); | |
| otherwise | |
| error('matlab2tikz:handleAllChildren', ... | |
| 'I don''t know how to handle this object: %s\n', ... | |
| get(child, 'Type')); | |
| end | |
| str = addLegendInformation(m2t, str, legendString, interpreter); | |
| % append the environment | |
| pgfEnvironments{end+1} = str; | |
| end | |
| end | |
| % ============================================================================== | |
| function [m2t, legendString, interpreter] = findLegendInformation(m2t, child) | |
| % Check if 'child' is referenced in a legend. | |
| % If yes, some plot types may want to add stuff (e.g. 'forget plot'). | |
| % Add '\addlegendentry{...}' then after the plot. | |
| legendString = ''; | |
| interpreter = ''; | |
| hasLegend = false; | |
| if isempty(child) | |
| return; % an empty (i.e. non-existent) child cannot have a legend entry | |
| end | |
| % Check if current handle is referenced in a legend. | |
| switch getEnvironment | |
| case 'MATLAB' | |
| [legendString, interpreter, hasLegend] = findLegendInfoMATLAB(m2t, child); | |
| case 'Octave' | |
| % Octave does not store a reference to the legend entry in the | |
| % plotted objects. It references the plotted objects in reverse, | |
| % in the legend's 'deletefcn' property. | |
| % The variable m2t.gcaAssociatedLegend is set in drawAxes(). | |
| if ~isempty(m2t.gcaAssociatedLegend) | |
| delfun = get(m2t.gcaAssociatedLegend,'deletefcn'); | |
| legendEntryPeers = delfun{6}; % See set(hlegend, "deletefcn", {@deletelegend2, ca, [], [], t1, hplots}); in legend.m | |
| hasLegend = ismember(child, legendEntryPeers); | |
| interpreter = get(m2t.gcaAssociatedLegend, 'interpreter'); | |
| legendString = getOrDefault(child,'displayname',''); | |
| end | |
| otherwise | |
| errorUnknownEnvironment(); | |
| end | |
| % split string to cell, if newline character '\n' (ASCII 10) is present | |
| delimeter = sprintf('\n'); | |
| legendString = regexp(legendString,delimeter,'split'); | |
| m2t.currentHandleHasLegend = hasLegend && ~isempty(legendString); | |
| end | |
| % ============================================================================== | |
| function [legendString, interpreter, hasLegend] = findLegendInfoMATLAB(m2t, child) | |
| % finds the legend info in MATLAB (only!). Eventually this could be merged back | |
| % into `findLegendInformation` | |
| legendString = ''; | |
| interpreter = ''; | |
| hasLegend = false; | |
| %FIXME: this part (e.g. fall back objects) should be restructured. | |
| for legendHandle = m2t.legendHandles(:)' | |
| ud = get(legendHandle, 'UserData'); | |
| if isfield(ud, 'handles') | |
| plotChildren = ud.handles; | |
| else | |
| plotChildren = getOrDefault(legendHandle, 'PlotChildren', []); | |
| end | |
| k = find(child == plotChildren); | |
| if isempty(k) | |
| % Lines of error bar plots are not referenced | |
| % directly in legends as an error bars plot contains | |
| % two "lines": the data and the deviations. Here, the | |
| % legends refer to the specgraph.errorbarseries | |
| % handle which is 'Parent' to the line handle. | |
| k = find(get(child,'Parent') == plotChildren); | |
| end | |
| if ~isempty(k) | |
| % Legend entry found. Add it to the plot. | |
| hasLegend = true; | |
| interpreter = get(legendHandle, 'Interpreter'); | |
| if ~isempty(ud) && isfield(ud,'strings') | |
| legendString = ud.lstrings(k); | |
| else | |
| legendString = get(child, 'DisplayName'); | |
| end | |
| end | |
| end | |
| end | |
| % ============================================================================== | |
| function str = addLegendInformation(m2t, str, legendString, interpreter) | |
| % Add legend after the plot data. | |
| % The test for ischar(str) && ~isempty(str) is a workaround for hggroups; | |
| % the output might not necessarily be a string, but a cellstr. | |
| if ischar(str) && ~isempty(str) && m2t.currentHandleHasLegend | |
| c = prettyPrint(m2t, legendString, interpreter); | |
| % We also need a legend alignment option to make multiline | |
| % legend entries work. This is added by default in getLegendOpts(). | |
| str = [str, sprintf('\\addlegendentry{%s};\n\n', join(m2t, c, '\\'))]; | |
| end | |
| 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.axesContainers{end+1} = struct('handle', handle, ... | |
| 'name', '', ... | |
| 'comment', [], ... | |
| 'options', {opts_new()}, ... | |
| 'content', {cell(0)}, ... | |
| 'children', {cell(0)}); | |
| % update gca | |
| m2t.currentHandles.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.axesContainers{end}.is3D = isAxis3D(handle); | |
| % Flag if axis contains barplot | |
| m2t.axesContainers{end}.barAddedAxisOption = false; | |
| m2t.gcaAssociatedLegend = getAssociatedLegend(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.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, 'point meta min', sprintf(m2t.ff, clim(1))); | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, 'point meta max', sprintf(m2t.ff, clim(2))); | |
| end | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % Recurse into the children of this environment. | |
| [m2t, childrenEnvs] = handleAllChildren(m2t, handle); | |
| m2t.axesContainers{end} = addChildren(m2t.axesContainers{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.axesContainers{end}.options = opts_merge(m2t.axesContainers{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. Well. | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, 'hide axis', []); | |
| % % An invisible axes container *can* have visible children, so don't | |
| % % immediately bail out here. | |
| % children = get(handle, 'Children'); | |
| % for child = children(:)' | |
| % if isVisible(child) | |
| % % If the axes contain something that's visible, add an invisible | |
| % % axes pair. | |
| % m2t.axesContainers{end}.name = 'axis'; | |
| % m2t.axesContainers{end}.options = {m2t.axesContainers{end}.options{:}, ... | |
| % 'hide x axis', 'hide y axis'}; | |
| % m2t.axesContainers{end}.comment = getTag(handle); | |
| % break; | |
| % end | |
| % end | |
| % % recurse into the children of this environment | |
| % [m2t, childrenEnvs] = handleAllChildren(m2t, handle); | |
| % m2t.axesContainers{end} = addChildren(m2t.axesContainers{end}, childrenEnvs); | |
| % return | |
| end | |
| m2t.axesContainers{end}.name = 'axis'; | |
| m2t = drawBackgroundOfAxes(m2t, handle); | |
| m2t = drawTitleOfAxes(m2t, handle); | |
| m2t = drawBoxAndLineLocationsOfAxes(m2t, handle); | |
| m2t = drawGridOfAxes(m2t, handle); | |
| m2t = drawLegendOptionsOfAxes(m2t, handle); | |
| m2t.axesContainers{end}.options = opts_append_userdefined(... | |
| m2t.axesContainers{end}.options, m2t.cmdOpts.Results.extraAxisOptions); | |
| end | |
| % ============================================================================== | |
| function m2t = drawGridOfAxes(m2t, handle) | |
| % draws the grids of an axes | |
| %TODO: has{XYZ}Grid is always false without a good reason | |
| hasXGrid = false; | |
| hasYGrid = false; | |
| hasZGrid = false; | |
| if hasXGrid || hasYGrid || hasZGrid | |
| matlabGridLineStyle = get(handle, 'GridLineStyle'); | |
| % Take over the grid line style in any case when in strict mode. | |
| % If not, don't add anything in case of default line grid line style | |
| % and effectively take Pgfplots' default. | |
| defaultMatlabGridLineStyle = ':'; | |
| if m2t.cmdOpts.Results.strict ... | |
| || ~strcmpi(matlabGridLineStyle,defaultMatlabGridLineStyle) | |
| gls = translateLineStyle(matlabGridLineStyle); | |
| axisGridOpts = {'grid style', sprintf('{%s}', gls)}; | |
| m2t.axesContainers{end}.options = cat(1, ... | |
| m2t.axesContainers{end}.options,... | |
| axisGridOpts); | |
| end | |
| else | |
| % 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.cmdOpts.Results.strict | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'axis on top', []); | |
| end | |
| % FIXME: axis background, axis grid, main, axis ticks, axis lines, axis tick labels, axis descriptions, axis foreground | |
| end | |
| end | |
| % ============================================================================== | |
| function m2t = add3DOptionsOfAxes(m2t, handle) | |
| % adds 3D specific options of an axes object | |
| if isAxis3D(handle) | |
| [m2t, zopts] = getAxisOptions(m2t, handle, 'z'); | |
| m2t.axesContainers{end}.options = opts_merge(... | |
| m2t.axesContainers{end}.options, zopts); | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'view', sprintf(['{', m2t.ff, '}{', m2t.ff, '}'], get(handle, 'View'))); | |
| end | |
| end | |
| % ============================================================================== | |
| function legendhandle = getAssociatedLegend(m2t, handle) | |
| % Check if the axis is referenced by a legend (only necessary for Octave) | |
| legendhandle = []; | |
| switch getEnvironment | |
| case 'Octave' | |
| % Make sure that m2t.legendHandles is a row vector. | |
| for lhandle = m2t.legendHandles(:)' | |
| ud = get(lhandle, 'UserData'); | |
| if isVisibleContainer(lhandle) && any(handle == ud.handle) | |
| legendhandle = lhandle; | |
| break; | |
| end | |
| end | |
| case 'MATLAB' | |
| % no action needed | |
| otherwise | |
| errorUnknownEnvironment(); | |
| end | |
| 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.cmdOpts.Results.width, ... | |
| m2t.cmdOpts.Results.height, m2t.axesBoundingBox); | |
| % set the width | |
| if (~m2t.cmdOpts.Results.noSize) | |
| % optionally prevents setting the width and height of the axis | |
| m2t = setDimensionOfAxes(m2t, 'width', pos.w); | |
| m2t = setDimensionOfAxes(m2t, 'height', pos.h); | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, 'at', ... | |
| ['{(' formatDim(pos.x.value, pos.x.unit) ','... | |
| formatDim(pos.y.value, pos.y.unit) ')}']); | |
| % the following is general MATLAB behavior: | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'scale only axis', []); | |
| end | |
| end | |
| % ============================================================================== | |
| function m2t = setDimensionOfAxes(m2t, widthOrHeight, dimension) | |
| % sets the dimension "name" of the current axes to the struct "dim" | |
| m2t.axesContainers{end}.options = opts_add(... | |
| m2t.axesContainers{end}.options, 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.axesContainers{end}.is3D | |
| % Note: set 'plot box ratio' for 3D axes to avoid bug with | |
| % 'scale mode = uniformly' (see #560) | |
| aspectRatio = getPlotBoxAspectRatio(handle); | |
| m2t.axesContainers{end}.options = opts_add(... | |
| m2t.axesContainers{end}.options, '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.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'axis background/.style', sprintf('{fill=%s}', col)); | |
| end | |
| end | |
| % ============================================================================== | |
| function m2t = drawTitleOfAxes(m2t, handle) | |
| % processes the title of an axes object | |
| [m2t, m2t.axesContainers{end}.options] = getTitle(m2t, handle, ... | |
| m2t.axesContainers{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); | |
| style = getFontStyle(m2t, object); | |
| if length(str) > 1 %multiline | |
| style = opts_add(style, 'align', 'center'); | |
| end | |
| if ~isempty(style) | |
| opts = opts_add(opts, [tikzKeyword ' style'], ... | |
| sprintf('{%s}', opts_print(m2t, 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 = strcmpi(get(h, 'box'), 'on'); | |
| xLoc = get(h, 'XAxisLocation'); | |
| yLoc = get(h, 'YAxisLocation'); | |
| isXaxisBottom = strcmpi(xLoc,'bottom'); | |
| isYaxisLeft = strcmpi(yLoc,'left'); | |
| % Only flip the labels to the other side if not at the default | |
| % left/bottom positions | |
| if isBoxOn | |
| if ~isXaxisBottom | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'xticklabel pos','right'); | |
| end | |
| if ~isYaxisLeft | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'yticklabel pos','right'); | |
| end | |
| % Position axes lines (strips the box) | |
| else | |
| m2t.axesContainers{end}.options = ... | |
| opts_append(m2t.axesContainers{end}.options, ... | |
| 'axis x line*', xLoc); | |
| m2t.axesContainers{end}.options = ... | |
| opts_append(m2t.axesContainers{end}.options, ... | |
| 'axis y line*', yLoc); | |
| if m2t.axesContainers{end}.is3D | |
| % There's no such attribute as 'ZAxisLocation'. | |
| % Instead, the default seems to be 'left'. | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'axis z line*', 'left'); | |
| end | |
| end | |
| end | |
| % ============================================================================== | |
| function m2t = drawLegendOptionsOfAxes(m2t, handle) | |
| % See if there are any legends that need to be plotted. | |
| % Since the legends are at the same level as axes in the hierarchy, | |
| % we can't work out which relates to which using the tree | |
| % so we have to do it by looking for a plot inside which the legend sits. | |
| % This could be done better with a heuristic of finding | |
| % the nearest legend to a plot, which would cope with legends outside | |
| % plot boundaries. | |
| switch getEnvironment | |
| case 'MATLAB' | |
| legendHandle = legend(handle); | |
| if ~isempty(legendHandle) | |
| [m2t, key, legendOpts] = getLegendOpts(m2t, legendHandle); | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| key, ... | |
| ['{', legendOpts, '}']); | |
| end | |
| case 'Octave' | |
| % TODO: How to uniquely connect a legend with a pair of axes in Octave? | |
| axisDims = pos2dims(get(handle,'Position')); %#ok | |
| % siblings of this handle: | |
| siblings = get(get(handle,'Parent'), 'Children'); | |
| % "siblings" always(?) is a column vector. Iterating over the column | |
| % with the for statement below wouldn't return the individual vector | |
| % elements but the same column vector, resulting in no legends exported. | |
| % So let's make sure "siblings" is a row vector by reshaping it: | |
| siblings = reshape(siblings, 1, []); | |
| for sibling = siblings | |
| if sibling && strcmpi(get(sibling,'Type'), 'axes') && strcmpi(get(sibling,'Tag'), 'legend') | |
| legDims = pos2dims(get(sibling, 'Position')); %#ok | |
| % TODO The following logic does not work for 3D plots. | |
| % => Commented out. | |
| % This creates problems though for stacked plots with legends. | |
| % if ( legDims.left > axisDims.left ... | |
| % && legDims.bottom > axisDims.bottom ... | |
| % && legDims.left + legDims.width < axisDims.left + axisDims.width ... | |
| % && legDims.bottom + legDims.height < axisDims.bottom + axisDims.height) | |
| [m2t, key, legendOpts] = getLegendOpts(m2t, sibling); | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| key, ... | |
| ['{', legendOpts, '}']); | |
| % end | |
| end | |
| end | |
| otherwise | |
| errorUnknownEnvironment(); | |
| end | |
| 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.axesContainers) | |
| if m2t.axesContainers{k}.handle == parentAxesHandle | |
| k0 = k; | |
| parentFound = true; | |
| break; | |
| end | |
| end | |
| if parentFound | |
| m2t.axesContainers{k0}.options = ... | |
| opts_append(m2t.axesContainers{k0}.options, ... | |
| matlab2pgfplotsColormap(m2t, m2t.currentHandles.colormap), []); | |
| % Append cell string. | |
| m2t.axesContainers{k0}.options = cat(1,... | |
| m2t.axesContainers{k0}.options, ... | |
| getColorbarOptions(m2t, handle)); | |
| else | |
| warning('matlab2tikz:parentAxesOfColorBarNotFound',... | |
| 'Could not find parent axes for color bar. Skipping.'); | |
| end | |
| end | |
| % ============================================================================== | |
| function tag = getTag(handle) | |
| % if a tag is given, use it as comment | |
| tag = get(handle, 'tag'); | |
| if ~isempty(tag) | |
| tag = sprintf('Axis "%s"', tag); | |
| else | |
| tag = sprintf('Axis at [%.2g %.2f %.2g %.2g]', get(handle, 'position')); | |
| 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.cmdOpts.Results.strict | |
| [m2t, col] = getColor(m2t, handle, color, 'patch'); | |
| if strcmpi(get(handle, 'box'), 'on') | |
| % 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 | |
| options = ... | |
| opts_add(options, ... | |
| ['every outer ', axis, ' axis line/.append style'], ... | |
| ['{', col, '}']); | |
| options = ... | |
| opts_add(options, ... | |
| ['every ',axis,' tick label/.append style'], ... | |
| ['{font=\color{',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 | |
| 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); | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % get grids | |
| if strcmpi(getOrDefault(handle, [upper(axis),'Grid'], 'off'), 'on'); | |
| options = opts_add(options, [axis, 'majorgrids'], []); | |
| end | |
| if strcmpi(getOrDefault(handle, [upper(axis),'MinorGrid'], 'off'), 'on'); | |
| options = opts_add(options, [axis, 'minorgrids'], []); | |
| end | |
| 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); | |
| if isempty(ticks) | |
| % If no ticks are present, we need to enforce this in any case. | |
| pgfTicks = '\empty'; | |
| else | |
| if strcmpi(tickMode, 'auto') && ~m2t.cmdOpts.Results.strict | |
| % If the ticks are set automatically, and strict conversion is | |
| % not required, then let Pgfplots take care of the ticks. | |
| % In most cases, this looks a lot better anyway. | |
| pgfTicks = []; | |
| else % strcmpi(tickMode,'manual') || m2t.cmdOpts.Results.strict | |
| pgfTicks = join(m2t, cellstr(num2str(ticks(:))), ', '); | |
| end | |
| end | |
| keywordTickLabelMode = [upper(axis), 'TickLabelMode']; | |
| tickLabelMode = get(handle, keywordTickLabelMode); | |
| keywordTickLabel = [upper(axis), 'TickLabel']; | |
| tickLabels = cellstr(get(handle, keywordTickLabel)); | |
| if strcmpi(tickLabelMode, 'auto') && ~m2t.cmdOpts.Results.strict | |
| pgfTickLabels = []; | |
| else % strcmpi(tickLabelMode,'manual') || m2t.cmdOpts.Results.strict | |
| 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 = strcmpi(getOrDefault(handle, keywordMinorTick, 'off'), 'on'); | |
| tickDirection = getOrDefault(handle, 'TickDir', 'in'); | |
| options = setAxisTicks(m2t, options, axis, pgfTicks, pgfTickLabels, ... | |
| hasMinorTicks, tickDirection); | |
| end | |
| % ============================================================================== | |
| function options = setAxisTicks(m2t, options, axis, ticks, tickLabels,hasMinorTicks, tickDir) | |
| % 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.cmdOpts.Results.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 | |
| 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 options = setAxisLimits(m2t, handle, axis, options) | |
| % set the upper/lower limit of an axis | |
| limits = get(handle, [upper(axis),'Lim']); | |
| if isfinite(limits(1)) | |
| options = opts_add(options, [axis,'min'], sprintf(m2t.ff, limits(1))); | |
| end | |
| if isfinite(limits(2)) | |
| options = opts_add(options, [axis,'max'], sprintf(m2t.ff, limits(2))); | |
| end | |
| end | |
| % ============================================================================== | |
| function bool = isVisibleContainer(axisHandle) | |
| if ~isVisible(axisHandle) | |
| % An invisible axes container *can* have visible children, so don't | |
| % immediately bail out here. | |
| children = get(axisHandle, 'Children'); | |
| bool = false; | |
| for child = children(:)' | |
| if isVisible(child) | |
| bool = true; | |
| return; | |
| end | |
| end | |
| else | |
| bool = true; | |
| end | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = drawLine(m2t, h, yDeviation) | |
| % 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 ~isVisible(h) | |
| return | |
| end | |
| % Check if there is anything to plot (line annotation has no marker) | |
| lineStyle = get(h, 'LineStyle'); | |
| lineWidth = get(h, 'LineWidth'); | |
| marker = getOrDefault(h, 'Marker','none'); | |
| hasLines = ~isNone(lineStyle) && lineWidth > 0; | |
| hasMarkers = ~isNone(marker); | |
| if ~hasLines && ~hasMarkers | |
| return | |
| end | |
| % Color | |
| color = get(h, 'Color'); | |
| [m2t, xcolor] = getColor(m2t, h, color, 'patch'); | |
| % Line and marker options | |
| lineOptions = getLineOptions(m2t, lineStyle, lineWidth); | |
| [m2t, markerOptions] = getMarkerOptions(m2t, h); | |
| drawOptions = opts_new(); | |
| drawOptions = opts_add(drawOptions, 'color', xcolor); | |
| drawOptions = opts_merge(drawOptions, lineOptions, markerOptions); | |
| % Check for "special" lines, e.g.: | |
| if strcmpi(get(h, 'Tag'), 'zplane_unitcircle') | |
| % Draw unit circle and axes. | |
| % TODO Don't hardcode "10". | |
| opts = opts_print(m2t, 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)]; | |
| return | |
| end | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| [data] = getXYZDataFromLine(m2t, h); | |
| % check if the *optional* argument 'yDeviation' was given | |
| hasDeviations = false; | |
| if nargin > 2 | |
| data = [data, yDeviation(:,1:2)]; | |
| hasDeviations = true; | |
| end | |
| % Check if any value is infinite/NaN. In that case, add appropriate option. | |
| m2t = jumpAtUnboundCoords(m2t, data); | |
| [m2t, str] = writePlotData(m2t, str, data, drawOptions); | |
| [m2t, str] = addLabel(m2t, str); | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = writePlotData(m2t, str, data, drawOptions) | |
| % actually writes the plot data to file | |
| is3D = m2t.axesContainers{end}.is3D; | |
| if is3D | |
| % Don't try to be smart in parametric 3d plots: Just plot all the data. | |
| [m2t, table, tabOpts] = makeTable(m2t, {'','',''}, data); | |
| str = sprintf('%s\\addplot3 [%s]\n table[%s] {%s};\n ', ... | |
| str, opts_print(m2t, drawOptions, ','), ... | |
| opts_print(m2t, tabOpts,','), table); | |
| else | |
| % split the data into logical chunks | |
| dataCell = splitLine(m2t, data); | |
| % plot them | |
| for k = 1:length(dataCell) | |
| % If the line has a legend string, make sure to only include a legend | |
| % entry for the *last* occurrence of the plot series. | |
| % Hence the condition k<length(xDataCell). | |
| %if ~isempty(m2t.legendHandles) && (~m2t.currentHandleHasLegend || k < length(dataCell)) | |
| if ~m2t.currentHandleHasLegend || k < length(dataCell) | |
| % No legend entry found. Don't include plot in legend. | |
| hiddenDrawOptions = maybeShowInLegend(false, drawOptions); | |
| opts = opts_print(m2t, hiddenDrawOptions, ','); | |
| else | |
| opts = opts_print(m2t, drawOptions, ','); | |
| end | |
| [m2t, Part] = plotLine2d(m2t, opts, dataCell{k}); | |
| str = [str, Part]; | |
| end | |
| end | |
| end | |
| % ============================================================================== | |
| function [data] = getXYZDataFromLine(m2t, h) | |
| % Retrieves the X, Y and Z (if appropriate) data from a Line object | |
| % | |
| % First put them all together in one multiarray. | |
| % This also implicitly makes sure that the lengths match. | |
| try | |
| xData = get(h, 'XData'); | |
| yData = get(h, 'YData'); | |
| catch | |
| % Line annotation | |
| xData = get(h, 'X'); | |
| yData = get(h, 'Y'); | |
| end | |
| is3D = m2t.axesContainers{end}.is3D; | |
| if ~is3D | |
| data = [xData(:), yData(:)]; | |
| else | |
| zData = get(h, 'ZData'); | |
| data = applyHgTransform(m2t, [xData(:), yData(:), zData(:)]); | |
| end | |
| end | |
| % ============================================================================== | |
| function [m2t, generatedCodeSoFar, labelCode] = addLabel(m2t, generatedCodeSoFar) | |
| % conditionally add a LaTeX label after the current plot | |
| if ~exist('generatedCodeSoFar','var') || isempty(generatedCodeSoFar) | |
| generatedCodeSoFar = ''; | |
| end | |
| if m2t.cmdOpts.Results.automaticLabels | |
| [pathstr, name] = fileparts(m2t.cmdOpts.Results.filename); %#ok | |
| labelName = sprintf('addplot:%s%d', name, m2t.automaticLabelIndex); | |
| labelCode = sprintf('\\label{%s}\n', labelName); | |
| m2t.automaticLabelIndex = m2t.automaticLabelIndex + 1; | |
| userWarning(m2t, 'Automatically added label ''%s'' for line plot.', labelName); | |
| generatedCodeSoFar = [generatedCodeSoFar, labelCode]; | |
| end | |
| end | |
| % ============================================================================== | |
| function [m2t,str] = plotLine2d(m2t, opts, data) | |
| errorbarMode = (size(data,2) == 4); % is (optional) yDeviation given? | |
| str = ''; | |
| if errorbarMode | |
| m2t = needsPgfplotsVersion(m2t, [1,9]); | |
| str = sprintf('plot [error bars/.cd, y dir = both, y explicit]\n'); | |
| end | |
| % Convert to string array then cell to call sprintf once (and no loops). | |
| [m2t, table, tabOpts] = makeTable(m2t, repmat({''}, size(data,2)), data); | |
| if errorbarMode | |
| tabOpts = opts_add(tabOpts, 'y error plus index', '2'); | |
| tabOpts = opts_add(tabOpts, 'y error minus index', '3'); | |
| end | |
| str = sprintf('\\addplot [%s]\n %s table[%s]{%s};\n',... | |
| opts, str, opts_print(m2t, tabOpts, ', '), table); | |
| end | |
| % ============================================================================== | |
| function dataCell = splitLine(m2t, data) | |
| % Split the xData, yData into several chunks of data for each of which | |
| % an \addplot will be generated. | |
| dataCell{1} = data; | |
| % Split each of the current chunks further with respect to outliers. | |
| dataCell = splitByArraySize(m2t, dataCell); | |
| end | |
| % ============================================================================== | |
| function dataCellNew = splitByArraySize(m2t, dataCell) | |
| % 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 plots, and this | |
| % function does the job. | |
| dataCellNew = cell(0); | |
| %TODO: pre-allocate the cell array such that it doesn't grow during the loop | |
| %TODO: scale `maxChunkLength` with the number of columns in the data array | |
| for data = dataCell | |
| chunkStart = 1; | |
| len = size(data{1}, 1); | |
| while chunkStart <= len | |
| chunkEnd = min(chunkStart + m2t.cmdOpts.Results.maxChunkLength - 1, len); | |
| % Copy over the data to the new containers. | |
| dataCellNew{end+1} = data{1}(chunkStart:chunkEnd,:); | |
| % Add an extra (overlap) point to the data stream; | |
| % otherwise the line between two data chunks would be broken. | |
| % Technically, this is only needed when the plot has a line | |
| % connecting the points, but the additional cost when there is no | |
| % line doesn't justify the added complexity. | |
| if chunkEnd~=len | |
| dataCellNew{end} = [dataCellNew{end};... | |
| data{1}(chunkEnd+1,:)]; | |
| end | |
| chunkStart = chunkEnd + 1; | |
| end | |
| end | |
| end | |
| % ============================================================================== | |
| function lineOpts = getLineOptions(m2t, lineStyle, lineWidth) | |
| % Gathers the line options. | |
| lineOpts = opts_new(); | |
| if ~isNone(lineStyle) && (lineWidth > m2t.tol) | |
| lineOpts = opts_add(lineOpts, translateLineStyle(lineStyle)); | |
| end | |
| % Take over the line width in any case when in strict mode. If not, don't add | |
| % anything in case of default line width and effectively take Pgfplots' | |
| % default. | |
| % Also apply the line width if no actual line is there; the markers make use | |
| % of this, too. | |
| matlabDefaultLineWidth = 0.5; | |
| if m2t.cmdOpts.Results.strict ... | |
| || ~abs(lineWidth-matlabDefaultLineWidth) <= m2t.tol | |
| lineOpts = opts_add(lineOpts, 'line width', sprintf('%.1fpt', lineWidth)); | |
| 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.cmdOpts.Results.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 | |
| % print no lines | |
| if isNone(lineStyle) || lineWidth==0 | |
| drawOptions = opts_add(drawOptions, 'only marks'); | |
| end | |
| % get the marker color right | |
| markerFaceColor = get(h, 'markerfaceColor'); | |
| markerEdgeColor = get(h, 'markeredgeColor'); | |
| [tikzMarker, markOptions] = translateMarker(m2t, marker, ... | |
| markOptions, ~isNone(markerFaceColor)); | |
| [m2t, markOptions] = setColor(m2t, h, markOptions, 'fill', markerFaceColor); | |
| if ~strcmpi(markerEdgeColor,'auto') | |
| [m2t, markOptions] = setColor(m2t, h, markOptions, 'draw', markerEdgeColor); | |
| end | |
| % add it all to drawOptions | |
| drawOptions = opts_add(drawOptions, 'mark', tikzMarker); | |
| if ~isempty(markOptions) | |
| mo = opts_print(m2t, markOptions, ','); | |
| drawOptions = opts_add(drawOptions, 'mark options', ['{' mo '}']); | |
| end | |
| end | |
| end | |
| % ============================================================================== | |
| function [tikzMarkerSize, isDefault] = ... | |
| translateMarkerSize(m2t, matlabMarker, matlabMarkerSize) | |
| % The markersizes of Matlab and TikZ are related, but not equal. This | |
| % is because | |
| % | |
| % 1.) MATLAB uses the MarkerSize property to describe something like | |
| % the diameter of the mark, while TikZ refers to the 'radius', | |
| % 2.) MATLAB and TikZ take different measures (e.g. the | |
| % edge of a square vs. its diagonal). | |
| if(~ischar(matlabMarker)) | |
| error('matlab2tikz:translateMarkerSize', ... | |
| 'Variable matlabMarker is not a string.'); | |
| end | |
| if(~isnumeric(matlabMarkerSize)) | |
| error('matlab2tikz:translateMarkerSize', ... | |
| 'Variable matlabMarkerSize is not a numeral.'); | |
| end | |
| % 6pt is the default MATLAB marker size for all markers | |
| defaultMatlabMarkerSize = 6; | |
| isDefault = abs(matlabMarkerSize(1)-defaultMatlabMarkerSize)<m2t.tol; | |
| % matlabMarkerSize can be vector data, use first index to check the default | |
| % marker size. When the script also handles different markers together with | |
| % changing size and color, the test should be extended to a vector norm, e.g. | |
| % sqrt(e^T*e) < tol, where e=matlabMarkerSize-defaultMatlabMarkerSize | |
| switch (matlabMarker) | |
| case 'none' | |
| tikzMarkerSize = []; | |
| case {'+','o','x','*','p','pentagram','h','hexagram'} | |
| % In MATLAB, the marker size refers to the edge length of a | |
| % square (for example) (~diameter), whereas in TikZ the | |
| % distance of an edge to the center is the measure (~radius). | |
| % Hence divide by 2. | |
| tikzMarkerSize = matlabMarkerSize(:) / 2; | |
| case '.' | |
| % as documented on the Matlab help pages: | |
| % | |
| % Note that MATLAB draws the point marker (specified by the '.' | |
| % symbol) at one-third the specified size. | |
| % The point (.) marker type does not change size when the | |
| % specified value is less than 5. | |
| % | |
| tikzMarkerSize = matlabMarkerSize(:) / 2 / 3; | |
| case {'s','square'} | |
| % Matlab measures the diameter, TikZ half the edge length | |
| tikzMarkerSize = matlabMarkerSize(:) / 2 / sqrt(2); | |
| case {'d','diamond'} | |
| % MATLAB measures the width, TikZ the height of the diamond; | |
| % the acute angle (at the top and the bottom of the diamond) | |
| % is a manually measured 75 degrees (in TikZ, and MATLAB | |
| % probably very similar); use this as a base for calculations | |
| tikzMarkerSize = matlabMarkerSize(:) / 2 / atan(75/2 *pi/180); | |
| case {'^','v','<','>'} | |
| % for triangles, matlab takes the height | |
| % and tikz the circumcircle radius; | |
| % the triangles are always equiangular | |
| tikzMarkerSize = matlabMarkerSize(:) / 2 * (2/3); | |
| otherwise | |
| error('matlab2tikz:translateMarkerSize', ... | |
| 'Unknown matlabMarker ''%s''.', matlabMarker); | |
| end | |
| end | |
| % ============================================================================== | |
| function [tikzMarker, markOptions] = ... | |
| translateMarker(m2t, matlabMarker, markOptions, faceColorToggle) | |
| % Translates MATLAB markers to their Tikz equivalents | |
| % #COMPLEX: inherently large switch-case | |
| if ~ischar(matlabMarker) | |
| error('matlab2tikz:translateMarker:MarkerNotAString',... | |
| 'matlabMarker is not a string.'); | |
| end | |
| switch (matlabMarker) | |
| case 'none' | |
| tikzMarker = ''; | |
| case '+' | |
| tikzMarker = '+'; | |
| case 'o' | |
| if faceColorToggle | |
| tikzMarker = '*'; | |
| else | |
| tikzMarker = 'o'; | |
| end | |
| case '.' | |
| tikzMarker = '*'; | |
| case 'x' | |
| tikzMarker = 'x'; | |
| otherwise % the following markers are only available with PGF's | |
| % plotmarks library | |
| userInfo(m2t, '\nMake sure to load \\usetikzlibrary{plotmarks} in the preamble.\n'); | |
| 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] = drawPatch(m2t, handle) | |
| % 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.axesContainers{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.axesContainers{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 | |
| lineStyle = get(handle, 'LineStyle'); | |
| lineWidth = get(handle, 'LineWidth'); | |
| lineOptions = getLineOptions(m2t, lineStyle, lineWidth); | |
| drawOptions = opts_merge(drawOptions, lineOptions); | |
| % 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); | |
| [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 | |
| drawOpts = opts_print(m2t, drawOptions,','); | |
| % Plot the actual data. | |
| [m2t, verticesTable, tabOpts] = makeTable(m2t, columnNames, Vertices); | |
| tabOpts = opts_merge(tabOpts, verticesTableOptions); | |
| str = sprintf('%s\n\\%s[%s]\ntable[%s] {%s}%s;\n',... | |
| str, plotCmd, drawOpts, opts_print(m2t, 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.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| matlab2pgfplotsColormap(m2t, m2t.currentHandles.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.axesContainers{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 ~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 | |
| options = opts_add(options, property, xcolor); | |
| 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 | |
| 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.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, 'unbounded coords', 'jump'); | |
| end | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = drawImage(m2t, handle) | |
| 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 (m2t.cmdOpts.Results.imagesAsPng) | |
| [m2t, str] = imageAsPNG(m2t, handle, xData, yData, cData); | |
| else | |
| [m2t, str] = imageAsTikZ(m2t, handle, xData, yData, cData); | |
| end | |
| % Make sure that the axes are still visible above the image. | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'axis on top', []); | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = imageAsPNG(m2t, handle, xData, yData, cData) | |
| m2t.imageAsPngNo = m2t.imageAsPngNo + 1; | |
| % ------------------------------------------------------------------------ | |
| % draw a png image | |
| [pngFileName, pngReferencePath] = externalFilename(m2t, m2t.imageAsPngNo, '.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', alphaData}; | |
| end | |
| if (ndims(colorData) == 2) %#ok don't use ismatrix (cfr. #143) | |
| if size(m2t.currentHandles.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.currentHandles.colormap, ... | |
| pngFileName, 'png'); | |
| else % use true-color instead | |
| imwrite(ind2rgb(colorData, m2t.currentHandles.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.currentHandles.gca, 'XLim'); | |
| xw = xLim(2) - xLim(1); | |
| else | |
| xw = (xData(end)-xData(1)) / (n-1); | |
| end | |
| if m == 1 | |
| yLim = get(m2t.currentHandles.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)); | |
| str = sprintf('\\addplot [forget plot] graphics [%s] {%s};\n', ... | |
| opts_print(m2t, opts, ','), 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) | |
| % 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. | |
| switch length(xData) | |
| case 2 % only the limits given; common for generic image plots | |
| hX = 1; | |
| case size(cData,1) % specific x-data is given | |
| hX = (xData(end)-xData(1)) / (length(xData)-1); | |
| otherwise | |
| error('drawImage:arrayLengthMismatch', ... | |
| 'Array lengths not matching (%d = size(cdata,1) ~= length(xData) = %d).', size(cData,1), length(xData)); | |
| end | |
| X = xData(1):hX:xData(end); | |
| switch length(yData) | |
| case 2 % only the limits given; common for generic image plots | |
| hY = 1; | |
| case size(cData,2) % specific y-data is given | |
| hY = (yData(end)-yData(1)) / (length(yData)-1); | |
| otherwise | |
| error('drawImage:arrayLengthMismatch', ... | |
| 'Array lengths not matching (%d = size(cData,2) ~= length(yData) = %d).', size(cData,2), length(yData)); | |
| end | |
| Y = yData(1):hY:yData(end); | |
| [m2t, xcolor] = getColor(m2t, handle, cData, 'image'); | |
| % The following section takes pretty long to execute, although in | |
| % principle it is discouraged to use TikZ for those; LaTeX will take | |
| % forever to compile. | |
| % Still, a bug has been filed on MathWorks to allow for one-line | |
| % sprintf'ing with (string+num) cells (Request ID: 1-9WHK4W); | |
| % <http://www.mathworks.de/support/service_requests/Service_Request_Detail.do?ID=183481&filter=&sort=&statusorder=0&dateorder=0>. | |
| % An alternative approach could be to use 'surf' or 'patch' of pgfplots | |
| % with inline tables. | |
| str = ''; | |
| m = length(X); | |
| n = length(Y); | |
| for i = 1:m | |
| for j = 1:n | |
| str = [str, ... | |
| sprintf(['\t\\fill [%s] ', ... | |
| '(axis cs:', m2t.ff,',', m2t.ff,') rectangle ', ... | |
| '(axis cs:', m2t.ff,',',m2t.ff,');\n'], ... | |
| xcolor{n-j+1,i}, ... | |
| X(i)-hX/2, Y(j)-hY/2, ... | |
| X(i)+hX/2, Y(j)+hY/2 ... | |
| )]; | |
| end | |
| 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 revesed 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.currentHandles.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.currentHandles.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) | |
| if isHG2() | |
| [m2t, str] = drawContourHG2(m2t, h); | |
| else | |
| % Save legend state for the contour group | |
| hasLegend = m2t.currentHandleHasLegend; | |
| % Plot children patches | |
| children = get(h,'children'); | |
| 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}] = drawText(m2t,child); | |
| else | |
| [m2t, str{ii}] = drawPatch(m2t,child); | |
| 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) | |
| str = ''; | |
| % 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 = strcmpi(get(h,'Fill'),'on'); | |
| if isFilled | |
| [m2t, str] = drawFilledContours(m2t, str, h, contours, istart, nrows); | |
| else | |
| % Add colormap | |
| cmap = m2t.currentHandles.colormap; | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 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 strcmpi(get(h,'ShowText'),'off') | |
| plotoptions = opts_add(plotoptions,'contour/labels','false'); | |
| end | |
| % Make contour table | |
| [m2t, table, tabOpts] = makeTable(m2t, {'',''}, contours); | |
| str = sprintf('\\addplot[%s] table[%s] {%%\n%s};\n', ... | |
| opts_print(m2t, plotoptions, ', '),... | |
| opts_print(m2t, 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, str, h, contours, istart, nrows) | |
| % 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. | |
| % 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). | |
| 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; | |
| % Add zero level fill | |
| xdata = get(h,'XData'); | |
| ydata = get(h,'YData'); | |
| zerolevel = [0, 4; | |
| min(xdata), min(ydata); | |
| min(xdata), max(ydata); | |
| max(xdata), max(ydata); | |
| max(xdata), min(ydata)]; | |
| cellcont = [zerolevel; cellcont]; | |
| % Plot | |
| columnNames = {'x','y'}; | |
| for ii = 1:ncont + 1 | |
| drawOpts = opts_new(); | |
| % Get color | |
| zval = cellcont{ii}(1,1); | |
| [m2t, xcolor] = getColor(m2t,h,zval,'image'); | |
| drawOpts = opts_add(drawOpts,'fill',xcolor); | |
| % Toggle legend entry | |
| hasLegend = ii == 1 && m2t.currentHandleHasLegend; | |
| drawOpts = maybeShowInLegend(hasLegend, drawOpts); | |
| % Print table | |
| [m2t, table, tabOpts] = makeTable(m2t, columnNames, cellcont{ii}(2:end,:)); | |
| % Fillplot | |
| str = sprintf('%s\\addplot[%s] table[%s] {%%\n%s};\n', ... | |
| str, opts_print(m2t,drawOpts,','), opts_print(m2t,tabOpts,','), table); | |
| end | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = drawHggroup(m2t, h) | |
| % Octave doesn't have the handle() function, so there's no way to determine | |
| % the nature of the plot anymore at this point. Set to 'unknown' to force | |
| % fallback handling. This produces something for bar plots, for example. | |
| % #COMPLEX: big switch-case | |
| try | |
| cl = class(handle(h)); | |
| catch %#ok | |
| cl = 'unknown'; | |
| end | |
| switch(cl) | |
| case {'specgraph.barseries', 'matlab.graphics.chart.primitive.Bar'} | |
| % hist plots and friends | |
| [m2t, str] = drawBarseries(m2t, h); | |
| case {'specgraph.stemseries', 'matlab.graphics.chart.primitive.Stem'} | |
| % stem plots | |
| [m2t, str] = drawStemSeries(m2t, h); | |
| case {'specgraph.stairseries', 'matlab.graphics.chart.primitive.Stair'} | |
| % stair plots | |
| [m2t, str] = drawStairSeries(m2t, h); | |
| case {'specgraph.areaseries', 'matlab.graphics.chart.primitive.Area'} | |
| % scatter plots | |
| [m2t,str] = drawAreaSeries(m2t, h); | |
| case {'specgraph.quivergroup', 'matlab.graphics.chart.primitive.Quiver'} | |
| % quiver arrows | |
| [m2t, str] = drawQuiverGroup(m2t, h); | |
| case {'specgraph.errorbarseries', 'matlab.graphics.chart.primitive.ErrorBar'} | |
| % error bars | |
| [m2t,str] = drawErrorBars(m2t, h); | |
| case {'specgraph.scattergroup','matlab.graphics.chart.primitive.Scatter'} | |
| % scatter plots | |
| [m2t,str] = drawScatterPlot(m2t, h); | |
| case {'specgraph.contourgroup', 'matlab.graphics.chart.primitive.Contour'} | |
| [m2t,str] = drawContour(m2t, h); | |
| case {'hggroup', 'matlab.graphics.primitive.Group'} | |
| % handle all those the usual way | |
| [m2t, str] = handleAllChildren(m2t, h); | |
| case 'unknown' | |
| % Weird spurious class from Octave. | |
| [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 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 = findobj(m2t.currentHandles.gcf,'Tag','scribeOverlay'); | |
| annotHandles = findobj(get(annotPanes,'Children'),'Visible','on'); | |
| else | |
| annotHandles = findobj(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 %#ok | |
| cl = 'unknown'; | |
| end | |
| switch cl | |
| % Line | |
| case {'scribe.line', 'matlab.graphics.shape.Line'} | |
| [m2t, str] = drawLine(m2t, h); | |
| % Ellipse | |
| case {'scribe.scribeellipse','matlab.graphics.shape.Ellipse'} | |
| [m2t, str] = drawEllipse(m2t, h); | |
| % 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] = drawText(m2t, h); | |
| % Tetx 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] = drawTextarrow(m2t, h); | |
| % Rectangle | |
| case {'scribe.scriberect', 'matlab.graphics.shape.Rectangle'} | |
| [m2t, str] = drawRectangle(m2t, h); | |
| otherwise | |
| userWarning(m2t, 'Don''t know annotation ''%s''.', cl); | |
| return | |
| end | |
| % Add annotation to scribe overlay | |
| m2t.axesContainers{end} = addChildren(m2t.axesContainers{end}, str); | |
| end | |
| % ============================================================================== | |
| function [m2t,str] = drawSurface(m2t, h) | |
| [m2t, opts, s] = shaderOpts(m2t, h,'surf'); | |
| tabOpts = 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.axesContainers{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.axesContainers{end}.options(end+1,:) = ... | |
| {matlab2pgfplotsColormap(m2t, CData, 'patchmap'), []}; | |
| % Index into custom colormap | |
| color = (0:nrows-1)'; | |
| tabOpts = opts_add(tabOpts, 'colormap name','surfmap'); | |
| else | |
| opts = opts_add(opts,matlab2pgfplotsColormap(m2t, m2t.currentHandles.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 | |
| tabOpts = opts_add(tabOpts, 'point meta','\thisrow{c}'); | |
| data = [data, color]; | |
| % Add mesh/rows=<num rows> for specifying the row data instead of empty | |
| % lines in the data list below. This makes it possible to reduce the | |
| % data writing to one single sprintf() call. | |
| opts = opts_add(opts,'mesh/rows',sprintf('%d', numrows)); | |
| % Print the addplot options | |
| str = sprintf('\n\\%s[%%\n%s,\n%s]', plotCmd, s.plotType, opts_print(m2t, opts, ',')); | |
| % Print the data | |
| [m2t, table, tabOptsExtra] = makeTable(m2t, columnNames, data); | |
| tabOpts = opts_merge(tabOptsExtra, tabOpts); | |
| % Here is where everything is put together | |
| str = sprintf('%s\ntable[%s] {%%\n%s};\n', ... | |
| str, opts_print(m2t, 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, str] = addLabel(m2t, str); | |
| 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.axesContainers{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) | |
| % 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'))) ... | |
| || strcmpi(get(handle, 'Visible'), 'off') ... | |
| || (strcmpi(get(handle, 'HandleVisibility'), 'off') && ... | |
| ~m2t.cmdOpts.Results.showHiddenStrings) | |
| str = ''; | |
| return; | |
| end | |
| [m2t, str] = drawText(m2t, handle); | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = drawText(m2t, handle) | |
| % 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. | |
| str = ''; | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % get required properties | |
| String = get(handle, 'String'); | |
| Interpreter = get(handle, 'Interpreter'); | |
| String = prettyPrint(m2t, String, Interpreter); | |
| % Concatenate multiple lines | |
| String = join(m2t, String, '\\'); | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % 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); | |
| style = opts_merge(style, getFontStyle(m2t, handle)); | |
| color = get(handle, 'Color'); | |
| [m2t, tcolor] = getColor(m2t, handle, color, 'patch'); | |
| style = opts_add(style, 'text', tcolor); | |
| EdgeColor = get(handle, 'EdgeColor'); | |
| [m2t, style] = setColor(m2t, handle, style, 'draw', EdgeColor); | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % plot the thing | |
| [m2t, posString] = getPositionOfText(m2t, handle); | |
| str = sprintf('\\node[%s]\nat %s {%s};\n', ... | |
| opts_print(m2t, style, ', '), posString, String); | |
| 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 ~isempty(alignment) | |
| style = opts_add(style, alignment); | |
| end | |
| % Set 'align' option that is needed for multiline text | |
| style = opts_add(style, 'align', HorizontalAlignment); | |
| 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.axesContainers{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.currentHandles.gca, 'XLim',[-Inf +Inf]); | |
| ylim = getOrDefault(m2t.currentHandles.gca, 'YLim',[-Inf +Inf]); | |
| zlim = getOrDefault(m2t.currentHandles.gca, 'ZLim',[-Inf +Inf]); | |
| is3D = m2t.axesContainers{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.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'clip', 'false'); | |
| end | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = drawRectangle(m2t, h) | |
| 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) || strcmpi(get(h, 'HandleVisibility'), 'off') | |
| return; | |
| end | |
| % TODO handle Curvature = [0.8 0.4] | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % Get draw options. | |
| lineStyle = get(h, 'LineStyle'); | |
| lineWidth = get(h, 'LineWidth'); | |
| drawOptions = getLineOptions(m2t, lineStyle, lineWidth); | |
| [m2t, drawOptions] = getRectangleFaceOptions(m2t, h, drawOptions); | |
| [m2t, drawOptions] = getRectangleEdgeOptions(m2t, h, drawOptions); | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| pos = pos2dims(get(h, 'Position')); | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % plot the thing | |
| str = sprintf(['\\draw[%s] (axis cs:',m2t.ff,',',m2t.ff, ')', ... | |
| ' rectangle (axis cs:',m2t.ff,',',m2t.ff,');\n'], ... | |
| opts_print(m2t, drawOptions, ', '), ... | |
| 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) % 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') | |
| 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') | |
| 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 [m2t, str] = drawScatterPlot(m2t, h) | |
| str = ''; | |
| xData = get(h, 'XData'); | |
| yData = get(h, 'YData'); | |
| zData = get(h, 'ZData'); | |
| cData = get(h, 'CData'); | |
| sData = get(h, 'SizeData'); | |
| matlabMarker = get(h, 'Marker'); | |
| markerFaceColor = get(h, 'MarkerFaceColor'); | |
| markerEdgeColor = get(h, 'MarkerEdgeColor'); | |
| hasFaceColor = ~isNone(markerFaceColor); | |
| hasEdgeColor = ~isNone(markerEdgeColor); | |
| markOptions = opts_new(); | |
| [tikzMarker, markOptions] = translateMarker(m2t, matlabMarker, ... | |
| markOptions, hasFaceColor); | |
| constMarkerkSize = length(sData) == 1; % constant marker size | |
| % Rescale marker size (not definitive, follow discussion in #316) | |
| sData = translateMarkerSize(m2t, matlabMarker, sqrt(sData)/2); | |
| drawOptions = opts_new(); | |
| if length(cData) == 3 | |
| [m2t, drawOptions] = getScatterOptsOneColor(m2t, h, drawOptions, ... | |
| markOptions, tikzMarker, ... | |
| cData, sData, constMarkerkSize); | |
| elseif size(cData,2) == 3 | |
| drawOptions = getScatterOptsRGB(m2t, drawOptions); | |
| else | |
| [m2t, drawOptions] = getScatterOptsColormap(m2t, h, drawOptions, ... | |
| markOptions, tikzMarker, hasEdgeColor, hasFaceColor); | |
| end | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % Plot the thing. | |
| [env, data, sColumn] = organizeScatterData(m2t, xData, yData, zData, sData); | |
| if ~constMarkerkSize % | |
| drawOptions = opts_add(drawOptions, 'visualization depends on', ... | |
| ['{\thisrowno{', num2str(sColumn), '} \as \perpointmarksize}']); | |
| drawOptions = opts_add(drawOptions, ... | |
| 'scatter/@pre marker code/.append style', ... | |
| '{/tikz/mark size=\perpointmarksize}'); | |
| end | |
| drawOpts = opts_print(m2t, drawOptions, ','); | |
| [data, metaPart] = addCDataToScatterData(data, cData); | |
| % The actual printing. | |
| nColumns = size(data, 2); | |
| [m2t, table, tabOpts] = makeTable(m2t, repmat({''},1,nColumns), data); | |
| tabOpts = opts_merge(tabOpts, metaPart); | |
| str = sprintf('%s\\%s[%s] plot table[%s]{%s};\n', str, env, ... | |
| drawOpts, opts_print(m2t, tabOpts, ','), table); | |
| end | |
| % ============================================================================== | |
| function [m2t, drawOptions] = getScatterOptsOneColor(m2t, h, drawOptions, ... | |
| markOptions, tikzMarker, cData, sData, constMarkerkSize) | |
| % 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', cData); | |
| [m2t, ecolor, hasEdgeColor] = getColorOfMarkers(m2t, h, 'MarkerEdgeColor', cData); | |
| if constMarkerkSize | |
| drawOptions = opts_add(drawOptions, 'only marks'); | |
| drawOptions = opts_add(drawOptions, 'mark', tikzMarker); | |
| drawOptions = opts_add(drawOptions, 'mark options', ... | |
| ['{' opts_print(m2t, markOptions, ',') '}']); | |
| drawOptions = opts_add(drawOptions, 'mark size', ... | |
| sprintf('%.4fpt', sData)); % FIXME: investigate whether to use `m2t.ff` | |
| if hasFaceColor && hasEdgeColor | |
| drawOptions = opts_add(drawOptions, 'draw', ecolor); | |
| drawOptions = opts_add(drawOptions, 'fill', xcolor); | |
| else | |
| drawOptions = opts_add(drawOptions, 'color', xcolor); | |
| end | |
| else % if changing marker size but same color on all marks | |
| markerOptions = opts_new(); | |
| markerOptions = opts_add(markerOptions, 'mark', tikzMarker); | |
| markerOptions = opts_add(markerOptions, 'mark options', ... | |
| ['{' opts_print(m2t, markOptions, ',') '}']); | |
| 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, 'scatter'); | |
| drawOptions = opts_add(drawOptions, 'only marks'); | |
| drawOptions = opts_add(drawOptions, 'color', xcolor); | |
| drawOptions = opts_add(drawOptions, 'mark', tikzMarker); | |
| drawOptions = opts_add(drawOptions, 'mark options', ... | |
| ['{' opts_print(m2t, markOptions, ',') '}']); | |
| if ~hasFaceColor | |
| drawOptions = opts_add(drawOptions, ... | |
| 'scatter/use mapped color', xcolor); | |
| else | |
| drawOptions = opts_add(drawOptions, ... | |
| 'scatter/use mapped color', ... | |
| ['{' opts_print(m2t, markerOptions,',') '}']); | |
| end | |
| end | |
| end | |
| function drawOptions = getScatterOptsRGB(m2t, drawOptions) | |
| % scatter plots with each marker a different RGB color (not yet supported!) | |
| drawOptions = opts_add(drawOptions, 'only marks'); | |
| 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 | |
| end | |
| function [m2t, drawOptions] = getScatterOptsColormap(m2t, h, drawOptions, ... | |
| markOptions, tikzMarker, hasEdgeColor, hasFaceColor) | |
| % scatter plot where the colors are set using a color map | |
| markerOptions = opts_new(); | |
| markerOptions = opts_add(markerOptions, 'mark', tikzMarker); | |
| markerOptions = opts_add(markerOptions, 'mark options', ... | |
| ['{' opts_print(m2t, markOptions, ',') '}']); | |
| if hasEdgeColor && hasFaceColor | |
| [m2t, ecolor] = getColor(m2t, h, markerEdgeColor,'patch'); | |
| markerOptions = opts_add(markerOptions, 'draw', ecolor); | |
| else | |
| markerOptions = opts_add(markerOptions, 'draw', 'mapped color'); | |
| end | |
| if hasFaceColor | |
| markerOptions = opts_add(markerOptions, 'fill', 'mapped color'); | |
| end | |
| drawOptions = opts_add(drawOptions, 'scatter'); | |
| drawOptions = opts_add(drawOptions, 'only marks'); | |
| drawOptions = opts_add(drawOptions, 'scatter src', 'explicit'); | |
| drawOptions = opts_add(drawOptions, 'scatter/use mapped color', ... | |
| ['{' opts_print(m2t, markerOptions, ',') '}']); | |
| % Add color map. | |
| m2t.axesContainers{end}.options = opts_append(... | |
| m2t.axesContainers{end}.options, ... | |
| matlab2pgfplotsColormap(m2t, m2t.currentHandles.colormap), []); | |
| end | |
| % ============================================================================== | |
| function [env, data, sColumn] = organizeScatterData(m2t, xData, yData, zData, sData) | |
| % reorganizes the {X,Y,Z,S} data into a single matrix | |
| sColumn = []; | |
| if ~m2t.axesContainers{end}.is3D | |
| env = 'addplot'; | |
| if length(sData) == 1 | |
| data = [xData(:), yData(:)]; | |
| else | |
| sColumn = 2; | |
| data = [xData(:), yData(:), sData(:)]; | |
| end | |
| else | |
| env = 'addplot3'; | |
| if length(sData) == 1 | |
| data = applyHgTransform(m2t, [xData(:),yData(:),zData(:)]); | |
| else | |
| sColumn = 3; | |
| data = applyHgTransform(m2t, [xData(:),yData(:),zData(:),sData(:)]); | |
| end | |
| end | |
| end | |
| % ============================================================================== | |
| function [data, metaOptions] = addCDataToScatterData(data, cData) | |
| % adds the cData vector to the data table of a scatter plot | |
| metaOptions = opts_new(); | |
| 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)); | |
| else | |
| metaOptions = opts_add(metaOptions, ... | |
| 'meta index', sprintf('%d', size(data,2))); | |
| 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) | |
| if ~isVisible(h) | |
| str = ''; | |
| return; | |
| end | |
| % Init options | |
| opts = opts_new(); | |
| % Face | |
| faceColor = get(h,'FaceColor'); | |
| [m2t, opts] = setColor(m2t, h, opts, 'fill', faceColor, 'none'); | |
| % FaceAlpha | |
| faceAlpha = get(h, 'FaceAlpha'); | |
| if ~isNone(faceColor) && isnumeric(faceAlpha) && faceAlpha ~= 1.0 | |
| opts = opts_add(opts,'fill opacity', sprintf(m2t.ff,faceAlpha)); | |
| end | |
| % Edge | |
| edgeColor = get(h, 'EdgeColor'); | |
| [m2t, opts] = setColor(m2t, h, opts, 'draw', edgeColor, 'none'); | |
| % Data | |
| binEdges = get(h, 'BinEdges'); | |
| binValue = get(h, 'Values'); | |
| data = [binEdges(:), [binValue(:); binValue(end)]]; | |
| % Bar type (depends on orientation) | |
| isVertical = strcmpi(get(h,'Orientation'),'vertical'); | |
| if isVertical | |
| opts = opts_add(opts, 'ybar interval'); | |
| else | |
| opts = opts_add(opts, 'xbar interval'); | |
| data = fliplr(data); | |
| end | |
| % Make table | |
| [m2t, table, tableOptions] = makeTable(m2t, {'x','y'},data); | |
| % Add 'area legend' (x/ybar interval legend do not seem to work) | |
| opts = opts_add(opts, 'area legend'); | |
| % Print out | |
| drawOpts = opts_print(m2t, opts, ','); | |
| tabOpts = opts_print(m2t, tableOptions, ','); | |
| str = sprintf('\\addplot[%s] plot table[%s] {%s};\n', ... | |
| drawOpts, tabOpts, table); | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = drawBarseries(m2t, h) | |
| % Takes care of plots like the ones produced by MATLAB's hist. | |
| % The main pillar is Pgfplots's '{x,y}bar' plot. | |
| % | |
| % TODO Get rid of code duplication with 'drawAxes'. | |
| str = ''; | |
| if ~isVisible(h) | |
| return; % don't bother drawing invisible things | |
| end | |
| % 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.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'log origin', 'infty'); | |
| %TODO: wait for pgfplots to implement other base values (see #438) | |
| end | |
| xData = get(h, 'XData'); | |
| yData = get(h, 'YData'); | |
| % init drawOptions | |
| drawOptions = opts_new(); | |
| [barType, isHoriz] = getOrientationOfBarSeries(h); | |
| [m2t, drawOptions] = setBarLayoutOfBarSeries(m2t, h, barType, drawOptions); | |
| % define edge color | |
| edgeColor = get(h, 'EdgeColor'); | |
| lineStyle = get(h, 'LineStyle'); | |
| if isNone(lineStyle) | |
| drawOptions = opts_add(drawOptions, 'draw', 'none'); | |
| else | |
| [m2t, drawOptions] = setColor(m2t, h, drawOptions, 'draw', ... | |
| edgeColor, 'none'); | |
| end | |
| [m2t, drawOptions] = getFaceColorOfBar(m2t, h, drawOptions); | |
| % Add 'area legend' to the options as otherwise the legend indicators | |
| % will just highlight the edges. | |
| drawOptions = opts_add(drawOptions, 'area legend'); | |
| % plot the thing | |
| if isHoriz | |
| [yDataPlot, xDataPlot] = deal(xData, yData); % swap values | |
| else | |
| [xDataPlot, yDataPlot] = deal(xData, yData); | |
| end | |
| [m2t, table, tabOpts] = makeTable(m2t, '', xDataPlot, '', yDataPlot); | |
| str = sprintf('\\addplot[%s] plot table[%s] {%s};\n', ... | |
| opts_print(m2t, drawOptions, ','), ... | |
| opts_print(m2t, tabOpts, ','), table); | |
| end | |
| % ============================================================================== | |
| function [barType, isHorizontal] = getOrientationOfBarSeries(h) | |
| % determines the orientation (horizontal/vertical) of a BarSeries object | |
| isHorizontal = strcmpi(get(h, 'Horizontal'), 'on'); | |
| if isHorizontal | |
| barType = 'xbar'; | |
| else | |
| barType = 'ybar'; | |
| end | |
| end | |
| % ============================================================================== | |
| function [m2t, drawOptions] = setBarLayoutOfBarSeries(m2t, h, barType, drawOptions) | |
| % sets the options specific to a bar layour (grouped vs stacked) | |
| barlayout = get(h, 'BarLayout'); | |
| switch barlayout | |
| case 'grouped' % grouped bar plots | |
| % Get number of bars series and bar series id | |
| [numBarSeries, barSeriesId] = getNumBarAndId(h); | |
| % Maximum group width relative to the minimum distance between two | |
| % x-values. See <MATLAB>/toolbox/matlab/specgraph/makebars.m | |
| maxGroupWidth = 0.8; | |
| if numBarSeries == 1 | |
| groupWidth = 1.0; | |
| else | |
| groupWidth = min(maxGroupWidth, numBarSeries/(numBarSeries+1.5)); | |
| end | |
| % Calculate the width of each bar and the center point shift as in | |
| % makebars.m | |
| % Get the shifts of the bar centers. | |
| % In case of numBars==1, this returns 0, | |
| % In case of numBars==2, this returns [-1/4, 1/4], | |
| % In case of numBars==3, this returns [-1/3, 0, 1/3], | |
| % and so forth. | |
| assumedBarWidth = groupWidth/numBarSeries; % assumption | |
| barShift = (barSeriesId - 0.5) * assumedBarWidth - groupWidth/2; | |
| % 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. | |
| barWidth = get(h, 'BarWidth') * assumedBarWidth; | |
| % Bar type | |
| drawOptions = opts_add(drawOptions, barType); | |
| % Bar width | |
| drawOptions = opts_add(drawOptions, 'bar width', ... | |
| formatDim(barWidth, '')); | |
| % Bar shift | |
| if barShift ~= 0 | |
| drawOptions = opts_add(drawOptions, 'bar shift', ... | |
| formatDim(barShift, '')); | |
| end | |
| case 'stacked' % stacked plots | |
| % Pass option to parent axis & disallow anything but stacked plots | |
| % Make sure this happens exactly *once*. | |
| if ~m2t.axesContainers{end}.barAddedAxisOption; | |
| barWidth = get(h, 'BarWidth'); | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, ... | |
| 'bar width', formatDim(barWidth,'')); | |
| m2t.axesContainers{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 | |
| bargroup = bargroup{1}; | |
| end | |
| % Get bar series Id | |
| [dummy, barSeriesId] = ismember(h, bargroup); | |
| end | |
| % ============================================================================== | |
| function [m2t, drawOptions] = getFaceColorOfBar(m2t, h, drawOptions) | |
| % retrieve the FaceColor of a barseries object | |
| if ~isempty(get(h,'Children')) | |
| % quite oddly, before MATLAB R2014b this value is stored in a child | |
| % patch and not in the object itself | |
| obj = get(h, 'Children'); | |
| else % R2014b and newer | |
| obj = h; | |
| end | |
| faceColor = get(obj, 'FaceColor'); | |
| [m2t, drawOptions] = setColor(m2t, h, drawOptions, 'fill', faceColor); | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = drawStemSeries(m2t, h) | |
| [m2t, str] = drawStemOrStairSeries_(m2t, h, 'ycomb'); | |
| end | |
| function [m2t, str] = drawStairSeries(m2t, h) | |
| [m2t, str] = drawStemOrStairSeries_(m2t, h, 'const plot'); | |
| end | |
| function [m2t, str] = drawStemOrStairSeries_(m2t, h, plotType) | |
| str = ''; | |
| lineStyle = get(h, 'LineStyle'); | |
| lineWidth = get(h, 'LineWidth'); | |
| marker = get(h, 'Marker'); | |
| if (isNone(lineStyle) || lineWidth==0) && isNone(marker) | |
| return % nothing to plot! | |
| end | |
| % deal with draw options | |
| color = get(h, 'Color'); | |
| [m2t, plotColor] = getColor(m2t, h, color, 'patch'); | |
| lineOptions = getLineOptions(m2t, lineStyle, lineWidth); | |
| [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); | |
| % insert draw options | |
| drawOpts = opts_print(m2t, drawOptions, ','); | |
| % plot the thing | |
| xData = get(h, 'XData'); | |
| yData = get(h, 'YData'); | |
| [m2t, table, tabOpts] = makeTable(m2t, '', xData, '', yData); | |
| str = sprintf('%s\\addplot[%s] plot table[%s] {%s};\n', ... | |
| str, drawOpts, opts_print(m2t, tabOpts, ','), table); | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = drawAreaSeries(m2t, h) | |
| % Takes care of MATLAB's stem plots. | |
| % | |
| % TODO Get rid of code duplication with 'drawAxes'. | |
| str = ''; | |
| if ~isfield(m2t, 'addedAreaOption') || isempty(m2t.addedAreaOption) || ~m2t.addedAreaOption | |
| % Add 'area style' to axes options. | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, 'area style'); | |
| m2t.axesContainers{end}.options = ... | |
| opts_add(m2t.axesContainers{end}.options, 'stack plots', 'y'); | |
| m2t.addedAreaOption = true; | |
| end | |
| % Handle draw options. | |
| % define edge color | |
| drawOptions = opts_new(); | |
| edgeColor = get(h, 'EdgeColor'); | |
| [m2t, xEdgeColor] = getColor(m2t, h, edgeColor, 'patch'); | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % define face color; | |
| if ~isempty(get(h,'Children')) | |
| % quite oddly, before MATLAB R2014b this value is stored in a child | |
| % patch and not in the object itself | |
| obj = get(h, 'Children'); | |
| else % R2014b and newer | |
| obj = h; | |
| end | |
| faceColor = get(obj, 'FaceColor'); | |
| [m2t, xFaceColor] = getColor(m2t, h, faceColor, 'patch'); | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % gather the draw options | |
| lineStyle = get(h, 'LineStyle'); | |
| drawOptions = opts_add(drawOptions, 'fill', xFaceColor); | |
| if isNone(lineStyle) | |
| drawOptions = opts_add(drawOptions, 'draw', 'none'); | |
| else | |
| drawOptions = opts_add(drawOptions, 'draw', xEdgeColor); | |
| end | |
| % Toggle legend entry | |
| drawOptions = maybeShowInLegend(m2t.currentHandleHasLegend, drawOptions); | |
| drawOpts = opts_print(m2t, drawOptions, ','); | |
| % plot the thing | |
| xData = get(h, 'XData'); | |
| yData = get(h, 'YData'); | |
| [m2t, table, tabOpts] = makeTable(m2t, '', xData, '', yData); | |
| str = sprintf('%s\\addplot[%s] plot table[%s]{%s}\n\\closedcycle;\n',... | |
| str, drawOpts, opts_print(m2t, tabOpts, ','), table); | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = drawQuiverGroup(m2t, h) | |
| % Takes care of MATLAB's quiver plots. | |
| % used for arrow styles, in case there are more than one quiver fields | |
| m2t.quiverId = m2t.quiverId + 1; | |
| str = ''; | |
| [x,y,z,u,v,w] = getAndRescaleQuivers(m2t,h); | |
| is3D = m2t.axesContainers{end}.is3D; | |
| % prepare output | |
| if is3D | |
| name = 'addplot3'; | |
| format = [m2t.ff,',',m2t.ff,',',m2t.ff]; | |
| else % 2D plotting | |
| name = 'addplot'; | |
| format = [m2t.ff,',',m2t.ff]; | |
| end | |
| data = NaN(6,numel(x)); | |
| data(1,:) = x; | |
| data(2,:) = y; | |
| data(3,:) = z; | |
| data(4,:) = x + u; | |
| data(5,:) = y + v; | |
| data(6,:) = z + w; | |
| if ~is3D | |
| data([3 6],:) = []; % remove Z-direction | |
| end | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % gather the arrow options | |
| showArrowHead = get(h, 'ShowArrowHead'); | |
| lineStyle = get(h, 'LineStyle'); | |
| lineWidth = get(h, 'LineWidth'); | |
| if (isNone(lineStyle) || lineWidth==0) && ~showArrowHead | |
| return | |
| end | |
| arrowOpts = opts_new(); | |
| if showArrowHead | |
| arrowOpts = opts_add(arrowOpts, '->'); | |
| else | |
| arrowOpts = opts_add(arrowOpts, '-'); | |
| end | |
| color = get(h, 'Color'); | |
| lineOpts = getLineOptions(m2t, lineStyle, lineWidth); | |
| [m2t, arrowcolor] = getColor(m2t, h, color, 'patch'); | |
| arrowOpts = opts_add(arrowOpts, 'color', arrowcolor); | |
| arrowOpts = opts_merge(arrowOpts, lineOpts); | |
| % define arrow style | |
| arrowOptions = opts_print(m2t, arrowOpts, ','); | |
| % Append the arrow style to the TikZ options themselves. | |
| % TODO: Look into replacing this by something more 'local', | |
| % (see \pgfplotset{}). | |
| m2t.content.options = opts_add(m2t.content.options,... | |
| sprintf('arrow%d/.style', m2t.quiverId), ... | |
| ['{', arrowOptions, '}']); | |
| % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
| % return the vector field code | |
| str = [str, ... | |
| sprintf(['\\',name,' [arrow',num2str(m2t.quiverId), '] ', ... | |
| 'coordinates{(',format,') (',format,')};\n'],... | |
| data)]; | |
| %FIXME: external | |
| 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.axesContainers{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 getOrDefault(h, 'AutoScale', true) | |
| 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) | |
| % Takes care of MATLAB's error bar plots. | |
| if isa(h,'matlab.graphics.chart.primitive.ErrorBar') % MATLAB R2014b+ | |
| hData = h; | |
| upDev = get(h, 'UData'); | |
| loDev = get(h, 'LData'); | |
| yDeviations = [upDev(:), loDev(:)]; | |
| else % Legacy Handling (Octave and MATLAB R2014a and older): | |
| % 'errorseries' plots have two line-plot children, one of which contains | |
| % the information about the center points; 'XData' and 'YData' components | |
| % are both of length n. | |
| % The other contains the information about the deviations (errors), more | |
| % more precisely: the lines to be drawn. Those are | |
| % ___ | |
| % | | |
| % | | |
| % X <-- (x0,y0) | |
| % | | |
| % _|_ | |
| % | |
| % X: x0, x0, x0-eps, x0+eps, x0-eps, x0+eps; | |
| % Y: y0-dev, y0+dev, y0-dev, y0-dev, y0+dev, y0+dev. | |
| % | |
| % Hence, 'XData' and 'YData' are of length 6*n and contain redundant info. | |
| % Some versions of MATLAB(R) insert more columns with NaNs (to be able to | |
| % pass the entire X, Y arrays into plot()) such that the data is laid out as | |
| % | |
| % X: x0, x0, NaN, x0-eps, x0+eps, NaN, x0-eps, x0+eps; | |
| % Y: y0-dev, y0+dev, NaN, y0-dev, y0-dev, NaN, y0+dev, y0+dev, | |
| % | |
| % or with another columns of NaNs added at the end. | |
| c = get(h, 'Children'); | |
| % Find out which contains the data and which the deviations. | |
| %TODO: this can be simplified using sort | |
| n1 = length(get(c(1),'XData')); | |
| n2 = length(get(c(2),'XData')); | |
| if n2 == 6*n1 | |
| % 1 contains centerpoint info | |
| dataIdx = 1; | |
| errorIdx = 2; | |
| numDevData = 6; | |
| elseif n1 == 6*n2 | |
| % 2 contains centerpoint info | |
| dataIdx = 2; | |
| errorIdx = 1; | |
| numDevData = 6; | |
| elseif n2 == 9*n1-1 || n2 == 9*n1 | |
| % 1 contains centerpoint info | |
| dataIdx = 1; | |
| errorIdx = 2; | |
| numDevData = 9; | |
| elseif n1 == 9*n2-1 || n1 == 9*n2 | |
| % 2 contains centerpoint info | |
| dataIdx = 2; | |
| errorIdx = 1; | |
| numDevData = 9; | |
| else | |
| error('drawErrorBars:errorMatch', ... | |
| 'Sizes of and error data not matching (6*%d ~= %d and 6*%d ~= %d, 9*%d-1 ~= %d, 9*%d-1 ~= %d).', ... | |
| n1, n2, n2, n1, n1, n2, n2, n1); | |
| end | |
| hData = c(dataIdx); | |
| hError = c(errorIdx); | |
| % prepare error array (that is, gather the y-deviations) | |
| yValues = get(hData , 'YData'); | |
| yErrors = get(hError, 'YData'); | |
| n = length(yValues); | |
| yDeviations = zeros(n, 2); | |
| %TODO: this can be vectorized | |
| for k = 1:n | |
| % upper deviation | |
| kk = numDevData*(k-1) + 1; | |
| upDev = abs(yValues(k) - yErrors(kk)); | |
| % lower deviation | |
| kk = numDevData*(k-1) + 2; | |
| loDev = abs(yValues(k) - yErrors(kk)); | |
| yDeviations(k,:) = [upDev loDev]; | |
| end | |
| end | |
| % Now run drawLine() with deviation information. | |
| [m2t, str] = drawLine(m2t, hData, yDeviations); | |
| end | |
| % ============================================================================== | |
| function [m2t, str] = drawEllipse(m2t, handle) | |
| % Takes care of MATLAB's ellipse annotations. | |
| % | |
| % c = get(h, 'Children'); | |
| drawOptions = opts_new(); | |
| p = get(handle,'position'); | |
| radius = p([3 4]) / 2; | |
| center = p([1 2]) + radius; | |
| lineStyle = get(handle, 'LineStyle'); | |
| lineWidth = get(handle, 'LineWidth'); | |
| color = get(handle, 'Color'); | |
| [m2t, xcolor] = getColor(m2t, handle, color, 'patch'); | |
| lineOptions = getLineOptions(m2t, lineStyle, lineWidth); | |
| 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); | |
| opt = opts_print(m2t, 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) | |
| % 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.cmdOpts.Results.showHiddenStrings | |
| child = findobj(handle, 'type', 'text'); | |
| [m2t, str{end+1}] = drawText(m2t, child); | |
| 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 = {}; | |
| 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 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 m2t.cmdOpts.Results.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.cmdOpts.Results.strict | |
| % Sampled colors. | |
| numColors = size(m2t.currentHandles.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_add(axisOptions, ... | |
| 'colorbar style', ... | |
| ['{' opts_print(m2t, 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'; | |
| 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.currentHandles.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.currentHandles.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' | |
| error('matlab2tikz:anycolor2rgb:ColorModelNoneNotAllowed',... | |
| ['Color model ''none'' not allowed here. ',... | |
| 'Make sure this case gets intercepted before.']); | |
| 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 = get(patchhandle, 'Children'); | |
| cdata = get(child, 'CData'); | |
| end | |
| if isempty(cdata) || ~isnumeric(cdata) | |
| % R2014b+: CData is implicit by the ordering of the siblings | |
| siblings = get(get(patchhandle, 'Parent'), 'Children'); | |
| 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.currentHandles.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.currentHandles.colormap, 1); | |
| colorindex = zeros(size(cdata)); | |
| idx1 = cdata <= clim(1); | |
| idx2 = cdata >= clim(2); | |
| idx3 = ~idx1 & ~idx2; | |
| colorindex(idx1) = 1; | |
| colorindex(idx2) = m; | |
| colorindex(idx3) = fix((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, lOpts] = getLegendOpts(m2t, handle) | |
| % Need to check that there's nothing inside visible before we | |
| % abandon this legend -- an invisible property of the parent just | |
| % means the legend has no box. | |
| children = get(handle, 'Children'); | |
| if ~isVisible(handle) && ~any(isVisible(children)) | |
| return | |
| end | |
| lStyle = opts_new(); | |
| lStyle = legendPosition(m2t, handle, lStyle); | |
| lStyle = legendOrientation(m2t, handle, lStyle); | |
| lStyle = legendEntryAlignment(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) || strcmpi(get(handle,'box'),'off') | |
| 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'; | |
| lOpts = opts_print(m2t, lStyle, ','); | |
| end | |
| % ============================================================================== | |
| function [lStyle] = legendOrientation(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] = legendPosition(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.currentHandles.gca, 'Position'); | |
| axesUnit = get(m2t.currentHandles.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] = legendEntryAlignment(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 | |
| % if the axis is logscaled, MATLAB does not store the labels, | |
| % but the exponents to 10 | |
| if isLogAxis | |
| for k = 1:length(tickLabels) | |
| if isnumeric(tickLabels{k}) | |
| str = num2str(tickLabels{k}); | |
| else | |
| str = tickLabels{k}; | |
| end | |
| if strcmpi(tickLabelMode,'auto') | |
| tickLabels{k} = sprintf('$10^{%s}$', str); | |
| end | |
| 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.cmdOpts.Results.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 = 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 | |
| table = lower(table); % convert NaN and Inf to lower case for TikZ | |
| table = [join(m2t, [header;table], ROWSEP) ROWSEP]; | |
| if m2t.cmdOpts.Results.externalData | |
| % output data to external file | |
| m2t.dataFileNo = m2t.dataFileNo + 1; | |
| [filename, latexFilename] = externalFilename(m2t, m2t.dataFileNo, '.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.extraRgbColorNames]; | |
| colorSpecs = [xcolColorSpecs, m2t.extraRgbColorSpecs]; | |
| %% check if rgb is a predefined color | |
| for kColor = 1:length(colorSpecs) | |
| Ck = colorSpecs{kColor}(:); | |
| if max(abs(Ck - rgb(:))) < m2t.colorPrecision | |
| 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.colorPrecision | |
| 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.extraRgbColorNames)+1); | |
| m2t.extraRgbColorNames{end+1} = colorLiteral; | |
| m2t.extraRgbColorSpecs{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, ',') | |
| if isempty(cellstr) | |
| newstr = ''; | |
| return | |
| end | |
| % convert all values to strings first | |
| nElem = numel(cellstr); | |
| for k = 1:nElem | |
| if isnumeric(cellstr{k}) | |
| cellstr{k} = sprintf(m2t.ff, cellstr{k}); | |
| elseif iscell(cellstr{k}) | |
| cellstr{k} = join(m2t, cellstr{k}, delimiter); | |
| % this will fail for heavily nested cells | |
| elseif ~ischar(cellstr{k}) | |
| error('matlab2tikz:join:NotCellstrOrNumeric',... | |
| 'Expected cellstr or numeric.'); | |
| end | |
| end | |
| % inspired by strjoin of recent versions of MATLAB | |
| newstr = cell(2,nElem); | |
| newstr(1,:) = reshape(cellstr, 1, nElem); | |
| newstr(2,1:nElem-1) = {delimiter}; % put delimiters in-between the elements | |
| newstr = [newstr{:}]; | |
| 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.currentHandles.gcf, 'Position'); | |
| figuresize = figuresize([3 4]); | |
| figureunit = get(m2t.currentHandles.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.cmdOpts.Results.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 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 opts = addIfNotDefault(m2t, type, handle, key, default, pgfKey, opts) | |
| % sets an option in the options array named `pgfKey` if the MATLAB option is | |
| % not a default value | |
| % FIXME: this function is currently unused -- remove it in the future? | |
| [value, isDefault] = getAndCheckDefault(type, handle, key, default); | |
| if ~isDefault || m2t.cmdOpts.Results.strict | |
| opts = opts_add(opts, pgfKey, value); | |
| end | |
| end | |
| % ============================================================================== | |
| function bool = isVisible(handles) | |
| % Determines whether an object is actually visible or not. | |
| bool = strcmpi(get(handles,'Visible'), 'on'); | |
| % There's another handle property, 'HandleVisibility', which may or may not | |
| % determine the visibility of the object. Empirically, it seems to be 'off' | |
| % whenever we're dealing with an object that's not user-created, such as | |
| % automatic axis ticks, baselines in bar plots, axis lines for polar plots | |
| % and so forth. For now, don't check 'HandleVisibility'. | |
| 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 = double(axesHandles(idx)); | |
| % Compute the bounding box if width or height of the figure are set by | |
| % parameter | |
| if ~isempty(m2t.cmdOpts.Results.width) || ~isempty(m2t.cmdOpts.Results.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.cmdOpts.Results.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.cmdOpts.Results.showWarnings | |
| warning('matlab2tikz:userWarning', message, varargin{:}); | |
| end | |
| 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.cmdOpts.Results.(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(m2t, 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 | |
| strings = cellstrOneLinePerCell(strings); | |
| % Now loop over the strings and return them pretty-printed in c. | |
| c = {}; | |
| for k = 1:length(strings) | |
| % linear indexing for independence of cell array dimensions | |
| s = strings{k}; | |
| % If the user set the matlab2tikz parameter 'parseStrings' to false, no | |
| % parsing of strings takes place, thus making the user 100% responsible. | |
| if ~m2t.cmdOpts.Results.parseStrings | |
| c = 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 | |
| % The interpreter property of the text element defines how the string | |
| % is parsed | |
| switch lower(interpreter) | |
| case 'latex' % Basic subset of the LaTeX markup language | |
| % Replace $$...$$ with $...$ for groups, but otherwise leave | |
| % untouched. | |
| % Displaymath \[...\] seems to be unsupported by TikZ/PGF. | |
| % If this changes, use '\\[$2\\]' as replacement below. | |
| % Do not escape dollar in replacement string (e.g., "\$$2\$"), | |
| % since this is not properly handled by octave 3.8.2. | |
| string = regexprep(s, '(\$\$)(.*?)(\$\$)', '$$2$'); | |
| case 'tex' % Subset of plain TeX markup language | |
| % Deal with UTF8 characters. | |
| string = s; | |
| % degree symbol following "^" or "_" needs to be escaped | |
| string = regexprep(string, '([\^\_])°', '$1{{}^\\circ}'); | |
| string = strrep(string, '°', '^\circ'); | |
| string = strrep(string, '∞', '\infty'); | |
| % Parse string piece-wise in a separate function. | |
| string = parseTexString(m2t, string); | |
| case 'none' % Literal characters | |
| % Make special characters TeX compatible | |
| string = strrep(s, '\', '\textbackslash{}'); | |
| % Note: '{' and '}' can't be converted to '\{' and '\}', | |
| % respectively, via strrep(...) as this would lead to | |
| % backslashes converted to '\textbackslash\{\}' because | |
| % the backslash was converted to '\textbackslash{}' in | |
| % the previous step. Using regular expressions with | |
| % negative look-behind makes sure any braces in 'string' | |
| % were not introduced by escaped backslashes. | |
| % Also keep in mind that escaping braces before backslashes | |
| % would not remedy the issue -- in that case 'string' would | |
| % contain backslashes introduced by brace escaping that are | |
| % not supposed to be printable characters. | |
| repl = switchMatOct('\\{', '\{'); | |
| string = regexprep(string, '(?<!\\textbackslash){', repl); | |
| repl = switchMatOct('\\}', '\}'); | |
| string = regexprep(string, '(?<!\\textbackslash{)}', repl); | |
| string = strrep(string, '$', '\$'); | |
| string = strrep(string, '%', '\%'); | |
| string = strrep(string, '_', '\_'); | |
| string = strrep(string, '^', '\textasciicircum{}'); | |
| string = strrep(string, '#', '\#'); | |
| string = strrep(string, '&', '\&'); | |
| string = strrep(string, '~', '\textasciitilde{}'); % or '\~{}' | |
| % Clean up: remove superfluous '{}' if it's followed by a backslash | |
| string = strrep(string, '{}\', '\'); | |
| % Clean up: remove superfluous '{}' at the end of 'string' | |
| string = regexprep(string, '\{\}$', ''); | |
| % Make sure to return a string and not a cellstr. | |
| if iscellstr(string) | |
| string = string{1}; | |
| end | |
| otherwise | |
| error('matlab2tikz:prettyPrint', 'Unknown interpreter'); | |
| end | |
| c{end+1} = string; | |
| end | |
| end | |
| % ============================================================================== | |
| function strings = cellstrOneLinePerCell(strings) | |
| % convert to cellstr that contains only one-line strings | |
| if ischar(strings) | |
| strings = cellstr(strings); | |
| elseif iscellstr(strings) | |
| cs = {}; | |
| for s = strings | |
| tmp = cellstr(s); | |
| cs = {cs{:}, tmp{:}}; | |
| end | |
| strings = cs; | |
| else | |
| error('matlab2tikz:cellstrOneLinePerCell', ... | |
| 'Data type not understood.'); | |
| end | |
| end | |
| % ============================================================================== | |
| function parsed = parseTexString(m2t, string) | |
| if iscellstr(string) | |
| % Convert cell string to regular string, otherwise MATLAB complains | |
| string = string{:}; | |
| end | |
| % Get the position of all braces | |
| bracesPos = regexp(string, '\{|\}'); | |
| % Exclude braces that are part of any of these MATLAB-supported TeX commands: | |
| % \color{...} \color[...]{...} \fontname{...} \fontsize{...} | |
| [sCmd, eCmd] = regexp(string, '\\(color(\[[^\]]*\])?|fontname|fontsize)\{[^}]*\}'); | |
| for i = 1:length(sCmd) | |
| bracesPos(bracesPos >= sCmd(i) & bracesPos <= eCmd(i)) = []; | |
| end | |
| % Exclude braces that are preceded by an odd number of backslashes which | |
| % means the brace is escaped and thus to be printed, not a grouping brace | |
| expr = '(?<!\\)(\\\\)*\\(\{|\})'; | |
| escaped = regexp(string, expr, 'end'); | |
| % It's necessary to go over 'string' with the same RegEx again to catch | |
| % overlapping matches, e.g. string == '\{\}'. In such a case the simple | |
| % regexp(...) above only finds the first brace. What we have to do is look | |
| % only at the part of 'string' that starts with the first brace but doesn't | |
| % encompass its escaping backslash. Iterating over all previously found | |
| % matches makes sure all overlapping matches are found, too. That way even | |
| % cases like string == '\{\} \{\}' are handled correctly. | |
| % The call to unique(...) is not necessary to get the behavior described, but | |
| % by removing duplicates in 'escaped' it's cleaner than without. | |
| for i = escaped | |
| escaped = unique([escaped, regexp(string(i:end), expr, 'end') + i-1]); | |
| end | |
| % Now do the actual removal of escaped braces | |
| for i = 1:length(escaped) | |
| bracesPos(bracesPos == escaped(i)) = []; | |
| end | |
| parsed = ''; | |
| % Have a virtual brace one character left of where the actual string | |
| % begins (remember, MATLAB strings start counting at 1, not 0). This is | |
| % to make sure substrings left of the first brace get parsed, too. | |
| prevBracePos = 0; | |
| % Iterate over all the brace positions in order to split up 'string' | |
| % at those positions and then parse the substrings. A virtual brace is | |
| % added right of where the actual string ends to make sure substrings | |
| % right of the right-most brace get parsed as well. | |
| for currBracePos = [bracesPos, length(string)+1] | |
| if (prevBracePos + 1) < currBracePos | |
| % Parse the substring between (but not including) prevBracePos | |
| % and currBracePos, i.e. between the previous brace and the | |
| % current one (but only if there actually is a non-empty | |
| % substring). Then append it to the output string. | |
| substring = string(prevBracePos+1 : currBracePos-1); | |
| parsed = [parsed, parseTexSubstring(m2t, substring)]; | |
| end | |
| if currBracePos <= length(string) | |
| % Append the brace itself to the output string, but only if the | |
| % current brace position is within the limits of the string, i.e. | |
| % don't append anything for the last, virtual brace that is only | |
| % there to enable parsing of substrings beyond the right-most | |
| % actual brace. | |
| brace = string(currBracePos); | |
| parsed = [parsed, brace]; | |
| end | |
| % The current brace position will be next iteration's previous one | |
| prevBracePos = currBracePos; | |
| end | |
| % Enclose everything in $...$ to use math mode | |
| parsed = ['$' parsed '$']; | |
| % ...except when everything is text | |
| parsed = regexprep(parsed, '^\$\\text\{([^}]*)\}\$$', '$1'); | |
| % start-> $ \text {(non-}) } $<-end | |
| % ...or when the parsed string is empty | |
| parsed = regexprep(parsed, '^\$\$$', ''); | |
| % Ensure math mode for pipe symbol (issue #587) | |
| parsed = strrep(parsed, '|', '$|$'); | |
| end | |
| % ============================================================================== | |
| function string = parseTexSubstring(m2t, string) | |
| origstr = string; % keep this for warning messages | |
| % Font families (italic, bold, etc.) get a trailing '{}' because they may be | |
| % followed by a letter which would produce an error in (La)TeX. | |
| for i = {'it', 'bf', 'rm', 'sl'} | |
| string = strrep(string, ['\' i{:}], ['\' i{:} '{}']); | |
| end | |
| % The same holds true for special characters like \alpha | |
| % The list of MATLAB-supported TeX characters was taken from | |
| % http://www.mathworks.com/help/techdoc/ref/text_props.html#String | |
| named = {'alpha', 'angle', 'ast', 'beta', 'gamma', 'delta', ... | |
| 'epsilon', 'zeta', 'eta', 'theta', 'vartheta', 'iota', ... | |
| 'kappa', 'lambda', 'mu', 'nu', 'xi', 'pi', 'rho', ... | |
| 'sigma', 'varsigma', 'tau', 'equiv', 'Im', 'otimes', ... | |
| 'cap', 'int', 'rfloor', 'lfloor', 'perp', 'wedge', ... | |
| 'rceil', 'vee', 'langle', 'upsilon', 'phi', 'chi', ... | |
| 'psi', 'omega', 'Gamma', 'Delta', 'Theta', 'Lambda', ... | |
| 'Xi', 'Pi', 'Sigma', 'Upsilon', 'Phi', 'Psi', 'Omega', ... | |
| 'forall', 'exists', 'ni', 'cong', 'approx', 'Re', ... | |
| 'oplus', 'cup', 'subseteq', 'lceil', 'cdot', 'neg', ... | |
| 'times', 'surd', 'varpi', 'rangle', 'sim', 'leq', ... | |
| 'infty', 'clubsuit', 'diamondsuit', 'heartsuit', ... | |
| 'spadesuit', 'leftrightarrow', 'leftarrow', ... | |
| 'Leftarrow', 'uparrow', 'rightarrow', 'Rightarrow', ... | |
| 'downarrow', 'circ', 'pm', 'geq', 'propto', 'partial', ... | |
| 'bullet', 'div', 'neq', 'aleph', 'wp', 'oslash', ... | |
| 'supseteq', 'nabla', 'ldots', 'prime', '0', 'mid', ... | |
| 'copyright' }; | |
| for i = named | |
| string = strrep(string, ['\' i{:}], ['\' i{:} '{}']); | |
| % FIXME: Only append '{}' if there's an odd number of backslashes | |
| % in front of the items from 'named'. If it's an even | |
| % number instead, that means there's an escaped (printable) | |
| % backslash and some text like "alpha" after that. | |
| end | |
| % Some special characters' names are subsets of others, e.g. '\o' is | |
| % a subset of '\omega'. This would produce undesired double-escapes. | |
| % For example if '\o' was converted to '\o{}' after '\omega' has been | |
| % converted to '\omega{}' this would result in '\o{}mega{}' instead of | |
| % '\omega{}'. Had '\o' been converted to '\o{}' _before_ '\omega' is | |
| % converted then the result would be '\o{}mega' and thus also wrong. | |
| % To circumvent the problem all those special character names that are | |
| % subsets of others are now converted using a regular expression that | |
| % uses negative lookahead. The special handling of the backslash is | |
| % required for MATLAB/Octave compatibility. | |
| string = regexprep(string, '(\\)o(?!mega|times|plus|slash)', '$1o{}'); | |
| string = regexprep(string, '(\\)in(?!t|fty)', '$1in{}'); | |
| string = regexprep(string, '(\\)subset(?!eq)', '$1subset{}'); | |
| string = regexprep(string, '(\\)supset(?!eq)', '$1supset{}'); | |
| % Convert '\0{}' (TeX text mode) to '\emptyset{}' (TeX math mode) | |
| string = strrep(string, '\0{}', '\emptyset{}'); | |
| % Add skip to \fontsize | |
| % This is required for a successful LaTeX run on the output as in contrast | |
| % to MATLAB/Octave it requires the skip parameter (even if it's zero) | |
| string = regexprep(string, '(\\fontsize\{[^}]*\})', '$1{0}'); | |
| % Put '\o{}' inside \text{...} as it is a text mode symbol that does not | |
| % exist in math mode (and LaTeX gives a warning if you use it in math mode) | |
| string = strrep(string, '\o{}', '\text{\o{}}'); | |
| % Put everything that isn't a TeX command inside \text{...} | |
| expr = '(\\[a-zA-Z]+(\[[^\]]*\])?(\{[^}]*\}){1,2})'; | |
| % |( \cmd )( [...]? )( {...}{1,2} )| | |
| % ( subset $1 ) | |
| repl = '}$1\\text{'; | |
| string = regexprep(string, expr, repl); | |
| % ...\alpha{}... -> ...}\alpha{}\text{... | |
| string = ['\text{' string '}']; | |
| % ...}\alpha{}\text{... -> \text{...}\alpha{}\text{...} | |
| % '_' has to be in math mode so long as it's not escaped as '\_' in which | |
| % case it remains as-is. Extra care has to be taken to make sure any | |
| % backslashes in front of the underscore are not themselves escaped and | |
| % thus printable backslashes. This is the case if there's an even number | |
| % of backslashes in a row. | |
| repl = '$1}_\\text{'; | |
| string = regexprep(string, '(?<!\\)((\\\\)*)_', repl); | |
| % '^' has to be in math mode so long as it's not escaped as '\^' in which | |
| % case it is expressed as '\textasciicircum{}' for compatibility with | |
| % regular TeX. Same thing here regarding even/odd number of backslashes | |
| % as in the case of underscores above. | |
| repl = '$1\\textasciicircum{}'; | |
| string = regexprep(string, '(?<!\\)((\\\\)*)\\\^', repl); | |
| repl = '$1}^\\text{'; | |
| string = regexprep(string, '(?<!\\)((\\\\)*)\^', repl); | |
| % '<' and '>' has to be either in math mode or needs to be typeset as | |
| % '\textless' and '\textgreater' in textmode | |
| % This is handled better, if 'parseStringsAsMath' is activated | |
| if m2t.cmdOpts.Results.parseStringsAsMath == 0 | |
| string = regexprep(string, '<', '\\textless{}'); | |
| string = regexprep(string, '>', '\\textgreater{}'); | |
| end | |
| % Move font styles like \bf into the \text{} command. | |
| expr = '(\\bf|\\it|\\rm|\\fontname)({\w*})+(\\text{)'; | |
| while regexp(string, expr) | |
| string = regexprep(string, expr, '$3$1$2'); | |
| end | |
| % Replace Fontnames | |
| [dummy, dummy, dummy, dummy, fonts, dummy, subStrings] = regexp(string, '\\fontname{(\w*)}'); %#ok | |
| fonts = fonts2tex(fonts); | |
| subStrings = [subStrings; fonts, {''}]; | |
| string = cell2mat(subStrings(:)'); | |
| % Merge adjacent \text fields: | |
| string = mergeAdjacentTexCmds(string, '\text'); | |
| % '\\' has to be escaped to '\textbackslash{}' | |
| % This cannot be done with strrep(...) as it would replace e.g. 4 backslashes | |
| % with three times the replacement string because it finds overlapping matches | |
| % (see http://www.mathworks.de/help/techdoc/ref/strrep.html) | |
| % Note: Octave's backslash handling is broken. Even though its output does | |
| % not resemble MATLAB's, the same m2t code is used for either software. That | |
| % way MATLAB-compatible code produces the same matlab2tikz output no matter | |
| % which software it's executed in. So long as this MATLAB incompatibility | |
| % remains in Octave you're probably better off not using backslashes in TeX | |
| % text anyway. | |
| string = regexprep(string, '(\\)\\', '$1textbackslash{}'); | |
| % '_', '^', '{', and '}' are already escaped properly, even in MATLAB's TeX | |
| % dialect (and if they're not, that's intentional) | |
| % Escape "$", "%", and "#" to make them compatible to true TeX while in | |
| % MATLAB/Octave they are not escaped | |
| string = strrep(string, '$', '\$'); | |
| string = strrep(string, '%', '\%'); | |
| string = strrep(string, '#', '\#'); | |
| % Escape "§" as "\S" since it can give UTF-8 problems otherwise. | |
| % The TeX string 'a_§' in particular lead to problems in Octave 3.6.0. | |
| % m2t transcoded that string into '$\text{a}_\text{*}\text{#}$' with | |
| % * = 0xC2 and # = 0xA7 which corresponds with the two-byte UTF-8 | |
| % encoding. Even though this looks like an Octave bug that shows | |
| % during the '..._\text{abc}' to '..._\text{a}\text{bc}' conversion, | |
| % it's best to include the workaround here. | |
| string = strrep(string, '§', '\S{}'); | |
| string = escapeAmpersands(m2t, string, origstr); | |
| string = escapeTildes(m2t, string, origstr); | |
| % Convert '..._\text{abc}' and '...^\text{abc}' to '..._\text{a}\text{bc}' | |
| % and '...^\text{a}\text{bc}', respectively. | |
| % Things get a little more complicated if instead of 'a' it's e.g. '$'. The | |
| % latter has been converted to '\$' by now and simply extracting the first | |
| % character from '\text{\$bc}' would result in '\text{$}\text{$bc}' which | |
| % is syntactically wrong. Instead the whole command '\$' has to be moved in | |
| % front of the \text{...} block, e.g. '..._\text{\$bc}' -> '..._\$\text{bc}'. | |
| % Note that the problem does not occur for the majority of special characters | |
| % like '\alpha' because they use math mode and therefore are never inside a | |
| % \text{...} block to begin with. This means that the number of special | |
| % characters affected by this issue is actually quite small: | |
| % $ # % & _ { } \o § ~ \ ^ | |
| expr = ['(_|\^)(\\text)\{([^}\\]|\\\$|\\#|\\%|\\&|\\_|\\\{|\\\}|', ... | |
| ... % (_/^)(\text) {(non-}\| \$ | \#| \%| \&| \_| \{ | \} | | |
| ... % ($1)( $2 ) ( $3 -> | |
| '\\o\{\}|\\S\{\}|\\textasciitilde\{\}|\\textbackslash\{\}|', ... | |
| ... % \o{} | \S{} | \textasciitilde{} | \textbackslash{} | | |
| ... % <- $3 -> | |
| '\\textasciicircum\{\})']; | |
| % \textasciicircum{} ) | |
| % <- $3 ) | |
| string = regexprep(string, expr, '$1$2{$3}$2{'); | |
| string = parseStringsAsMath(m2t, string); | |
| % Clean up: remove empty \text{} | |
| string = strrep(string, '\text{}', ''); | |
| % \text{}\alpha{}\text{...} -> \alpha{}\text{...} | |
| % Clean up: convert '{}\' to '\' unless it's prefixed by a backslash which | |
| % means the opening brace is escaped and thus a printable character instead | |
| % of a grouping brace. | |
| string = regexprep(string, '(?<!\\)\{\}(\\)', '$1'); | |
| % \alpha{}\text{...} -> \alpha\text{...} | |
| % Clean up: convert '{}}' to '}' unless it's prefixed by a backslash | |
| string = regexprep(string, '(?<!\\)\{\}\}', '}'); | |
| % Clean up: remove '{}' at the end of 'string' unless it's prefixed by a | |
| % backslash | |
| string = regexprep(string, '(?<!\\)\{\}$', ''); | |
| end | |
| % ============================================================================== | |
| function string = escapeTildes(m2t, string, origstr) | |
| % Escape plain "~" in MATLAB and replace escaped "\~" in Octave with a proper | |
| % escape sequence. An un-escaped "~" produces weird output in Octave, thus | |
| % give a warning in that case | |
| switch getEnvironment | |
| case 'MATLAB' | |
| string = strrep(string, '~', '\textasciitilde{}'); % or '\~{}' | |
| case 'Octave' | |
| string = strrep(string, '\~', '\textasciitilde{}'); % ditto | |
| if regexp(string, '(?<!\\)~') | |
| userWarning(m2t, ... | |
| ['TeX string ''%s'' contains un-escaped ''~''. ' ... | |
| 'For proper display in Octave you probably ' ... | |
| 'want to escape it even though that''s ' ... | |
| 'incompatible with MATLAB. ' ... | |
| 'In the matlab2tikz output it will have its ' ... | |
| 'usual TeX function as a non-breaking space.'], ... | |
| origstr) | |
| end | |
| otherwise | |
| errorUnknownEnvironment(); | |
| end | |
| end | |
| % ============================================================================== | |
| function string = escapeAmpersands(m2t, string, origstr) | |
| % Escape plain "&" in MATLAB and replace it and the following character with | |
| % a space in Octave unless the "&" is already escaped | |
| switch getEnvironment | |
| case 'MATLAB' | |
| string = strrep(string, '&', '\&'); | |
| case 'Octave' | |
| % Ampersands should already be escaped in Octave. | |
| % Octave (tested with 3.6.0) handles un-escaped ampersands a little | |
| % funny in that it removes the following character, if there is one: | |
| % 'abc&def' -> 'abc ef' | |
| % 'abc&\deltaef' -> 'abc ef' | |
| % 'abc&$ef' -> 'abc ef' | |
| % 'abcdef&' -> 'abcdef' | |
| % Don't remove closing brace after '&' as this would result in | |
| % unbalanced braces | |
| string = regexprep(string, '(?<!\\)&(?!})', ' '); | |
| string = regexprep(string, '(?<!\\)&}', '}'); | |
| if regexp(string, '(?<!\\)&\\') | |
| % If there's a backslash after the ampersand, that means not only | |
| % the backslash should be removed but the whole escape sequence, | |
| % e.g. '\delta' or '\$'. Actually the '\delta' case is the | |
| % trickier one since by now 'string' would have been turned from | |
| % 'abc&\deltaef' into '\text{abc&}\delta{}\text{ef}', i.e. after | |
| % the ampersand first comes a closing brace and then '\delta'; | |
| % the latter as well as the ampersand itself should be removed | |
| % while the brace must remain in place to avoid unbalanced braces. | |
| userWarning(m2t, ... | |
| ['TeX string ''%s'' contains a special character ' ... | |
| 'after an un-escaped ''&''. The output generated ' ... | |
| 'by matlab2tikz will not precisely match that ' ... | |
| 'which you see in Octave itself in that the ' ... | |
| 'special character and the preceding ''&'' is ' ... | |
| 'not replaced with a space.'], origstr) | |
| end | |
| otherwise | |
| errorUnknownEnvironment(); | |
| end | |
| end | |
| % ============================================================================== | |
| function [string] = parseStringsAsMath(m2t, string) | |
| % Some further processing makes the output behave more like TeX math mode, | |
| % but only if the matlab2tikz parameter parseStringsAsMath=true. | |
| if m2t.cmdOpts.Results.parseStringsAsMath | |
| % Some characters should be in math mode: =-+/,.()<>0-9 | |
| expr = '(\\text)\{([^}=\-+/,.()<>0-9]*)([=\-+/,.()<>0-9]+)([^}]*)\}'; | |
| % \text {(any non-"x"/'}'char)( any "x" char )(non-}) } | |
| % ( $1 ) ( $2 )( $3 )( $4) | |
| while regexp(string, expr) | |
| % Iterating is necessary to catch all occurrences. See above. | |
| string = regexprep(string, expr, '$1{$2}$3$1{$4}'); | |
| end | |
| % \text{ } should be a math-mode space | |
| string = regexprep(string, '\\text\{(\s+)}', '$1'); | |
| % '<<' probably means 'much smaller than', i.e. '\ll' | |
| repl = switchMatOct('$1\\ll{}$2', '$1\ll{}$2'); | |
| string = regexprep(string, '([^<])<<([^<])', repl); | |
| % '>>' probably means 'much greater than', i.e. '\gg' | |
| repl = switchMatOct('$1\\gg{}$2', '$1\gg{}$2'); | |
| string = regexprep(string, '([^>])>>([^>])', repl); | |
| % Single letters are most likely variables and thus should be in math mode | |
| string = regexprep(string, '\\text\{([a-zA-Z])\}', '$1'); | |
| end | |
| end | |
| % ============================================================================== | |
| function tex = fonts2tex(fonts) | |
| % Returns a tex command for each fontname in the cell array fonts. | |
| if ~iscell(fonts) | |
| error('matlab2tikz:fonts2tex', ... | |
| 'Expecting a cell array as input.'); | |
| end | |
| tex = cell(size(fonts)); | |
| for ii = 1:numel(fonts) | |
| font = fonts{ii}{1}; | |
| % List of known fonts. | |
| switch lower(font) | |
| case 'courier' | |
| tex{ii} = '\ttfamily{}'; | |
| case 'times' | |
| tex{ii} = '\rmfamily{}'; | |
| case {'arial', 'helvetica'} | |
| tex{ii} = '\sffamily{}'; | |
| otherwise | |
| warning('matlab2tikz:fonts2tex', ... | |
| 'Unknown font ''%s''. Using tex default font.',font); | |
| % Unknown font -> Switch to standard font. | |
| tex{ii} = '\rm{}'; | |
| end | |
| end | |
| end | |
| % ============================================================================== | |
| function string = mergeAdjacentTexCmds(string, cmd) | |
| % Merges adjacent tex commands like \text into one command | |
| % If necessary, add a backslash | |
| if cmd(1) ~= '\' | |
| cmd = ['\' cmd]; | |
| end | |
| % Link each bracket to the corresponding bracket | |
| link = zeros(size(string)); | |
| pos = [regexp([' ' string], '([^\\]{)'), ... | |
| regexp([' ' string], '([^\\]})')]; | |
| pos = sort(pos); | |
| ii = 1; | |
| while ii <= numel(pos) | |
| if string(pos(ii)) == '}' | |
| link(pos(ii-1)) = pos(ii); | |
| link(pos(ii)) = pos(ii - 1); | |
| pos([ii-1, ii]) = []; | |
| ii = ii - 1; | |
| else | |
| ii = ii + 1; | |
| end | |
| end | |
| % Find dispensable commands | |
| pos = regexp(string, ['}\' cmd '{']); | |
| delete = zeros(0,1); | |
| len = numel(cmd); | |
| for p = pos | |
| l = link(p); | |
| if l > len && isequal(string(l-len:l-1), cmd) | |
| delete(end+1,1) = p; | |
| end | |
| end | |
| % 3. Remove these commands (starting from the back | |
| delete = repmat(delete, 1, len+2) + repmat(0:len+1,numel(delete), 1); | |
| string(delete(:)) = []; | |
| end | |
| function dims = pos2dims(pos) | |
| % Position quadruplet [left, bottom, width, height] to dimension structure | |
| dims = struct('left' , pos(1), 'bottom', pos(2)); | |
| if numel(pos) == 4 | |
| dims.width = pos(3); | |
| dims.height = pos(4); | |
| dims.right = dims.left + dims.width; | |
| dims.top = dims.bottom + dims.height; | |
| end | |
| end | |
| % OPTION ARRAYS ================================================================ | |
| function opts = opts_new() | |
| % create a new options array | |
| opts = cell(0,2); | |
| end | |
| function opts = opts_add(opts, key, value) | |
| % add a key-value pair to an options array (with duplication check) | |
| if ~exist('value','var') | |
| value = ''; | |
| end | |
| value = char(value); | |
| % Check if the key already exists. | |
| if opts_has(opts, key) | |
| oldValue = opts_get(opts, key); | |
| if isequal(value, oldValue) | |
| return; % no action needed: value already present | |
| else | |
| error('matlab2tikz:opts_add', ... | |
| ['Trying to add (%s, %s) to options, but it already ' ... | |
| 'contains the conflicting key-value pair (%s, %s).'], ... | |
| key, value, key, oldValue); | |
| end | |
| end | |
| opts = opts_append(opts, key, value); | |
| end | |
| function 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(m2t, opts, sep) | |
| % pretty print an options array | |
| 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 = join(m2t, c, sep); | |
| end | |
| % ============================================================================== | |
| function [env, versionString] = getEnvironment() | |
| % Checks if we are in MATLAB or Octave. | |
| persistent cache | |
| alternatives = {'MATLAB', 'Octave'}; | |
| if isempty(cache) | |
| for iCase = 1:numel(alternatives) | |
| env = alternatives{iCase}; | |
| vData = ver(env); | |
| if ~isempty(vData) % found the right environment | |
| versionString = vData.Version; | |
| % store in cache | |
| cache.env = env; | |
| cache.versionString = versionString; | |
| return; | |
| end | |
| end | |
| % fall-back values | |
| env = ''; | |
| versionString = ''; | |
| else | |
| env = cache.env; | |
| versionString = cache.versionString; | |
| end | |
| 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 bool = isVersionBelow(versionA, versionB) | |
| % Checks if versionA is smaller than versionB | |
| vA = versionArray(versionA); | |
| vB = versionArray(versionB); | |
| n = min(length(vA), length(vB)); | |
| deltaAB = vA(1:n) - vB(1:n); | |
| difference = find(deltaAB, 1, 'first'); | |
| % Empty difference then same version | |
| bool = ~isempty(difference) && deltaAB(difference) < 0; | |
| 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 arr = versionArray(str) | |
| % Converts a version string to an array. | |
| if ischar(str) | |
| % Translate version string from '2.62.8.1' to [2; 62; 8; 1]. | |
| switch getEnvironment | |
| case 'MATLAB' | |
| split = regexp(str, '\.', 'split'); % compatibility MATLAB < R2013a | |
| case 'Octave' | |
| split = strsplit(str, '.'); | |
| otherwise | |
| errorUnknownEnvironment(); | |
| end | |
| arr = str2num(char(split)); %#ok | |
| else | |
| arr = str; | |
| end | |
| arr = arr(:)'; | |
| 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 errorUnknownEnvironment() | |
| error('matlab2tikz:unknownEnvironment',... | |
| 'Unknown environment "%s". Need MATLAB(R) or Octave.', getEnvironment); | |
| 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 %#ok | |
| treeish = ''; | |
| end | |
| if ~isempty(treeish) | |
| formatted = sprintf('(commit %s)',treeish); | |
| end | |
| end | |
| % ============================================================================== |