7168 lines
288 KiB
Matlab
7168 lines
288 KiB
Matlab
function matlab2tikz(varargin)
|
|
%MATLAB2TIKZ Save figure in native LaTeX (TikZ/Pgfplots).
|
|
% MATLAB2TIKZ() saves the current figure as LaTeX file.
|
|
% MATLAB2TIKZ comes with several options that can be combined at will. For
|
|
% example:
|
|
%
|
|
% MATLAB2TIKZ('showInfo', false, 'strict', true, "figure.tex")
|
|
%
|
|
% MATLAB2TIKZ(FILENAME,...) or MATLAB2TIKZ('filename',FILENAME,...)
|
|
% stores the LaTeX code in FILENAME.
|
|
%
|
|
% MATLAB2TIKZ('filehandle',FILEHANDLE,...) stores the LaTeX code in the file
|
|
% referenced by FILEHANDLE. (default: [])
|
|
%
|
|
% MATLAB2TIKZ('figurehandle',FIGUREHANDLE,...) explicitly specifies the
|
|
% handle of the figure that is to be stored. (default: gcf)
|
|
%
|
|
% MATLAB2TIKZ('colormap',DOUBLE,...) explicitly specifies the colormap to be
|
|
% used. (default: current color map)
|
|
%
|
|
% MATLAB2TIKZ('strict',BOOL,...) tells MATLAB2TIKZ to adhere to MATLAB(R)
|
|
% conventions wherever there is room for relaxation. (default: false)
|
|
%
|
|
% MATLAB2TIKZ('strictFontSize',BOOL,...) retains the exact font sizes
|
|
% specified in MATLAB for the TikZ code. This goes against normal LaTeX
|
|
% practice. (default: false)
|
|
%
|
|
% MATLAB2TIKZ('showInfo',BOOL,...) turns informational output on or off.
|
|
% (default: true)
|
|
%
|
|
% MATLAB2TIKZ('showWarnings',BOOL,...) turns warnings on or off.
|
|
% (default: true)
|
|
%
|
|
% MATLAB2TIKZ('imagesAsPng',BOOL,...) stores MATLAB(R) images as (lossless)
|
|
% PNG files. This is more efficient than storing the image color data as TikZ
|
|
% matrix. (default: true)
|
|
%
|
|
% MATLAB2TIKZ('externalData',BOOL,...) stores all data points in external
|
|
% files as tab separated values (TSV files). (default: false)
|
|
%
|
|
% MATLAB2TIKZ('dataPath',CHAR, ...) defines where external data files
|
|
% and/or PNG figures are saved. It can be either an absolute or a relative
|
|
% path with respect to your MATLAB work directory. By default, data files are
|
|
% placed in the same directory as the TikZ output file. To place data files
|
|
% in your MATLAB work directory, you can use '.'. (default: [])
|
|
%
|
|
% MATLAB2TIKZ('relativeDataPath',CHAR, ...) tells MATLAB2TIKZ to use the
|
|
% given path to follow the external data files and PNG files. This is the
|
|
% relative path from your main LaTeX file to the data file directory.
|
|
% By default the same directory is used as the output (default: [])
|
|
%
|
|
% MATLAB2TIKZ('height',CHAR,...) sets the height of the image. This can be
|
|
% any LaTeX-compatible length, e.g., '3in' or '5cm' or '0.5\textwidth'. If
|
|
% unspecified, MATLAB2TIKZ tries to make a reasonable guess.
|
|
%
|
|
% MATLAB2TIKZ('width',CHAR,...) sets the width of the image.
|
|
% If unspecified, MATLAB2TIKZ tries to make a reasonable guess.
|
|
%
|
|
% MATLAB2TIKZ('noSize',BOOL,...) determines whether 'width', 'height', and
|
|
% 'scale only axis' are specified in the generated TikZ output. For compatibility with the
|
|
% tikzscale package set this to true. (default: false)
|
|
%
|
|
% MATLAB2TIKZ('extraCode',CHAR or CELLCHAR,...) explicitly adds extra code
|
|
% at the beginning of the output file. (default: [])
|
|
%
|
|
% MATLAB2TIKZ('extraCodeAtEnd',CHAR or CELLCHAR,...) explicitly adds extra
|
|
% code at the end of the output file. (default: [])
|
|
%
|
|
% MATLAB2TIKZ('extraAxisOptions',CHAR or CELLCHAR,...) explicitly adds extra
|
|
% options to the Pgfplots axis environment. (default: [])
|
|
%
|
|
% MATLAB2TIKZ('extraColors', {{'name',[R G B]}, ...} , ...) adds
|
|
% user-defined named RGB-color definitions to the TikZ output.
|
|
% R, G and B are expected between 0 and 1. (default: {})
|
|
%
|
|
% MATLAB2TIKZ('extraTikzpictureOptions',CHAR or CELLCHAR,...)
|
|
% explicitly adds extra options to the tikzpicture environment. (default: [])
|
|
%
|
|
% MATLAB2TIKZ('encoding',CHAR,...) sets the encoding of the output file.
|
|
%
|
|
% MATLAB2TIKZ('floatFormat',CHAR,...) sets the format used for float values.
|
|
% You can use this to decrease the file size. (default: '%.15g')
|
|
%
|
|
% MATLAB2TIKZ('maxChunkLength',INT,...) sets maximum number of data points
|
|
% per \addplot for line plots (default: 4000)
|
|
%
|
|
% MATLAB2TIKZ('parseStrings',BOOL,...) determines whether title, axes labels
|
|
% and the like are parsed into LaTeX by MATLAB2TIKZ's parser.
|
|
% If you want greater flexibility, set this to false and use straight LaTeX
|
|
% for your labels. (default: true)
|
|
%
|
|
% MATLAB2TIKZ('parseStringsAsMath',BOOL,...) determines whether to use TeX's
|
|
% math mode for more characters (e.g. operators and figures). (default: false)
|
|
%
|
|
% MATLAB2TIKZ('showHiddenStrings',BOOL,...) determines whether to show
|
|
% strings whose were deliberately hidden. This is usually unnecessary, but
|
|
% can come in handy for unusual plot types (e.g., polar plots). (default:
|
|
% false)
|
|
%
|
|
% MATLAB2TIKZ('interpretTickLabelsAsTex',BOOL,...) determines whether to
|
|
% interpret tick labels as TeX. MATLAB(R) doesn't allow to do that in R2014a
|
|
% or before. In R2014b and later, please set the "TickLabelInterpreter"
|
|
% property of the relevant axis to get the same effect. (default: false)
|
|
%
|
|
% MATLAB2TIKZ('arrowHeadSize', FLOAT, ...) allows to resize the arrow heads
|
|
% in quiver plots by rescaling the arrow heads by a positive scalar. (default: 10)
|
|
%
|
|
% MATLAB2TIKZ('tikzFileComment',CHAR,...) adds a custom comment to the header
|
|
% of the output file. (default: '')
|
|
%
|
|
% MATLAB2TIKZ('addLabels',BOOL,...) add labels to plots: using Tag property
|
|
% or automatic names (where applicable) which make it possible to refer to
|
|
% them using \ref{...} (e.g., in the caption of a figure). (default: false)
|
|
%
|
|
% MATLAB2TIKZ('standalone',BOOL,...) determines whether to produce
|
|
% a standalone compilable LaTeX file. Setting this to true may be useful for
|
|
% taking a peek at what the figure will look like. (default: false)
|
|
%
|
|
% MATLAB2TIKZ('checkForUpdates',BOOL,...) determines whether to automatically
|
|
% check for updates of matlab2tikz. (default: true (if not using git))
|
|
%
|
|
% MATLAB2TIKZ('semanticLineWidths',CELLMATRIX,...) allows you to customize
|
|
% the mapping of semantic "line width" values.
|
|
% A valid entry is an Nx2 cell array:
|
|
% - the first column contains the semantic names,
|
|
% - the second column contains the corresponding line widths in points.
|
|
% The entries you provide are used in addition to the pgf defaults:
|
|
% {'ultra thin', 0.1; 'very thin' , 0.2; 'thin', 0.4; 'semithick', 0.6;
|
|
% 'thick' , 0.8; 'very thick', 1.2; 'ultra thick', 1.6}
|
|
% or a single "NaN" can be provided to turn off this feature alltogether.
|
|
% If you specify the default names, their mapping will be overwritten.
|
|
% Inside your LaTeX document, you are responsible to make sure these TikZ
|
|
% styles are properly defined.
|
|
% (Default: NaN)
|
|
%
|
|
% Example
|
|
% x = -pi:pi/10:pi;
|
|
% y = tan(sin(x)) - sin(tan(x));
|
|
% plot(x,y,'--rs');
|
|
% matlab2tikz('myfile.tex');
|
|
%
|
|
% See also: cleanfigure
|
|
|
|
%% Check if we are in MATLAB or Octave.
|
|
minimalVersion = struct('MATLAB', struct('name','2014a', 'num',[8 3]), ...
|
|
'Octave', struct('name','3.8', 'num',[3 8]));
|
|
checkDeprecatedEnvironment(minimalVersion);
|
|
|
|
m2t.args = []; % For command line arguments
|
|
m2t.current = []; % For currently active objects
|
|
m2t.transform = []; % For hgtransform groups
|
|
m2t.pgfplotsVersion = [1,3];
|
|
m2t.about.name = 'matlab2tikz';
|
|
m2t.about.version = '1.1.0';
|
|
m2t.about.years = '2008--2016';
|
|
m2t.about.website = 'http://www.mathworks.com/matlabcentral/fileexchange/22022-matlab2tikz-matlab2tikz';
|
|
m2t.about.github = 'https://github.com/matlab2tikz/matlab2tikz';
|
|
m2t.about.wiki = [m2t.about.github '/wiki'];
|
|
m2t.about.issues = [m2t.about.github '/issues'];
|
|
m2t.about.develop = [m2t.about.github '/tree/develop'];
|
|
VCID = VersionControlIdentifier();
|
|
m2t.about.versionFull = strtrim(sprintf('v%s %s', m2t.about.version, VCID));
|
|
|
|
m2t.tol = 1.0e-15; % numerical tolerance (e.g. used to test equality of doubles)
|
|
|
|
% the actual contents of the TikZ file go here
|
|
m2t.content = struct('name', '', ...
|
|
'comment', [], ...
|
|
'options', {opts_new()}, ...
|
|
'content', {cell(0)}, ...
|
|
'children', {cell(0)});
|
|
m2t.preamble = sprintf(['\\usepackage[T1]{fontenc}\n', ...
|
|
'\\usepackage[utf8]{inputenc}\n', ...
|
|
'\\usepackage{pgfplots}\n', ...
|
|
'\\usepackage{grffile}\n', ...
|
|
'\\pgfplotsset{compat=newest}\n', ...
|
|
'\\usetikzlibrary{plotmarks}\n', ...
|
|
'\\usetikzlibrary{arrows.meta}\n', ...
|
|
'\\usepgfplotslibrary{patchplots}\n', ...
|
|
'\\usepackage{amsmath}\n']);
|
|
|
|
%% scan the options
|
|
ipp = m2tInputParser;
|
|
|
|
ipp = ipp.addOptional(ipp, 'filename', '', @(x) filenameValidation(x,ipp));
|
|
ipp = ipp.addOptional(ipp, 'filehandle', [], @filehandleValidation);
|
|
|
|
ipp = ipp.addParamValue(ipp, 'figurehandle', get(0,'CurrentFigure'), @ishandle);
|
|
ipp = ipp.addParamValue(ipp, 'colormap', [], @isnumeric);
|
|
ipp = ipp.addParamValue(ipp, 'strict', false, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'strictFontSize', false, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'showInfo', true, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'showWarnings', true, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'checkForUpdates', isempty(VCID), @islogical);
|
|
|
|
ipp = ipp.addParamValue(ipp, 'semanticLineWidths', NaN, @isValidSemanticLineWidthDefinition);
|
|
|
|
ipp = ipp.addParamValue(ipp, 'encoding' , '', @ischar);
|
|
ipp = ipp.addParamValue(ipp, 'standalone', false, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'tikzFileComment', '', @ischar);
|
|
ipp = ipp.addParamValue(ipp, 'extraColors', {}, @isColorDefinitions);
|
|
ipp = ipp.addParamValue(ipp, 'extraCode', {}, @isCellOrChar);
|
|
ipp = ipp.addParamValue(ipp, 'extraCodeAtEnd', {}, @isCellOrChar);
|
|
ipp = ipp.addParamValue(ipp, 'extraAxisOptions', {}, @isCellOrChar);
|
|
ipp = ipp.addParamValue(ipp, 'extraTikzpictureOptions', {}, @isCellOrChar);
|
|
ipp = ipp.addParamValue(ipp, 'floatFormat', '%.15g', @ischar);
|
|
ipp = ipp.addParamValue(ipp, 'automaticLabels', false, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'addLabels', false, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'showHiddenStrings', false, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'height', '', @ischar);
|
|
ipp = ipp.addParamValue(ipp, 'width' , '', @ischar);
|
|
ipp = ipp.addParamValue(ipp, 'imagesAsPng', true, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'externalData', false, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'dataPath', '', @ischar);
|
|
ipp = ipp.addParamValue(ipp, 'relativeDataPath', '', @ischar);
|
|
ipp = ipp.addParamValue(ipp, 'noSize', false, @islogical);
|
|
ipp = ipp.addParamValue(ipp, 'arrowHeadSize', 10, @(x) x>0);
|
|
|
|
% Maximum chunk length.
|
|
% TeX parses files line by line with a buffer of size buf_size. If the
|
|
% plot has too many data points, pdfTeX's buffer size may be exceeded.
|
|
% As a work-around, the plot is split into several smaller chunks.
|
|
%
|
|
% What is a "large" array?
|
|
% TeX parser buffer is buf_size=200 000 char on Mac TeXLive, let's say
|
|
% 100 000 to be on the safe side.
|
|
% 1 point is represented by 25 characters (estimation): 2 coordinates (10
|
|
% char), 2 brackets, comma and white space, + 1 extra char.
|
|
% That gives a magic arbitrary number of 4000 data points per array.
|
|
ipp = ipp.addParamValue(ipp, 'maxChunkLength', 4000, @isnumeric);
|
|
|
|
% By default strings like axis labels are parsed to match the appearance of
|
|
% strings as closely as possible to that generated by MATLAB.
|
|
% If the user wants to have particular strings in the matlab2tikz output that
|
|
% can't be generated in MATLAB, they can disable string parsing. In that case
|
|
% all strings are piped literally to the LaTeX output.
|
|
ipp = ipp.addParamValue(ipp, 'parseStrings', true, @islogical);
|
|
|
|
% In addition to regular string parsing, an additional stage can be enabled
|
|
% which uses TeX's math mode for more characters like figures and operators.
|
|
ipp = ipp.addParamValue(ipp, 'parseStringsAsMath', false, @islogical);
|
|
|
|
% As opposed to titles, axis labels and such, MATLAB(R) does not interpret tick
|
|
% labels as TeX. matlab2tikz retains this behavior, but if it is desired to
|
|
% interpret the tick labels as TeX, set this option to true.
|
|
ipp = ipp.addParamValue(ipp, 'interpretTickLabelsAsTex', false, @islogical);
|
|
|
|
%% deprecated parameters (will auto-generate warnings upon parse)
|
|
ipp = ipp.addParamValue(ipp, 'relativePngPath', '', @ischar);
|
|
ipp = ipp.deprecateParam(ipp, 'relativePngPath', 'relativeDataPath');
|
|
ipp = ipp.deprecateParam(ipp, 'automaticLabels', 'addLabels');
|
|
|
|
%% Finally parse all the arguments
|
|
ipp = ipp.parse(ipp, varargin{:});
|
|
m2t.args = ipp.Results; % store the input arguments back into the m2t data struct
|
|
|
|
%% Inform users of potentially dangerous options
|
|
warnAboutParameter(m2t, 'parseStringsAsMath', @(opt)(opt==true), ...
|
|
['This may produce undesirable string output. For full control over output\n', ...
|
|
'strings please set the parameter "parseStrings" to false.']);
|
|
warnAboutParameter(m2t, 'noSize', @(opt)(opt==true), ...
|
|
'This may impede both axes sizing and placement!');
|
|
warnAboutParameter(m2t, 'imagesAsPng', @(opt)(opt==false), ...
|
|
['It is highly recommended to use PNG data to store images.\n', ...
|
|
'Make sure to set "imagesAsPng" to true.']);
|
|
|
|
%% Do some global initialization
|
|
m2t.color = configureColors(m2t.args.extraColors);
|
|
m2t.semantic.LineWidth = configureSemanticLineWidths(m2t.args.semanticLineWidths);
|
|
|
|
% define global counter variables
|
|
m2t.count.pngFile = 0; % number of PNG files
|
|
m2t.count.tsvFile = 0; % number of TSV files
|
|
m2t.count.autolabel = 0; % number of automatic labels
|
|
m2t.count.plotyylabel = 0; % number of plotyy labels
|
|
|
|
%% shortcut
|
|
m2t.ff = m2t.args.floatFormat;
|
|
|
|
%% add global elements
|
|
if isempty(m2t.args.figurehandle)
|
|
error('matlab2tikz:figureNotFound','MATLAB figure not found.');
|
|
end
|
|
m2t.current.gcf = m2t.args.figurehandle;
|
|
if m2t.args.colormap
|
|
m2t.current.colormap = m2t.args.colormap;
|
|
else
|
|
m2t.current.colormap = get(m2t.current.gcf, 'colormap');
|
|
end
|
|
|
|
%% handle output file handle/file name
|
|
[m2t, fid, fileWasOpen] = openFileForOutput(m2t);
|
|
|
|
% By default, reference the PNG (if required) from the TikZ file
|
|
% as the file path of the TikZ file itself. This works if the MATLAB script
|
|
% is executed in the same folder where the TeX file sits.
|
|
if isempty(m2t.args.relativeDataPath)
|
|
if ~isempty(m2t.args.relativePngPath)
|
|
%NOTE: eventually break backwards compatibility of relative PNG path
|
|
m2t.relativeDataPath = m2t.args.relativePngPath;
|
|
userWarning(m2t, ['Using "relativePngPath" for "relativeDataPath".', ...
|
|
' This will stop working in a future release.']);
|
|
else
|
|
m2t.relativeDataPath = m2t.args.relativeDataPath;
|
|
end
|
|
else
|
|
m2t.relativeDataPath = m2t.args.relativeDataPath;
|
|
end
|
|
if isempty(m2t.args.dataPath)
|
|
m2t.dataPath = fileparts(m2t.tikzFileName);
|
|
else
|
|
m2t.dataPath = m2t.args.dataPath;
|
|
end
|
|
|
|
%% print some version info to the screen
|
|
userInfo(m2t, ['(To disable info messages, pass [''showInfo'', false] to matlab2tikz.)\n', ...
|
|
'(For all other options, type ''help matlab2tikz''.)\n']);
|
|
|
|
userInfo(m2t, '\nThis is %s %s.\n', m2t.about.name, m2t.about.versionFull)
|
|
|
|
% In Octave, put a new line and some spaces in between the URLs for clarity.
|
|
% In MATLAB this is not necessary, since the URLs get (shorter) descriptions.
|
|
sep = switchMatOct('', sprintf('\n '));
|
|
versionInfo = ['The latest developments can be retrieved from %s.\n', ...
|
|
'You can find more documentation on %s and %s.\n', ...
|
|
'If you encounter bugs or want a new feature, go to %s.\n', ...
|
|
'Please visit %s to rate %s or download the stable release.\n'];
|
|
userInfo(m2t, versionInfo, ...
|
|
clickableUrl(m2t.about.develop, 'our development branch'), ...
|
|
[sep clickableUrl(m2t.about.github, 'our GitHub page') sep], ...
|
|
[sep clickableUrl(m2t.about.wiki, 'wiki')], ...
|
|
[sep clickableUrl(m2t.about.issues, 'our issue tracker')],...
|
|
[clickableUrl(m2t.about.website, 'FileExchange') sep],...
|
|
m2t.about.name);
|
|
|
|
%% Save the figure as TikZ to file
|
|
m2t = saveToFile(m2t, fid, fileWasOpen);
|
|
|
|
%% Check for a new matlab2tikz version outside version control
|
|
if m2t.args.checkForUpdates
|
|
m2tUpdater(m2t.about, m2t.args.showInfo);
|
|
end
|
|
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, counterValue] = incrementGlobalCounter(m2t, counterName)
|
|
% Increments a global counter value and returns its value
|
|
m2t.count.(counterName) = m2t.count.(counterName) + 1;
|
|
counterValue = m2t.count.(counterName);
|
|
end
|
|
% ==============================================================================
|
|
function colorConfig = configureColors(extraColors)
|
|
% Sets the global color options for matlab2tikz
|
|
colorConfig = struct();
|
|
|
|
% Set the color resolution.
|
|
colorConfig.depth = 48; %[bit] RGB color depth (typical values: 24, 30, 48)
|
|
colorConfig.precision = 2^(-colorConfig.depth/3);
|
|
colorConfig.format = sprintf('%%0.%df',ceil(-log10(colorConfig.precision)));
|
|
|
|
% The following color RGB-values which will need to be defined:
|
|
%
|
|
% - 'extraNames' contains their designated names,
|
|
% - 'extraSpecs' their RGB specifications.
|
|
[colorConfig.extraNames, colorConfig.extraSpecs] = ...
|
|
dealColorDefinitions(extraColors);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, fid, fileWasOpen] = openFileForOutput(m2t)
|
|
% opens the output file and/or show a dialog to select one
|
|
if ~isempty(m2t.args.filehandle)
|
|
fid = m2t.args.filehandle;
|
|
fileWasOpen = true;
|
|
if ~isempty(m2t.args.filename)
|
|
userWarning(m2t, ...
|
|
'File handle AND file name for output given. File handle used, file name discarded.')
|
|
end
|
|
m2t.tikzFileName = fopen(fid);
|
|
else
|
|
fid = [];
|
|
fileWasOpen = false;
|
|
% set filename
|
|
if ~isempty(m2t.args.filename)
|
|
filename = m2t.args.filename;
|
|
else
|
|
[filename, pathname] = uiputfile({'*.tex;*.tikz'; '*.*'}, 'Save File');
|
|
filename = fullfile(pathname, filename);
|
|
end
|
|
m2t.tikzFileName = filename;
|
|
end
|
|
|
|
end
|
|
% ==============================================================================
|
|
function l = filenameValidation(x, p)
|
|
% is the filename argument NOT another keyword?
|
|
l = ischar(x) && ~any(strcmp(x,p.Parameters)); %FIXME: See #471
|
|
end
|
|
% ==============================================================================
|
|
function l = filehandleValidation(x)
|
|
% is the filehandle the handle to an opened file?
|
|
l = isnumeric(x) && any(x==fopen('all'));
|
|
end
|
|
% ==============================================================================
|
|
function bool = isCellOrChar(x)
|
|
bool = iscell(x) || ischar(x);
|
|
end
|
|
% ==============================================================================
|
|
function bool = isRGBTuple(color)
|
|
% Returns true when the color is a valid RGB tuple
|
|
bool = numel(color) == 3 && ...
|
|
all(isreal(color)) && ...
|
|
all( 0<=color & color<=1 ); % this also disallows NaN entries
|
|
end
|
|
% ==============================================================================
|
|
function bool = isColorDefinitions(colors)
|
|
% Returns true when the input is a cell array of color definitions, i.e.
|
|
% a cell array with in each cell a cell of the form {'name', [R G B]}
|
|
isValidEntry = @(e)( iscell(e) && ischar(e{1}) && isRGBTuple(e{2}) );
|
|
|
|
bool = iscell(colors) && all(cellfun(isValidEntry, colors));
|
|
end
|
|
% ==============================================================================
|
|
function bool = isValidSemanticLineWidthDefinition(defMat)
|
|
% Returns true when the input is a cell array of shape Nx2 and
|
|
% contents in each column a set of string and numerical value as needed
|
|
% for the semanticLineWidth option.
|
|
bool = iscell(defMat) && size(defMat, 2) == 2; % Nx2 cell array
|
|
bool = bool && all(cellfun(@ischar , defMat(:,1))); % first column: names
|
|
bool = bool && all(cellfun(@isnumeric, defMat(:,2))); % second column: line width in points
|
|
|
|
% alternatively: just 1 NaN to remove the defaults
|
|
bool = bool || (numel(defMat)==1 && isnan(defMat));
|
|
end
|
|
% ==============================================================================
|
|
function fid = fileOpenForWrite(m2t, filename)
|
|
% Set the encoding of the output file.
|
|
% Currently only MATLAB supports different encodings.
|
|
fid = -1;
|
|
|
|
[filepath] = fileparts(filename);
|
|
if ~exist(filepath,'dir') && ~isempty(filepath)
|
|
mkdir(filepath);
|
|
end
|
|
|
|
switch getEnvironment()
|
|
case 'MATLAB'
|
|
fid = fopen(filename, 'w', ...
|
|
'native', m2t.args.encoding);
|
|
case 'Octave'
|
|
fid = fopen(filename, 'w');
|
|
otherwise
|
|
errorUnknownEnvironment();
|
|
end
|
|
|
|
if fid == -1
|
|
error('matlab2tikz:fileOpenError', ...
|
|
'Unable to open file ''%s'' for writing.', filename);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function path = TeXpath(path)
|
|
path = strrep(path, filesep, '/');
|
|
% TeX uses '/' as a file separator (as UNIX). Windows, however, uses
|
|
% '\' which is not supported by TeX as a file separator
|
|
end
|
|
% ==============================================================================
|
|
function m2t = saveToFile(m2t, fid, fileWasOpen)
|
|
% Save the figure as TikZ to a file. All other routines are called from here.
|
|
|
|
% get all axes handles
|
|
[m2t, axesHandles] = findPlotAxes(m2t, m2t.current.gcf);
|
|
|
|
% Turn around the handles vector to make sure that plots that appeared
|
|
% first also appear first in the vector. This makes sure the z-order of
|
|
% superimposed axes is respected and is fundamental for plotyy.
|
|
axesHandles = axesHandles(end:-1:1);
|
|
|
|
% Alternative Positioning of axes.
|
|
% Select relevant Axes and draw them.
|
|
[m2t, axesBoundingBox] = getRelevantAxes(m2t, axesHandles);
|
|
|
|
m2t.axesBoundingBox = axesBoundingBox;
|
|
m2t.axes = {};
|
|
for relevantAxesHandle = m2t.relevantAxesHandles(:)'
|
|
m2t = drawAxes(m2t, relevantAxesHandle);
|
|
end
|
|
|
|
% Handle color bars.
|
|
for cbar = m2t.cbarHandles(:)'
|
|
m2t = handleColorbar(m2t, cbar);
|
|
end
|
|
|
|
% Draw annotations
|
|
m2t = drawAnnotations(m2t);
|
|
|
|
% Add all axes containers to the file contents.
|
|
for axesContainer = m2t.axes
|
|
m2t.content = addChildren(m2t.content, axesContainer);
|
|
end
|
|
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% actually print the stuff
|
|
minimalPgfplotsVersion = formatPgfplotsVersion(m2t.pgfplotsVersion);
|
|
|
|
m2t.content.comment = sprintf('This file was created by %s.\n', m2t.about.name);
|
|
|
|
if m2t.args.showInfo
|
|
% disable this info if showInfo=false
|
|
m2t.content.comment = [m2t.content.comment, ...
|
|
sprintf(['\n',...
|
|
'The latest updates can be retrieved from\n', ...
|
|
' %s\n', ...
|
|
'where you can also make suggestions and rate %s.\n'], ...
|
|
m2t.about.website, m2t.about.name ) ...
|
|
];
|
|
end
|
|
|
|
userInfo(m2t, 'You will need pgfplots version %s or newer to compile the TikZ output.',...
|
|
minimalPgfplotsVersion);
|
|
|
|
% Add custom comment.
|
|
if ~isempty(m2t.args.tikzFileComment)
|
|
m2t.content.comment = [m2t.content.comment, ...
|
|
sprintf('\n%s\n', m2t.args.tikzFileComment)
|
|
];
|
|
end
|
|
|
|
m2t.content.name = 'tikzpicture';
|
|
|
|
% Add custom TikZ options if any given.
|
|
m2t.content.options = opts_append_userdefined(m2t.content.options, ...
|
|
m2t.args.extraTikzpictureOptions);
|
|
|
|
m2t.content.colors = generateColorDefinitions(m2t.color);
|
|
|
|
% Open file if was not open
|
|
if ~fileWasOpen
|
|
fid = fileOpenForWrite(m2t, m2t.tikzFileName);
|
|
finally_fclose_fid = onCleanup(@() fclose(fid));
|
|
end
|
|
|
|
% Finally print it to the file
|
|
fprintf(fid, '%s', texcomment(m2t.content.comment));
|
|
addStandalone(m2t, fid, 'preamble');
|
|
addCustomCode(fid, '', m2t.args.extraCode, '');
|
|
addStandalone(m2t, fid, 'begin');
|
|
|
|
printAll(m2t, m2t.content, fid); % actual plotting happens here
|
|
|
|
addCustomCode(fid, '\n', m2t.args.extraCodeAtEnd, '');
|
|
|
|
addStandalone(m2t, fid, 'end');
|
|
end
|
|
% ==============================================================================
|
|
function addStandalone(m2t, fid, part)
|
|
% writes a part of a standalone LaTeX file definition
|
|
if m2t.args.standalone
|
|
switch part
|
|
case 'preamble'
|
|
fprintf(fid, '\\documentclass[tikz]{standalone}\n%s\n', m2t.preamble);
|
|
case 'begin'
|
|
fprintf(fid, '\\begin{document}\n');
|
|
case 'end'
|
|
fprintf(fid, '\n\\end{document}');
|
|
otherwise
|
|
error('m2t:unknownStandalonePart', ...
|
|
'Unknown standalone part "%s"', part);
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function str = generateColorDefinitions(colorConfig)
|
|
% Output the color definitions to LaTeX
|
|
str = '';
|
|
names = colorConfig.extraNames;
|
|
specs = colorConfig.extraSpecs;
|
|
ff = colorConfig.format;
|
|
|
|
if ~isempty(names)
|
|
colorDef = cell(1, length(names));
|
|
for k = 1:length(names)
|
|
% Append with '%' to avoid spacing woes in LaTeX
|
|
FORMAT = ['\\definecolor{%s}{rgb}{' ff ',' ff ',' ff '}%%\n'];
|
|
colorDef{k} = sprintf(FORMAT, names{k}, specs{k});
|
|
end
|
|
str = m2tstrjoin([colorDef, sprintf('%%\n')], '');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, axesHandles] = findPlotAxes(m2t, fh)
|
|
% find axes handles that are not legends/colorbars
|
|
% store detected legends and colorbars in 'm2t'
|
|
% fh figure handle
|
|
axesHandles = findall(fh, 'type', 'axes');
|
|
|
|
% Remove all legend handles, as they are treated separately.
|
|
if ~isempty(axesHandles)
|
|
% TODO fix for octave
|
|
tagKeyword = switchMatOct('Tag', 'tag');
|
|
% Find all legend handles. This is MATLAB-only.
|
|
m2t.legendHandles = findall(fh, tagKeyword, 'legend');
|
|
m2t.legendHandles = m2t.legendHandles(:)';
|
|
idx = ~ismember(axesHandles, m2t.legendHandles);
|
|
axesHandles = axesHandles(idx);
|
|
end
|
|
|
|
% Remove all colorbar handles, as they are treated separately.
|
|
if ~isempty(axesHandles)
|
|
colorbarKeyword = switchMatOct('Colorbar', 'colorbar');
|
|
% Find all colorbar handles. This is MATLAB-only.
|
|
cbarHandles = findall(fh, tagKeyword, colorbarKeyword);
|
|
% Octave also finds text handles here; no idea why. Filter.
|
|
m2t.cbarHandles = [];
|
|
for h = cbarHandles(:)'
|
|
if any(strcmpi(get(h, 'Type'),{'axes','colorbar'}))
|
|
m2t.cbarHandles = [m2t.cbarHandles, h];
|
|
end
|
|
end
|
|
m2t.cbarHandles = m2t.cbarHandles(:)';
|
|
idx = ~ismember(axesHandles, m2t.cbarHandles);
|
|
axesHandles = axesHandles(idx);
|
|
else
|
|
m2t.cbarHandles = [];
|
|
end
|
|
|
|
% Remove scribe layer holding annotations (MATLAB < R2014b)
|
|
m2t.scribeLayer = findall(axesHandles, 'Tag','scribeOverlay');
|
|
idx = ~ismember(axesHandles, m2t.scribeLayer);
|
|
axesHandles = axesHandles(idx);
|
|
end
|
|
% ==============================================================================
|
|
function str = texcomment(str)
|
|
% format a (multiline) string as TeX comment
|
|
if ~isempty(str)
|
|
EOL = sprintf('\n');
|
|
str = ['% ' strrep(str, EOL, [EOL '%']) EOL];
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function addCustomCode(fid, before, code, after)
|
|
if ~isempty(code)
|
|
fprintf(fid, before);
|
|
if ischar(code)
|
|
code = {code};
|
|
end
|
|
if iscellstr(code)
|
|
for str = code(:)'
|
|
fprintf(fid, '%s\n', str{1});
|
|
end
|
|
else
|
|
error('matlab2tikz:saveToFile', 'Need str or cellstr.');
|
|
end
|
|
fprintf(fid,after);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, pgfEnvironments] = handleAllChildren(m2t, h)
|
|
% Draw all children of a graphics object (if they need to be drawn).
|
|
% #COMPLEX: mainly a switch-case
|
|
str = '';
|
|
children = allchild(h);
|
|
|
|
% prepare cell array of pgfEnvironments
|
|
pgfEnvironments = cell(1, numel(children));
|
|
envCounter = 1;
|
|
|
|
% It's important that we go from back to front here, as this is
|
|
% how MATLAB does it, too. Significant for patch (contour) plots,
|
|
% and the order of plotting the colored patches.
|
|
for child = children(end:-1:1)'
|
|
|
|
% Check if object has legend. Some composite objects need to determine
|
|
% their status at the root level. For detailed explanations check
|
|
% getLegendEntries().
|
|
% TODO: could move this check into drawHggroup. Need to verify how
|
|
% hgtransform behaves though. (priority - LOW)
|
|
m2t = hasLegendEntry(m2t,child);
|
|
|
|
switch char(get(child, 'Type'))
|
|
% 'axes' environments are treated separately.
|
|
|
|
case 'line'
|
|
[m2t, str] = handleObject(m2t, child, @drawLine);
|
|
|
|
case 'constantline'
|
|
[m2t, str] = handleObject(m2t, child, @drawConstantLine);
|
|
|
|
case 'patch'
|
|
[m2t, str] = handleObject(m2t, child, @drawPatch);
|
|
|
|
case 'image'
|
|
[m2t, str] = handleObject(m2t, child, @drawImage);
|
|
|
|
case {'hggroup', 'matlab.graphics.primitive.Group', ...
|
|
'scatter', 'bar', 'stair', 'stem' ,'errorbar', 'area', ...
|
|
'quiver','contour'}
|
|
[m2t, str] = handleObject(m2t, child, @drawHggroup);
|
|
|
|
case 'hgtransform'
|
|
% From http://www.mathworks.de/de/help/matlab/ref/hgtransformproperties.html:
|
|
% Matrix: 4-by-4 matrix
|
|
% Transformation matrix applied to hgtransform object and its
|
|
% children. The hgtransform object applies the transformation
|
|
% matrix to all its children.
|
|
% More information at http://www.mathworks.de/de/help/matlab/creating_plots/group-objects.html.
|
|
m2t.transform = get(child, 'Matrix');
|
|
[m2t, str] = handleAllChildren(m2t, child);
|
|
m2t.transform = [];
|
|
|
|
case 'surface'
|
|
[m2t, str] = handleObject(m2t, child, @drawSurface);
|
|
|
|
case 'text'
|
|
[m2t, str] = handleObject(m2t, child, @drawVisibleText);
|
|
|
|
case 'rectangle'
|
|
[m2t, str] = handleObject(m2t, child, @drawRectangle);
|
|
|
|
case 'doubleendarrowshape'
|
|
[m2t, str] = handleObject(m2t, child, @drawArrow);
|
|
|
|
case 'arrowshape'
|
|
[m2t, str] = handleObject(m2t, child, @drawArrow);
|
|
|
|
case 'histogram'
|
|
[m2t, str] = handleObject(m2t, child, @drawHistogram);
|
|
|
|
case guitypes()
|
|
% don't do anything for GUI objects and their children
|
|
[m2t, str] = handleObject(m2t, child, @drawNothing);
|
|
|
|
case 'light'
|
|
% These objects are not supported and should not/cannot be
|
|
% supported by matlab2tikz or pgfplots.
|
|
[m2t, str] = handleObject(m2t, child, @drawNothing);
|
|
|
|
case ''
|
|
% No children found for handle. (It has only a title and/or
|
|
% labels). Carrying on as if nothing happened
|
|
|
|
otherwise
|
|
userWarning(m2t, 'matlab2tikz does not know how to handle object with type "%s"', get(child,'Type'));
|
|
% just try with the drawNothing handler (which does nothing)
|
|
% that at least allows a user to attach a custom handler from outside
|
|
[m2t, str] = handleObject(m2t, child, @drawNothing);
|
|
end
|
|
|
|
% A composite object might nest handleAllChildren calls that can
|
|
% modify the m2t.currentHandleHasLegend value. Re-instate the
|
|
% legend status. For detailed explanations check getLegendEntries().
|
|
m2t = hasLegendEntry(m2t,child);
|
|
[m2t, legendLabel, labelRef] = addPlotyyReference(m2t, child);
|
|
legendInfo = addLegendInformation(m2t, child);
|
|
% Add labelRef BEFORE next plot to preserve color order
|
|
str = join(m2t, {labelRef, str, legendLabel, legendInfo}, '');
|
|
|
|
% append the environment
|
|
pgfEnvironments{envCounter} = str;
|
|
envCounter = envCounter +1;
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, label, labelRef] = addPlotyyReference(m2t, h)
|
|
% Create labelled references to legend entries of the main plotyy axis
|
|
|
|
% This ensures we are either on the main or secondary axis
|
|
label = '';
|
|
labelRef = '';
|
|
if ~isAxisPlotyy(m2t.current.gca)
|
|
return
|
|
end
|
|
|
|
% Get current label counter
|
|
|
|
if hasPlotyyReference(m2t,h)
|
|
% Label the plot to later reference it. Only legend entries on the main
|
|
% plotyy axis will have a label
|
|
[m2t, labelNum] = incrementGlobalCounter(m2t, 'plotyylabel');
|
|
label = sprintf('\\label{%s}\n\n', plotyyLabelName(labelNum));
|
|
|
|
elseif m2t.currentHandleHasLegend && ~isempty(m2t.axes{end}.PlotyyReferences)
|
|
% We are on the secondary axis.
|
|
|
|
% We have produced a number of labels we can refer to so far.
|
|
% Also, here we have a number of references that are to be recorded.
|
|
% So, we make the last references (assuming the other ones have been
|
|
% realized already)
|
|
nReferences = numel(m2t.axes{end}.PlotyyReferences);
|
|
nLabels = m2t.count.plotyylabel;
|
|
|
|
% This is the range of labels, corresponding to the references
|
|
labelRange = (nLabels-nReferences+1):nLabels;
|
|
|
|
labelRef = cell(1, numel(labelRange));
|
|
% Create labelled references to legend entries of the main axis
|
|
for iRef = 1:nReferences
|
|
ref = m2t.axes{end}.PlotyyReferences(iRef);
|
|
lString = getLegendString(m2t,ref);
|
|
labelRef{iRef} = sprintf('\\addlegendimage{/pgfplots/refstyle=%s}\n\\addlegendentry{%s}\n',...
|
|
plotyyLabelName(labelRange(iRef)), lString);
|
|
end
|
|
labelRef = join(m2t, labelRef, '');
|
|
|
|
% Clear plotyy references. Ensures that references are created only once
|
|
m2t.axes{end}.PlotyyReferences = [];
|
|
else
|
|
% Do nothing: it's gonna be a legend entry.
|
|
% Not a label nor a referenced entry from the main axis.
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function label = plotyyLabelName(num)
|
|
% creates a LaTeX label for a plotyy trace
|
|
label = sprintf('plotyyref:leg%d', num);
|
|
end
|
|
% ==============================================================================
|
|
function legendInfo = addLegendInformation(m2t, h)
|
|
% Add the actual legend string
|
|
|
|
legendInfo = '';
|
|
if ~m2t.currentHandleHasLegend
|
|
return
|
|
end
|
|
legendString = getLegendString(m2t,h);
|
|
|
|
% We also need a legend alignment option to make multiline
|
|
% legend entries work. This is added by default in getLegendOpts().
|
|
legendInfo = sprintf('\\addlegendentry{%s}\n\n', legendString);
|
|
end
|
|
% ==============================================================================
|
|
function data = applyHgTransform(m2t, data)
|
|
if ~isempty(m2t.transform)
|
|
R = m2t.transform(1:3,1:3);
|
|
t = m2t.transform(1:3,4);
|
|
n = size(data, 1);
|
|
data = data * R' + kron(ones(n,1), t');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function m2t = drawAxes(m2t, handle)
|
|
% Input arguments:
|
|
% handle.................The axes environment handle.
|
|
|
|
assertRegularAxes(handle);
|
|
|
|
% Initialize empty environment.
|
|
% Use a struct instead of a custom subclass of hgsetget (which would
|
|
% facilitate writing clean code) as structs are more portable (old MATLAB(R)
|
|
% versions, GNU Octave).
|
|
m2t.axes{end+1} = struct('handle', handle, ...
|
|
'name', '', ...
|
|
'comment', [], ...
|
|
'options', {opts_new()}, ...
|
|
'content', {cell(0)}, ...
|
|
'children', {cell(0)});
|
|
|
|
% update gca
|
|
m2t.current.gca = handle;
|
|
|
|
% Check if axis is 3d
|
|
% In MATLAB, all plots are treated as 3D plots; it's just the view that
|
|
% makes 2D plots appear like 2D.
|
|
m2t.axes{end}.is3D = isAxis3D(handle);
|
|
|
|
% Flag if axis contains barplot
|
|
m2t.axes{end}.barAddedAxisOption = false;
|
|
|
|
% Get legend entries
|
|
m2t.axes{end}.LegendHandle = getAssociatedLegend(m2t, handle);
|
|
m2t.axes{end}.LegendEntries = getLegendEntries(m2t);
|
|
m2t = getPlotyyReferences(m2t, handle);
|
|
|
|
m2t = retrievePositionOfAxes(m2t, handle);
|
|
|
|
m2t = addAspectRatioOptionsOfAxes(m2t, handle);
|
|
|
|
% Axis direction
|
|
for axis = 'xyz'
|
|
m2t.([axis 'AxisReversed']) = ...
|
|
strcmpi(get(handle,[upper(axis),'Dir']), 'reverse');
|
|
end
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% Add color scaling
|
|
CLimMode = get(handle,'CLimMode');
|
|
if strcmpi(CLimMode,'manual') || ~isempty(m2t.cbarHandles)
|
|
clim = caxis(handle);
|
|
m2t = m2t_addAxisOption(m2t, 'point meta min', sprintf(m2t.ff, clim(1)));
|
|
m2t = m2t_addAxisOption(m2t, 'point meta max', sprintf(m2t.ff, clim(2)));
|
|
end
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% Recurse into the children of this environment.
|
|
[m2t, childrenEnvs] = handleAllChildren(m2t, handle);
|
|
m2t.axes{end} = addChildren(m2t.axes{end}, childrenEnvs);
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% The rest of this is handling axes options.
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% Get other axis options (ticks, axis color, label,...).
|
|
% This is set here such that the axis orientation indicator in m2t is set
|
|
% before -- if ~isVisible(handle) -- the handle's children are called.
|
|
[m2t, xopts] = getAxisOptions(m2t, handle, 'x');
|
|
[m2t, yopts] = getAxisOptions(m2t, handle, 'y');
|
|
|
|
m2t.axes{end}.options = opts_merge(m2t.axes{end}.options, xopts, yopts);
|
|
|
|
m2t = add3DOptionsOfAxes(m2t, handle);
|
|
|
|
if ~isVisible(handle)
|
|
% Setting hide{x,y} axis also hides the axis labels in Pgfplots whereas
|
|
% in MATLAB, they may still be visible. Instead use the following.
|
|
m2t = m2t_addAxisOption(m2t, 'axis line style', '{draw=none}');
|
|
m2t = m2t_addAxisOption(m2t, 'ticks', 'none');
|
|
% % An invisible axes container *can* have visible children, so don't
|
|
% % immediately bail out here.
|
|
% children = allchild(handle);
|
|
% for child = children(:)'
|
|
% if isVisible(child)
|
|
% % If the axes contain something that's visible, add an invisible
|
|
% % axes pair.
|
|
% m2t.axes{end}.name = 'axis';
|
|
% m2t.axes{end}.options = {m2t.axes{end}.options{:}, ...
|
|
% 'hide x axis', 'hide y axis'};
|
|
% NOTE: getTag was removed in 76d260d12e615602653d6f7b357393242b2430b3
|
|
% m2t.axes{end}.comment = getTag(handle);
|
|
% break;
|
|
% end
|
|
% end
|
|
% % recurse into the children of this environment
|
|
% [m2t, childrenEnvs] = handleAllChildren(m2t, handle);
|
|
% m2t.axes{end} = addChildren(m2t.axes{end}, childrenEnvs);
|
|
% return
|
|
end
|
|
m2t.axes{end}.name = 'axis';
|
|
|
|
m2t = drawBackgroundOfAxes(m2t, handle);
|
|
m2t = drawTitleOfAxes(m2t, handle);
|
|
m2t = drawBoxAndLineLocationsOfAxes(m2t, handle);
|
|
m2t = drawGridOfAxes(m2t, handle);
|
|
m2t = drawLegendOptionsOfAxes(m2t);
|
|
|
|
m2t.axes{end}.options = opts_append_userdefined(m2t.axes{end}.options, ...
|
|
m2t.args.extraAxisOptions);
|
|
end
|
|
% ==============================================================================
|
|
function m2t = drawGridOfAxes(m2t, handle)
|
|
% Draws the grids of an axis
|
|
options = opts_new();
|
|
|
|
% Check for major/minor grids
|
|
hasGrid = [isOn(get(handle, 'XGrid'));
|
|
isOn(get(handle, 'YGrid'));
|
|
isOn(get(handle, 'ZGrid')) && isAxis3D(handle)];
|
|
|
|
hasMinorGrid = [isOn(get(handle, 'XMinorGrid'));
|
|
isOn(get(handle, 'YMinorGrid'));
|
|
isOn(get(handle, 'ZMinorGrid')) && isAxis3D(handle)];
|
|
|
|
xyz = {'x', 'y', 'z'};
|
|
|
|
% Check for local grid options
|
|
% NOTE: for individual axis color options see the pfgmanual under
|
|
% major x grid style
|
|
for i=1:3
|
|
if hasGrid(i)
|
|
grid = [xyz{i}, 'majorgrids'];
|
|
options = opts_add(options, grid);
|
|
end
|
|
if hasMinorGrid(i)
|
|
grid = [xyz{i}, 'minorgrids'];
|
|
options = opts_add(options, grid);
|
|
end
|
|
end
|
|
|
|
% Check for global grid options
|
|
if any(hasGrid)
|
|
gridOpts = opts_new();
|
|
% Get the line style and translate it to pgfplots
|
|
[gridLS, isDefault] = getAndCheckDefault(...
|
|
'axes', handle, 'GridLineStyle', ':');
|
|
if ~isDefault || m2t.args.strict
|
|
gridOpts = opts_add(gridOpts, translateLineStyle(gridLS));
|
|
end
|
|
|
|
% Get the color of the grid and translate it to pgfplots usable
|
|
% values
|
|
[gridColor, defaultColor] = getAndCheckDefault(...
|
|
'axes', handle, 'GridColor', [0.15, 0.15, 0.15]);
|
|
if ~defaultColor
|
|
[m2t, gridColor] = getColor(m2t, handle, gridColor, 'patch');
|
|
gridOpts = opts_add(gridOpts, gridColor);
|
|
end
|
|
|
|
% Get the alpha of the grid and translate it to pgfplots
|
|
[gridAlpha, defaultAlpha] = getAndCheckDefault(...
|
|
'axes', handle, 'GridAlpha', 0.1);
|
|
if ~defaultAlpha
|
|
gridOpts = opts_add(gridOpts, 'opacity', num2str(gridAlpha));
|
|
end
|
|
|
|
if ~isempty(gridOpts)
|
|
options = opts_addSubOpts(options, 'grid style', gridOpts);
|
|
end
|
|
end
|
|
|
|
if any(hasMinorGrid)
|
|
minorGridOpts = opts_new();
|
|
% Get the line style and translate it to pgfplots
|
|
[minorGridLS, isDefault] = getAndCheckDefault(...
|
|
'axes', handle, 'MinorGridLineStyle', ':');
|
|
if ~isDefault || m2t.args.strict
|
|
minorGridOpts = opts_add(minorGridOpts, translateLineStyle(minorGridLS));
|
|
end
|
|
|
|
% Get the color of the grid and translate it to pgfplots usable
|
|
% values
|
|
[minorGridColor, defaultColor] = getAndCheckDefault(...
|
|
'axes', handle, 'MinorGridColor', [0.1, 0.1, 0.1]);
|
|
if ~defaultColor
|
|
[m2t, minorGridColor] = getColor(m2t, handle, minorGridColor, 'patch');
|
|
minorGridOpts = opts_add(minorGridOpts, minorGridColor);
|
|
end
|
|
|
|
% Get the alpha of the grid and translate it to pgfplots
|
|
[minorGridAlpha, defaultAlpha] = getAndCheckDefault(...
|
|
'axes', handle, 'MinorGridAlpha', 0.1);
|
|
if ~defaultAlpha
|
|
minorGridOpts = opts_add(minorGridOpts, 'opacity', num2str(minorGridAlpha));
|
|
end
|
|
|
|
if ~isempty(minorGridOpts)
|
|
options = opts_addSubOpts(options, 'minor grid style', minorGridOpts);
|
|
end
|
|
end
|
|
|
|
if ~any(hasGrid) && ~any(hasMinorGrid)
|
|
% When specifying 'axis on top', the axes stay above all graphs (which is
|
|
% default MATLAB behavior), but so do the grids (which is not default
|
|
% behavior).
|
|
%TODO: use proper grid ordering
|
|
if m2t.args.strict
|
|
options = opts_add(options, 'axis on top');
|
|
end
|
|
% FIXME: axis background, axis grid, main, axis ticks, axis lines, axis tick labels, axis descriptions, axis foreground
|
|
end
|
|
|
|
m2t.axes{end}.options = opts_merge(m2t.axes{end}.options, options);
|
|
end
|
|
% ==============================================================================
|
|
function m2t = add3DOptionsOfAxes(m2t, handle)
|
|
% adds 3D specific options of an axes object
|
|
if isAxis3D(handle)
|
|
[m2t, zopts] = getAxisOptions(m2t, handle, 'z');
|
|
m2t.axes{end}.options = opts_merge(m2t.axes{end}.options, zopts);
|
|
|
|
VIEWFORMAT = ['{' m2t.ff '}{' m2t.ff '}'];
|
|
m2t = m2t_addAxisOption(m2t, 'view', sprintf(VIEWFORMAT, get(handle, 'View')));
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function legendhandle = getAssociatedLegend(m2t, axisHandle)
|
|
% Get legend handle associated with current axis
|
|
|
|
legendhandle = [];
|
|
[env, envVersion] = getEnvironment();
|
|
switch env
|
|
case 'Octave'
|
|
% Make sure that m2t.legendHandles is a row vector.
|
|
for lhandle = m2t.legendHandles(:)'
|
|
if isVersionBelow(envVersion, [4,2,2]) % Octave commit 5865d2fef424
|
|
lhandleProp{1}='UserData';
|
|
lhandleProp{2}='handle';
|
|
else
|
|
lhandleProp{1}='__appdata__';
|
|
lhandleProp{2}='__axes_handle__';
|
|
end
|
|
ud = get(lhandle, lhandleProp{1});
|
|
% Empty if no legend and multiple handles if plotyy
|
|
if ~isempty(ud) && any(axisHandle == ud.(lhandleProp{2}))
|
|
legendhandle = lhandle;
|
|
break
|
|
end
|
|
end
|
|
case 'MATLAB'
|
|
if isprop(axisHandle, 'Legend') % R2017b behavior
|
|
legendhandle = axisHandle.Legend;
|
|
else
|
|
legendhandle = legend(axisHandle);
|
|
% this is the proper way for old versions of MATLAB
|
|
% in newer versions (see #1006), calling `legend` may
|
|
% create a legend in the figure where none existed before
|
|
end
|
|
end
|
|
|
|
% NOTE: there is a BUG in HG1 and Octave. Setting the box off sets the
|
|
% legend visibility off too. We assume the legend is visible if it has
|
|
% a visible child.
|
|
isInvisibleHG2 = isHG2() && ~isVisible(legendhandle);
|
|
isInvisibleHG1orOctave = (~isHG2() || strcmpi(env,'Octave')) &&...
|
|
~isVisibleContainer(legendhandle);
|
|
|
|
% Do not return the handle if legend is invisible
|
|
if isInvisibleHG1orOctave || isInvisibleHG2;
|
|
legendhandle = [];
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function entries = getLegendEntries(m2t)
|
|
% Retrieve the handles of the objects that have a legend entry
|
|
|
|
% Non-composite objects are straightforward, e.g. line, and have the
|
|
% legend entry at their same level, hence we return their handle.
|
|
%
|
|
% Hggroups behave differently depending on the environment and we might
|
|
% return the handle to the hgroot or to one of its children:
|
|
% 1) Matlab places the legend entry at the hgroot.
|
|
%
|
|
% Usually, the decision to place the legend is either unchanged from
|
|
% the first call to handleAllChildrena(axis) or delegated to a
|
|
% specialized drawing routine, e.g. drawContour(), if the group has to
|
|
% be drawn atomically. In this case, the legend entry stays with the
|
|
% hgroot.
|
|
%
|
|
% If the hggroup is a pure container like in a bodeplot, i.e. the
|
|
% `type` is not listed in drawHggroup(), a nested call to
|
|
% handleAllChildren(hgroot) follows. But, this second call cannot detect
|
|
% legend entries on the children. Hence, we pass down the legend entry
|
|
% from the hgroot to its first child.
|
|
%
|
|
% 2) Octave places the entry with one of the children of the hgroot.
|
|
% Hence, most of the hggroups are correctly dealt by a nested
|
|
% handleAllChildren() call which detects the entry on the child.
|
|
% However, when we can guess the type of hggroup with
|
|
% guessOctavePlotType(), the legend entry should be placed at the root
|
|
% level, hence we bubble it up from the child to the hgroot.
|
|
|
|
entries = [];
|
|
legendHandle = m2t.axes{end}.LegendHandle;
|
|
|
|
if isempty(legendHandle)
|
|
return
|
|
end
|
|
|
|
switch getEnvironment()
|
|
case 'Octave'
|
|
if isappdata(legendHandle,'__peer_objects__')
|
|
% Octave version 6.x or newer with refactored legend
|
|
entries = getappdata(legendHandle,'__peer_objects__')
|
|
else
|
|
% See set(hlegend, "deletefcn", {@deletelegend2, ca, [], [], t1, hplots}); in legend.m
|
|
delfun = get(legendHandle,'deletefcn');
|
|
entries = delfun{6};
|
|
end
|
|
|
|
% Bubble-up legend entry properties from child to hggroup root
|
|
% for guessable objects
|
|
for ii = 1:numel(entries)
|
|
child = entries(ii);
|
|
anc = ancestor(child,'hggroup');
|
|
if isempty(anc) % not an hggroup
|
|
continue
|
|
end
|
|
cl = guessOctavePlotType(anc);
|
|
if ~strcmpi(cl, 'unknown') % guessable hggroup, then bubble-up
|
|
legendString = get(child,'displayname');
|
|
set(anc,'displayname',legendString);
|
|
entries(ii) = anc;
|
|
end
|
|
end
|
|
|
|
case 'MATLAB'
|
|
% Undocumented property (exists at least since 2008a)
|
|
entries = get(legendHandle,'PlotChildren');
|
|
|
|
% Take only the first child from a pure hggroup (e.g. bodeplots)
|
|
for ii = 1:numel(entries)
|
|
entry = entries(ii);
|
|
% Note that class() is not supported in Octave
|
|
isHggroupClass = strcmpi(class(handle(entry)),'hggroup');
|
|
if isHggroupClass
|
|
children = get(entry, 'Children');
|
|
firstChild = children(1);
|
|
if isnumeric(firstChild)
|
|
firstChild = handle(firstChild);
|
|
end
|
|
% Inherits DisplayName from hggroup root
|
|
set(firstChild, 'DisplayName', get(entry, 'DisplayName'));
|
|
entries(ii) = firstChild;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function m2t = getPlotyyReferences(m2t,axisHandle)
|
|
% Retrieve references to legend entries of the main plotyy axis
|
|
%
|
|
% A plotyy plot has a main and a secondary axis. The legend is associated
|
|
% with the main axis and hence m2t will only include the legend entries
|
|
% that belong to the \axis[] that has a legend.
|
|
%
|
|
% One way to include the legend entries from the secondary axis (in the
|
|
% same legend) is to first label the \addplot[] and then reference them.
|
|
% See https://tex.stackexchange.com/questions/42697/42752#42752
|
|
%
|
|
% However, in .tex labels should come before they are referenced. Hence,
|
|
% we actually label the legend entries from the main axis and swap the
|
|
% legendhandle to the secondary axis.
|
|
%
|
|
% The legend will not be plotted with the main \axis[] and the labelled
|
|
% legend entries will be skipped until the secondary axis. Then, they will
|
|
% be listed before any legend entry from the secondary axis.
|
|
|
|
% Retrieve legend handle
|
|
if isAxisMain(axisHandle)
|
|
legendHandle = m2t.axes{end}.LegendHandle;
|
|
else
|
|
legendHandle = getAssociatedLegend(m2t,getPlotyyPeer(axisHandle));
|
|
m2t.axes{end}.LegendHandle = legendHandle;
|
|
end
|
|
|
|
% Not a plotyy axis or no legend
|
|
if ~isAxisPlotyy(axisHandle) || isempty(legendHandle)
|
|
m2t.axes{end}.PlotyyReferences = [];
|
|
|
|
elseif isAxisMain(axisHandle)
|
|
% Mark legend entries of the main axis for labelling
|
|
legendEntries = m2t.axes{end}.LegendEntries;
|
|
ancAxes = ancestor(legendEntries,'axes');
|
|
idx = ismember([ancAxes{:}], axisHandle);
|
|
m2t.axes{end}.PlotyyReferences = legendEntries(idx);
|
|
|
|
% Ensure no legend is created on the main axis
|
|
m2t.axes{end}.LegendHandle = [];
|
|
else
|
|
% Get legend entries associated to secondary plotyy axis. We can do
|
|
% this because we took the legendhandle from the peer (main axis)
|
|
legendEntries = getLegendEntries(m2t);
|
|
ancAxes = ancestor(legendEntries,'axes');
|
|
if iscell(ancAxes)
|
|
ancAxes = [ancAxes{:}];
|
|
end
|
|
idx = ismember(double(ancAxes), axisHandle);
|
|
m2t.axes{end}.LegendEntries = legendEntries(idx);
|
|
|
|
% Recover referenced legend entries of the main axis
|
|
m2t.axes{end}.PlotyyReferences = legendEntries(~idx);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function bool = isAxisMain(h)
|
|
% Check if it is the main axis e.g. in a plotyy plot
|
|
|
|
if ~isAxisPlotyy(h)
|
|
bool = true;
|
|
return % an axis not constructed by plotyy is always(?) a main axis
|
|
end
|
|
|
|
% If it is a Plotyy axis
|
|
switch getEnvironment()
|
|
case 'Octave'
|
|
plotyyAxes = get(h, '__plotyy_axes__');
|
|
bool = find(plotyyAxes == h) == 1;
|
|
|
|
case 'MATLAB'
|
|
% LegendPeerHandle was renamed to LayoutPeers in 9.1 (R2016b)
|
|
if verLessThan('matlab', '9.1')
|
|
bool = ~isempty(getappdata(h, 'LegendPeerHandle'));
|
|
else
|
|
bool = ~isempty(getappdata(h, 'LayoutPeers'));
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function bool = isAxisPlotyy(h)
|
|
% Check if handle is a plotyy axis
|
|
|
|
switch getEnvironment()
|
|
case 'Octave'
|
|
% Cannot test hidden property with isfield(), is always false
|
|
try
|
|
get(h, '__plotyy_axes__');
|
|
bool = true;
|
|
catch
|
|
bool = false;
|
|
end
|
|
|
|
case 'MATLAB'
|
|
bool = ~isempty(getappdata(h, 'graphicsPlotyyPeer'));
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function peer = getPlotyyPeer(axisHandle)
|
|
% Get the other axis coupled in plotyy plots
|
|
|
|
switch getEnvironment()
|
|
case 'Octave'
|
|
plotyyAxes = get(axisHandle, '__plotyy_axes__');
|
|
peer = setdiff(plotyyAxes, axisHandle);
|
|
|
|
case 'MATLAB'
|
|
peer = getappdata(axisHandle, 'graphicsPlotyyPeer');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function legendString = getLegendString(m2t, h)
|
|
% Retrieve the legend string for the given handle
|
|
str = getOrDefault(h, 'displayname', '');
|
|
interpreter = get(m2t.axes{end}.LegendHandle,'interpreter');
|
|
|
|
% HG1: autogenerated legend strings, i.e. data1,..., dataN, do not populate
|
|
% the 'displayname' property. Go through 'userdata'
|
|
if isempty(str)
|
|
ud = get(m2t.axes{end}.LegendHandle,'userdata');
|
|
idx = ismember(ud.handles, h);
|
|
str = ud.lstrings{idx};
|
|
end
|
|
|
|
% split string to cell, if newline character '\n' (ASCII 10) is present
|
|
delimeter = sprintf('\n');
|
|
str = regexp(str, delimeter, 'split');
|
|
str = prettyPrint(m2t, str, interpreter);
|
|
legendString = join(m2t, str, '\\');
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, bool] = hasLegendEntry(m2t, h)
|
|
% Check if the handle has a legend entry and track its legend status in m2t
|
|
legendEntries = m2t.axes{end}.LegendEntries;
|
|
if isnumeric(h)
|
|
legendEntries = double(legendEntries);
|
|
end
|
|
|
|
% Should not have a legend reference
|
|
bool = any(ismember(h, legendEntries)) && ~hasPlotyyReference(m2t,h);
|
|
m2t.currentHandleHasLegend = bool;
|
|
end
|
|
% ==============================================================================
|
|
function bool = hasPlotyyReference(m2t,h)
|
|
% Check if the handle has a legend reference
|
|
plotyyReferences = m2t.axes{end}.PlotyyReferences;
|
|
if isnumeric(h)
|
|
plotyyReferences = double(plotyyReferences);
|
|
end
|
|
|
|
bool = any(ismember(h, plotyyReferences));
|
|
end
|
|
% ==============================================================================
|
|
function m2t = retrievePositionOfAxes(m2t, handle)
|
|
% This retrieves the position of an axes and stores it into the m2t data
|
|
% structure
|
|
|
|
pos = getAxesPosition(m2t, handle, m2t.args.width, ...
|
|
m2t.args.height, m2t.axesBoundingBox);
|
|
% set the width
|
|
if (~m2t.args.noSize)
|
|
% optionally prevents setting the width and height of the axis
|
|
m2t = setDimensionOfAxes(m2t, 'width', pos.w);
|
|
m2t = setDimensionOfAxes(m2t, 'height', pos.h);
|
|
|
|
m2t = m2t_addAxisOption(m2t, 'at', ...
|
|
['{(' formatDim(pos.x.value, pos.x.unit) ','...
|
|
formatDim(pos.y.value, pos.y.unit) ')}']);
|
|
% the following is general MATLAB behavior:
|
|
m2t = m2t_addAxisOption(m2t, 'scale only axis');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function m2t = setDimensionOfAxes(m2t, widthOrHeight, dimension)
|
|
% sets the dimension "name" of the current axes to the struct "dim"
|
|
m2t = m2t_addAxisOption(m2t, widthOrHeight, ...
|
|
formatDim(dimension.value, dimension.unit));
|
|
end
|
|
% ==============================================================================
|
|
function m2t = addAspectRatioOptionsOfAxes(m2t, handle)
|
|
% Set manual aspect ratio for current axes
|
|
% TODO: deal with 'axis image', 'axis square', etc. (#540)
|
|
if strcmpi(get(handle, 'DataAspectRatioMode'), 'manual') ||...
|
|
strcmpi(get(handle, 'PlotBoxAspectRatioMode'), 'manual')
|
|
% we need to set the plot box aspect ratio
|
|
if m2t.axes{end}.is3D
|
|
% Note: set 'plot box ratio' for 3D axes to avoid bug with
|
|
% 'scale mode = uniformly' (see #560)
|
|
aspectRatio = getPlotBoxAspectRatio(handle);
|
|
m2t = m2t_addAxisOption(m2t, 'plot box ratio', ...
|
|
formatAspectRatio(m2t, aspectRatio));
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function m2t = drawBackgroundOfAxes(m2t, handle)
|
|
% draw the background color of the current axes
|
|
backgroundColor = get(handle, 'Color');
|
|
if ~isNone(backgroundColor) && isVisible(handle)
|
|
[m2t, col] = getColor(m2t, handle, backgroundColor, 'patch');
|
|
m2t = m2t_addAxisOption(m2t, 'axis background/.style', sprintf('{fill=%s}', col));
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function m2t = drawTitleOfAxes(m2t, handle)
|
|
% processes the title of an axes object
|
|
[m2t, m2t.axes{end}.options] = getTitle(m2t, handle, m2t.axes{end}.options);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, opts] = getTitle(m2t, handle, opts)
|
|
% gets the title and its markup from an axes/colorbar/...
|
|
[m2t, opts] = getTitleOrLabel_(m2t, handle, opts, 'Title');
|
|
end
|
|
function [m2t, opts] = getLabel(m2t, handle, opts, tikzKeyword)
|
|
% gets the label and its markup from an axes/colorbar/...
|
|
[m2t, opts] = getTitleOrLabel_(m2t, handle, opts, 'Label', tikzKeyword);
|
|
end
|
|
function [m2t, opts] = getAxisLabel(m2t, handle, axis, opts)
|
|
% convert an {x,y,z} axis label to TikZ
|
|
labelName = [upper(axis) 'Label'];
|
|
[m2t, opts] = getTitleOrLabel_(m2t, handle, opts, labelName);
|
|
end
|
|
function [m2t, opts] = getTitleOrLabel_(m2t, handle, opts, labelKind, tikzKeyword)
|
|
% gets a string element from an object
|
|
if ~exist('tikzKeyword', 'var') || isempty(tikzKeyword)
|
|
tikzKeyword = lower(labelKind);
|
|
end
|
|
object = get(handle, labelKind);
|
|
|
|
str = get(object, 'String');
|
|
if ~isempty(str)
|
|
interpreter = get(object, 'Interpreter');
|
|
str = prettyPrint(m2t, str, interpreter);
|
|
[m2t, style] = getFontStyle(m2t, object);
|
|
if length(str) > 1 %multiline
|
|
style = opts_add(style, 'align', 'center');
|
|
end
|
|
if ~isempty(style)
|
|
opts = opts_addSubOpts(opts, [tikzKeyword ' style'], style);
|
|
end
|
|
str = join(m2t, str, '\\[1ex]');
|
|
opts = opts_add(opts, tikzKeyword, sprintf('{%s}', str));
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function m2t = drawBoxAndLineLocationsOfAxes(m2t, h)
|
|
% draw the box and axis line location of an axes object
|
|
isBoxOn = isOn(get(h, 'box'));
|
|
xLoc = get(h, 'XAxisLocation');
|
|
yLoc = get(h, 'YAxisLocation');
|
|
isXaxisBottom = strcmpi(xLoc, 'bottom');
|
|
isYaxisLeft = strcmpi(yLoc, 'left');
|
|
|
|
if strcmpi(xLoc, 'origin')
|
|
xLoc = 'middle';
|
|
end
|
|
if strcmpi(yLoc, 'origin')
|
|
yLoc = 'middle';
|
|
end
|
|
|
|
% Only flip the labels to the other side if not at the default
|
|
% left/bottom positions
|
|
if isBoxOn
|
|
if ~isXaxisBottom
|
|
m2t = m2t_addAxisOption(m2t, 'xticklabel pos','right');
|
|
end
|
|
if ~isYaxisLeft
|
|
m2t = m2t_addAxisOption(m2t, 'yticklabel pos','right');
|
|
end
|
|
|
|
% Position axes lines (strips the box)
|
|
else
|
|
m2t = m2t_addAxisOption(m2t, 'axis x line*', xLoc);
|
|
m2t = m2t_addAxisOption(m2t, 'axis y line*', yLoc);
|
|
if m2t.axes{end}.is3D
|
|
% There's no such attribute as 'ZAxisLocation'.
|
|
% Instead, the default seems to be 'left'.
|
|
m2t = m2t_addAxisOption(m2t, 'axis z line*', 'left');
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function m2t = drawLegendOptionsOfAxes(m2t)
|
|
legendHandle = m2t.axes{end}.LegendHandle;
|
|
if isempty(legendHandle)
|
|
return
|
|
end
|
|
|
|
[m2t, key, legendOpts] = getLegendOpts(m2t, legendHandle);
|
|
m2t = m2t_addAxisOption(m2t, key, legendOpts);
|
|
end
|
|
% ==============================================================================
|
|
function m2t = handleColorbar(m2t, handle)
|
|
if isempty(handle)
|
|
return;
|
|
end
|
|
|
|
% Find the axes environment that this colorbar belongs to.
|
|
parentAxesHandle = double(get(handle,'axes'));
|
|
parentFound = false;
|
|
for k = 1:length(m2t.axes)
|
|
if m2t.axes{k}.handle == parentAxesHandle
|
|
k0 = k;
|
|
parentFound = true;
|
|
break;
|
|
end
|
|
end
|
|
if parentFound
|
|
m2t.axes{k0}.options = opts_append(m2t.axes{k0}.options, ...
|
|
matlab2pgfplotsColormap(m2t, m2t.current.colormap), []);
|
|
% Append cell string.
|
|
m2t.axes{k0}.options = cat(1, m2t.axes{k0}.options, ...
|
|
getColorbarOptions(m2t, handle));
|
|
else
|
|
warning('matlab2tikz:parentAxesOfColorBarNotFound',...
|
|
'Could not find parent axes for color bar. Skipping.');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, options] = getAxisOptions(m2t, handle, axis)
|
|
assertValidAxisSpecifier(axis);
|
|
|
|
options = opts_new();
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% axis colors
|
|
[color, isDfltColor] = getAndCheckDefault('Axes', handle, ...
|
|
[upper(axis),'Color'], [ 0 0 0 ]);
|
|
if ~isDfltColor || m2t.args.strict
|
|
[m2t, col] = getColor(m2t, handle, color, 'patch');
|
|
if isOn(get(handle, 'box'))
|
|
% If the axes are arranged as a box, make sure that the individual
|
|
% axes are drawn as four separate paths. This makes the alignment
|
|
% at the box corners somewhat less nice, but allows for different
|
|
% axis styles (e.g., colors).
|
|
options = opts_add(options, 'separate axis lines');
|
|
end
|
|
% set color of axis lines
|
|
options = ...
|
|
opts_add(options, ...
|
|
['every outer ', axis, ' axis line/.append style'], ...
|
|
['{', col, '}']);
|
|
% set color of tick labels
|
|
options = ...
|
|
opts_add(options, ...
|
|
['every ',axis,' tick label/.append style'], ...
|
|
['{font=\color{',col,'}}']);
|
|
% set color of ticks
|
|
options = ...
|
|
opts_add(options, ...
|
|
['every ',axis,' tick/.append style'], ...
|
|
['{',col,'}']);
|
|
end
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% handle the orientation
|
|
isAxisReversed = strcmpi(get(handle,[upper(axis),'Dir']), 'reverse');
|
|
m2t.([axis 'AxisReversed']) = isAxisReversed;
|
|
if isAxisReversed
|
|
options = opts_add(options, [axis, ' dir'], 'reverse');
|
|
end
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
axisScale = getOrDefault(handle, [upper(axis) 'Scale'], 'lin');
|
|
if strcmpi(axisScale, 'log');
|
|
options = opts_add(options, [axis,'mode'], 'log');
|
|
end
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% get axis limits
|
|
[m2t, options] = setAxisLimits(m2t, handle, axis, options);
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% get ticks along with the labels
|
|
[options] = getAxisTicks(m2t, handle, axis, options);
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% get axis label
|
|
[m2t, options] = getAxisLabel(m2t, handle, axis, options);
|
|
end
|
|
% ==============================================================================
|
|
function [options] = getAxisTicks(m2t, handle, axis, options)
|
|
% Return axis tick marks Pgfplots style. Nice: Tick lengths and such
|
|
% details are taken care of by Pgfplots.
|
|
assertValidAxisSpecifier(axis);
|
|
|
|
keywordTickMode = [upper(axis), 'TickMode'];
|
|
tickMode = get(handle, keywordTickMode);
|
|
keywordTick = [upper(axis), 'Tick'];
|
|
ticks = get(handle, keywordTick);
|
|
|
|
% hidden properties are not caught by hasProperties
|
|
isDatetimeTicks = isAxisTicksDateTime(handle, axis);
|
|
if isDatetimeTicks
|
|
ticks = datenum(ticks);
|
|
end
|
|
|
|
if isempty(ticks)
|
|
% If no ticks are present, we need to enforce this in any case.
|
|
pgfTicks = '\empty';
|
|
elseif strcmpi(tickMode, 'auto') && ~m2t.args.strict && ~isDatetimeTicks
|
|
% Let pgfplots decide if the tickmode is auto or conversion is not
|
|
% strict and we are not dealing with datetime ticks
|
|
pgfTicks = [];
|
|
else % strcmpi(tickMode,'manual') || m2t.args.strict
|
|
pgfTicks = join(m2t, cellstr(num2str(ticks(:))), ', ');
|
|
end
|
|
|
|
keywordTickLabelMode = [upper(axis), 'TickLabelMode'];
|
|
tickLabelMode = get(handle, keywordTickLabelMode);
|
|
if strcmpi(tickLabelMode, 'auto') && ~m2t.args.strict && ~isDatetimeTicks
|
|
pgfTickLabels = [];
|
|
else % strcmpi(tickLabelMode,'manual') || m2t.args.strict
|
|
% HG2 allows to set 'TickLabelInterpreter'.
|
|
% HG1 tacitly uses the interpreter 'none'.
|
|
% See http://www.mathworks.com/matlabcentral/answers/102053#comment_300079
|
|
fallback = defaultTickLabelInterpreter(m2t);
|
|
interpreter = getOrDefault(handle, 'TickLabelInterpreter', fallback);
|
|
keywordTickLabel = [upper(axis), 'TickLabel'];
|
|
tickLabels = cellstr(get(handle, keywordTickLabel));
|
|
tickLabels = prettyPrint(m2t, tickLabels, interpreter);
|
|
|
|
keywordScale = [upper(axis), 'Scale'];
|
|
isAxisLog = strcmpi(getOrDefault(handle,keywordScale, 'lin'), 'log');
|
|
[pgfTicks, pgfTickLabels] = ...
|
|
matlabTicks2pgfplotsTicks(m2t, ticks, tickLabels, isAxisLog, tickLabelMode);
|
|
end
|
|
|
|
keywordMinorTick = [upper(axis), 'MinorTick'];
|
|
hasMinorTicks = isOn(getOrDefault(handle, keywordMinorTick, 'off'));
|
|
tickDirection = getOrDefault(handle, 'TickDir', 'in');
|
|
|
|
options = setAxisTicks(m2t, options, axis, pgfTicks, pgfTickLabels, ...
|
|
hasMinorTicks, tickDirection, isDatetimeTicks);
|
|
|
|
options = setAxisTickLabelStyle(options, axis, handle);
|
|
end
|
|
% ==============================================================================
|
|
function options = setAxisTickLabelStyle(options, axis, handle)
|
|
% determine the style of tick labels
|
|
%TODO: translate the style of tick labels fully (font?, weight, ...)
|
|
kwRotation = [upper(axis), 'TickLabelRotation'];
|
|
rotation = getOrDefault(handle, kwRotation, 0);
|
|
if rotation ~= 0
|
|
options = opts_add(options, [axis, 'ticklabel style'], ...
|
|
sprintf('{rotate=%d}', rotation));
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function interpreter = defaultTickLabelInterpreter(m2t)
|
|
% determines the default tick label interpreter
|
|
% This is only relevant in HG1/Octave. In HG2, we use the interpreter
|
|
% set in the object (not the global default).
|
|
if m2t.args.interpretTickLabelsAsTex
|
|
interpreter = 'tex';
|
|
else
|
|
interpreter = 'none';
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function isDatetimeTicks = isAxisTicksDateTime(handle, axis)
|
|
% returns true when the axis has DateTime ticks
|
|
|
|
% If R2016b or above
|
|
if isa(get(handle, [upper(axis), 'Tick']), 'datetime')
|
|
isDatetimeTicks = true;
|
|
else
|
|
try
|
|
% Get hidden properties of the datetime axes manager
|
|
dtsManager = get(handle, 'DatetimeDurationPlotAxesListenersManager');
|
|
oldState = warning('off','MATLAB:structOnObject');
|
|
dtsManager = struct(dtsManager);
|
|
warning(oldState);
|
|
|
|
isDatetimeTicks = dtsManager.([upper(axis) 'DateTicks']) == 1;
|
|
catch
|
|
isDatetimeTicks = false;
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function options = setAxisTicks(m2t, options, axis, ticks, tickLabels,hasMinorTicks, tickDir,isDatetimeTicks)
|
|
% set ticks options
|
|
|
|
% According to http://www.mathworks.com/help/techdoc/ref/axes_props.html,
|
|
% the number of minor ticks is automatically determined by MATLAB(R) to
|
|
% fit the size of the axis. Until we know how to extract this number, use
|
|
% a reasonable default.
|
|
matlabDefaultNumMinorTicks = 3;
|
|
if ~isempty(ticks)
|
|
options = opts_add(options, [axis,'tick'], sprintf('{%s}', ticks));
|
|
end
|
|
if ~isempty(tickLabels)
|
|
options = opts_add(options, ...
|
|
[axis,'ticklabels'], sprintf('{%s}', tickLabels));
|
|
end
|
|
if hasMinorTicks
|
|
options = opts_add(options, [axis,'minorticks'], 'true');
|
|
if m2t.args.strict
|
|
options = opts_add(options, ...
|
|
sprintf('minor %s tick num', axis), ...
|
|
sprintf('{%d}', matlabDefaultNumMinorTicks));
|
|
end
|
|
end
|
|
|
|
if strcmpi(tickDir,'out')
|
|
options = opts_add(options, 'tick align', 'outside');
|
|
elseif strcmpi(tickDir,'both')
|
|
options = opts_add(options, 'tick align', 'center');
|
|
end
|
|
|
|
if isDatetimeTicks
|
|
options = opts_add(options, ['scaled ' axis ' ticks'], 'false');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function assertValidAxisSpecifier(axis)
|
|
% assert that axis is a valid axis specifier
|
|
if ~ismember(axis, {'x','y','z'})
|
|
error('matlab2tikz:illegalAxisSpecifier', ...
|
|
'Illegal axis specifier "%s".', axis);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function assertRegularAxes(handle)
|
|
% assert that the (axes) object specified by handle is a regular axes and not a
|
|
% colorbar or a legend
|
|
tag = lower(get(handle,'Tag'));
|
|
if ismember(tag,{'colorbar','legend'})
|
|
error('matlab2tikz:notARegularAxes', ...
|
|
['The object "%s" is not a regular axes object. ' ...
|
|
'It cannot be handled with drawAxes!'], handle);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, options] = setAxisLimits(m2t, handle, axis, options)
|
|
% set the upper/lower limit of an axis (but not for categorical values)
|
|
limits = get(handle, [upper(axis),'Lim']);
|
|
if isa(limits,'datetime')
|
|
limits = datenum(limits);
|
|
end
|
|
if ~isCategoricalType(limits(1)) && isfinite(limits(1))
|
|
options = opts_add(options, [axis,'min'], sprintf(m2t.ff, limits(1)));
|
|
end
|
|
if ~isCategoricalType(limits(2)) && isfinite(limits(2))
|
|
options = opts_add(options, [axis,'max'], sprintf(m2t.ff, limits(2)));
|
|
end
|
|
|
|
% Take care of categorical variables
|
|
if isCategoricalType(limits(1))
|
|
|
|
% Categorical variables do not need to specify limits, but symbolic values
|
|
categories = get(get(handle, [upper(axis),'Axis']), 'Categories');
|
|
symb_coords = sprintf('{%s}', join(m2t, cellstr(string(categories)), ','));
|
|
m2t = m2t_addAxisOption(m2t, ['symbolic ',axis,' coords'], symb_coords);
|
|
|
|
% Some extra space is needed, otherwise the bars will touch the boundary
|
|
options = opts_add(options, ['enlarge ',axis,' limits'], '0.2');
|
|
|
|
% Ticks only for data values, prevents randomly repeated labels
|
|
options = opts_add(options, [axis,'tick'], 'data');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function bool = isVisibleContainer(axisHandle)
|
|
if ~isVisible(axisHandle)
|
|
% An invisible axes container *can* have visible children, so don't
|
|
% immediately bail out here. Also it *can* have a visible title,
|
|
% labels or children
|
|
|
|
bool = false;
|
|
for prop = {'Children', 'Title', 'XLabel', 'YLabel', 'ZLabel'}
|
|
property = prop{1};
|
|
if strcmpi(property, 'Children')
|
|
children = allchild(axisHandle);
|
|
elseif isprop(axisHandle, property)
|
|
children = get(axisHandle, property);
|
|
else
|
|
continue; % don't check non-existent properties
|
|
end
|
|
for child = children(:)'
|
|
if isVisible(child)
|
|
bool = true;
|
|
return;
|
|
end
|
|
end
|
|
end
|
|
else
|
|
bool = true;
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawLine(m2t, h, custom)
|
|
% Returns the code for drawing a regular line and error bars.
|
|
% This is an extremely common operation and takes place in most of the
|
|
% not too fancy plots.
|
|
str = '';
|
|
if ~isLineVisible(h)
|
|
return; % there is nothing to plot
|
|
end
|
|
|
|
% Color
|
|
color = get(h, 'Color');
|
|
[m2t, xcolor] = getColor(m2t, h, color, 'patch');
|
|
% Line and marker options
|
|
[m2t, lineOptions] = getLineOptions(m2t, h);
|
|
[m2t, markerOptions] = getMarkerOptions(m2t, h);
|
|
|
|
% Only marks for legend consistency
|
|
hasNoLine = opts_has(lineOptions, 'draw') && isNone(opts_get(lineOptions, 'draw'));
|
|
if ~isempty(markerOptions) && hasNoLine
|
|
lineOptions = opts_remove(lineOptions, 'draw');
|
|
lineOptions = opts_add(lineOptions, 'only marks', []);
|
|
end
|
|
|
|
drawOptions = opts_new();
|
|
drawOptions = opts_add(drawOptions, 'color', xcolor);
|
|
drawOptions = opts_merge(drawOptions, lineOptions, markerOptions);
|
|
drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
|
|
|
|
% Check for "special" lines, e.g.:
|
|
if strcmpi(get(h, 'Tag'), 'zplane_unitcircle')
|
|
[m2t, str] = specialDrawZplaneUnitCircle(m2t, drawOptions);
|
|
return
|
|
end
|
|
|
|
% build the data matrix
|
|
data = getXYZDataFromLine(m2t, h);
|
|
xDeviation = getXDeviations(h);
|
|
yDeviation = getYDeviations(h);
|
|
|
|
errDir = '';
|
|
if ~isempty(xDeviation)
|
|
data = [data, xDeviation];
|
|
errDir = [errDir, 'x'];
|
|
end
|
|
if ~isempty(yDeviation)
|
|
data = [data, yDeviation];
|
|
errDir = [errDir, 'y'];
|
|
end
|
|
|
|
% Check if any value is infinite/NaN. In that case, add appropriate option.
|
|
m2t = jumpAtUnboundCoords(m2t, data);
|
|
|
|
[m2t, dataString] = writePlotData(m2t, data, drawOptions, errDir);
|
|
[m2t, labelString] = addLabel(m2t, h);
|
|
|
|
str = [dataString, labelString];
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = specialDrawZplaneUnitCircle(m2t, drawOptions)
|
|
% Draw unit circle and axes.
|
|
|
|
% TODO Don't hardcode "10", but extract from parent axes of |h|
|
|
opts = opts_print(drawOptions);
|
|
str = [sprintf('\\draw[%s] (axis cs:0,0) circle[radius=1];\n', opts), ...
|
|
sprintf('\\draw[%s] (axis cs:-10,0)--(axis cs:10,0);\n', opts), ...
|
|
sprintf('\\draw[%s] (axis cs:0,-10)--(axis cs:0,10);\n', opts)];
|
|
end
|
|
% ==============================================================================
|
|
function bool = isLineVisible(h)
|
|
% check if a line object is actually visible (has markers and so on)
|
|
|
|
lineStyle = get(h, 'LineStyle');
|
|
lineWidth = get(h, 'LineWidth');
|
|
marker = getOrDefault(h, 'Marker','none');
|
|
hasLines = ~isNone(lineStyle) && lineWidth > 0;
|
|
hasMarkers = ~isNone(marker);
|
|
hasDeviations = ~isempty(getYDeviations(h));
|
|
|
|
bool = isVisible(h) && (hasLines || hasMarkers || hasDeviations);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = writePlotData(m2t, data, drawOptions, errDir)
|
|
% actually writes the plot data to file
|
|
str = '';
|
|
|
|
is3D = m2t.axes{end}.is3D;
|
|
if is3D
|
|
% Don't try to be smart in parametric 3d plots: Just plot all the data.
|
|
[m2t, table, tableOptions] = makeTable(m2t, {'','',''}, data);
|
|
|
|
% Print out
|
|
drawOpts = opts_print(drawOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\addplot3 [%s]\n table[%s] {%s};\n ', ...
|
|
drawOpts, tabOpts, table);
|
|
else
|
|
% split the data into logical chunks
|
|
dataCell = splitLine(m2t, data);
|
|
|
|
% plot them
|
|
strPart = cell(1, length(dataCell));
|
|
for k = 1:length(dataCell)
|
|
%If drawOptions has error bar style options, then print these
|
|
%options on the variable errorBarOpts and remove them from
|
|
%drawOptions
|
|
if (opts_has(drawOptions,'error bar style'))
|
|
tmpOptions = opts_new();
|
|
tmpOptions = opts_add(tmpOptions,'error bars/.cd','');
|
|
% add x direction error bar style if necessary
|
|
if contains(errDir, 'x')
|
|
tmpOptions = opts_add(tmpOptions,'x dir','both');
|
|
tmpOptions = opts_add(tmpOptions,'x explicit','');
|
|
end
|
|
% add y direction error bar style if necessary
|
|
if contains(errDir, 'y')
|
|
tmpOptions = opts_add(tmpOptions,'y dir','both');
|
|
tmpOptions = opts_add(tmpOptions,'y explicit','');
|
|
end
|
|
tmpOptions = opts_copy(drawOptions, 'error bar style', tmpOptions);
|
|
tmpOptions = opts_copy(drawOptions, 'error mark options', tmpOptions);
|
|
|
|
errorBarOpts = opts_print(tmpOptions);
|
|
|
|
drawOptions = opts_remove(drawOptions, 'error bar style', ...
|
|
'error mark options');
|
|
|
|
else
|
|
errorBarOpts = '';
|
|
end
|
|
|
|
% If the line has a legend string, make sure to only include a legend
|
|
% entry for the *last* occurrence of the plot series.
|
|
% Hence the condition k<length(xDataCell).
|
|
%if ~isempty(m2t.legendHandles) && (~m2t.currentHandleHasLegend || k < length(dataCell))
|
|
if ~m2t.currentHandleHasLegend || k < length(dataCell)
|
|
% No legend entry found. Don't include plot in legend.
|
|
hiddenDrawOptions = maybeShowInLegend(false, drawOptions);
|
|
opts = opts_print(hiddenDrawOptions);
|
|
else
|
|
opts = opts_print(drawOptions);
|
|
end
|
|
|
|
[m2t, Part] = plotLine2d(m2t, opts, errorBarOpts, dataCell{k}, errDir);
|
|
strPart{k} = Part;
|
|
end
|
|
strPart = join(m2t, strPart, '');
|
|
str = [str, strPart];
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [data] = getXYZDataFromLine(m2t, h)
|
|
% Retrieves the X, Y and Z (if appropriate) data from a Line object
|
|
%
|
|
% First put them all together in one multiarray.
|
|
% This also implicitly makes sure that the lengths match.
|
|
try
|
|
xData = get(h, 'XData');
|
|
yData = get(h, 'YData');
|
|
catch
|
|
% Line annotation
|
|
xData = get(h, 'X');
|
|
yData = get(h, 'Y');
|
|
end
|
|
if isa(xData,'datetime')
|
|
xData = datenum(xData);
|
|
end
|
|
if isa(yData,'datetime')
|
|
yData = datenum(yData);
|
|
end
|
|
is3D = m2t.axes{end}.is3D;
|
|
if ~is3D
|
|
data = [xData(:), yData(:)];
|
|
else
|
|
zData = get(h, 'ZData');
|
|
data = applyHgTransform(m2t, [xData(:), yData(:), zData(:)]);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, labelCode] = addLabel(m2t, h)
|
|
% conditionally add a LaTeX label after the current plot
|
|
labelCode = '';
|
|
|
|
if m2t.args.automaticLabels||m2t.args.addLabels
|
|
lineTag = get(h,'Tag');
|
|
if ~isempty(lineTag)
|
|
labelName = sprintf('%s', lineTag);
|
|
else
|
|
[pathstr, name] = fileparts(m2t.args.filename); %#ok
|
|
labelName = sprintf('addplot:%s%d', name, m2t.count.autolabel);
|
|
[m2t] = incrementGlobalCounter(m2t, 'autolabel');
|
|
% TODO: First increment the counter, then use it such that the
|
|
% pattern is the same everywhere
|
|
end
|
|
labelCode = sprintf('\\label{%s}\n', labelName);
|
|
userWarning(m2t, 'Automatically added label ''%s'' for line plot.', labelName);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t,str] = plotLine2d(m2t, opts, errorBarOpts, data, errDir)
|
|
|
|
errorBar = '';
|
|
if ~isempty(errDir)
|
|
m2t = needsPgfplotsVersion(m2t, [1,9]);
|
|
errorBar = sprintf('plot [%s]\n', errorBarOpts);
|
|
end
|
|
|
|
% Convert to string array then cell to call sprintf once (and no loops).
|
|
[m2t, table, tableOptions] = makeTable(m2t, repmat({''}, size(data,2)), data);
|
|
switch errDir
|
|
case 'x'
|
|
tableOptions = opts_add(tableOptions, 'x error plus index', '2');
|
|
tableOptions = opts_add(tableOptions, 'x error minus index', '3');
|
|
case 'y'
|
|
tableOptions = opts_add(tableOptions, 'y error plus index', '2');
|
|
tableOptions = opts_add(tableOptions, 'y error minus index', '3');
|
|
case 'xy'
|
|
tableOptions = opts_add(tableOptions, 'x error plus index', '2');
|
|
tableOptions = opts_add(tableOptions, 'x error minus index', '3');
|
|
tableOptions = opts_add(tableOptions, 'y error plus index', '4');
|
|
tableOptions = opts_add(tableOptions, 'y error minus index', '5');
|
|
end
|
|
|
|
% Print out
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\addplot [%s]\n %s table[%s]{%s};\n',...
|
|
opts, errorBar, tabOpts, table);
|
|
end
|
|
% ==============================================================================
|
|
function dataCell = splitLine(m2t, data)
|
|
% TeX parses files line by line with a buffer of size buf_size. If the
|
|
% plot has too many data points, pdfTeX's buffer size may be exceeded.
|
|
% As a work-around, split the xData, yData into several chunks of data
|
|
% for each of which an \addplot will be generated.
|
|
|
|
% Get the length of the data array and the corresponding chung size
|
|
%TODO: scale `maxChunkLength` with the number of columns in the data array
|
|
len = size(data, 1);
|
|
chunkLength = m2t.args.maxChunkLength;
|
|
chunks = chunkLength * ones(ceil(len/chunkLength), 1);
|
|
if mod(len, chunkLength) ~=0
|
|
chunks(end) = mod(len, chunkLength);
|
|
end
|
|
|
|
% Cut the data into chunks
|
|
dataCell = mat2cell(data, chunks);
|
|
|
|
% Add an extra (overlap) point to the data stream otherwise the line
|
|
% between two data chunks would be broken. Technically, this is only
|
|
% needed when the plot has a line connecting the points, but the
|
|
% additional cost when there is no line doesn't justify the added
|
|
% complexity.
|
|
for i=1:length(dataCell)-1
|
|
dataCell{i}(end+1,:) = dataCell{i+1}(1,:);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, lineOpts] = getLineOptions(m2t, h)
|
|
% Gathers the line options.
|
|
lineOpts = opts_new();
|
|
|
|
% Get the options from the handle
|
|
lineWidth = get(h, 'LineWidth');
|
|
|
|
% Get the line style and check whether it is the default one
|
|
[lineStyle, isDefaultLS] = getAndCheckDefault('Line', h, 'LineStyle', '-');
|
|
|
|
if ~isDefaultLS && ~isNone(lineStyle) && (lineWidth > m2t.tol)
|
|
lineOpts = opts_add(lineOpts, translateLineStyle(lineStyle));
|
|
end
|
|
|
|
% Take over the line width in any case when in strict mode. If not, don't add
|
|
% anything in case of default line width and effectively take Pgfplots'
|
|
% default.
|
|
% Also apply the line width if no actual line is there; the markers make use
|
|
% of this, too.
|
|
matlabDefaultLineWidth = 0.5;
|
|
if ~isempty(m2t.semantic.LineWidth)
|
|
if ismember(lineWidth, [m2t.semantic.LineWidth{:,2}])
|
|
semStrID = lineWidth == [m2t.semantic.LineWidth{:,2}];
|
|
lineOpts = opts_add(lineOpts, m2t.semantic.LineWidth{semStrID,1});
|
|
else
|
|
warning('matlab2tikz:semanticLineWidthNotFound',...
|
|
['No semantic correspondance for lineWidth of ''%f'' found.'...
|
|
'Falling back to explicit export in points.'], lineWidth);
|
|
lineOpts = opts_add(lineOpts, 'line width', sprintf('%.1fpt', lineWidth));
|
|
end
|
|
elseif m2t.args.strict || ~abs(lineWidth-matlabDefaultLineWidth) <= m2t.tol
|
|
lineOpts = opts_add(lineOpts, 'line width', sprintf('%.1fpt', lineWidth));
|
|
end
|
|
|
|
% print no lines
|
|
if isNone(lineStyle) || lineWidth==0
|
|
lineOpts = opts_add(lineOpts, 'draw', 'none');
|
|
lineOpts = opts_add(lineOpts, 'only marks');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function list = configureSemanticLineWidths(semanticLineWidths)
|
|
% Defines the default semantic options of pgfplots and updates it when applicable
|
|
|
|
if isnan(semanticLineWidths)
|
|
% Remove the list
|
|
list = {};
|
|
return;
|
|
end
|
|
|
|
% Pgf/TikZ defaults (see pgfmanual 3.0.1a section 15.3.1 / page 166)
|
|
list = {'ultra thin', 0.1;
|
|
'very thin', 0.2;
|
|
'thin', 0.4;
|
|
'semithick', 0.6;
|
|
'thick', 0.8;
|
|
'very thick', 1.2;
|
|
'ultra thick', 1.6 };
|
|
|
|
% Update defaults or append the user provided setting
|
|
for ii = 1:size(semanticLineWidths, 1)
|
|
% Check for redefinitions of defaults
|
|
[isOverride, idx] = ismember(semanticLineWidths{ii, 1}, list{:, 1})
|
|
if isOverride
|
|
list{idx, 2} = semanticLineWidths{ii, 2};
|
|
else
|
|
list{end+1} = semanticLineWidths{ii, :};
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, drawOptions] = getMarkerOptions(m2t, h)
|
|
% Handles the marker properties of a line (or any other) plot.
|
|
drawOptions = opts_new();
|
|
|
|
marker = getOrDefault(h, 'Marker', 'none');
|
|
|
|
if ~isNone(marker)
|
|
markerSize = get(h, 'MarkerSize');
|
|
lineStyle = get(h, 'LineStyle');
|
|
lineWidth = get(h, 'LineWidth');
|
|
|
|
[tikzMarkerSize, isDefault] = ...
|
|
translateMarkerSize(m2t, marker, markerSize);
|
|
|
|
% take over the marker size in any case when in strict mode;
|
|
% if not, don't add anything in case of default marker size
|
|
% and effectively take Pgfplots' default.
|
|
if m2t.args.strict || ~isDefault
|
|
drawOptions = opts_add(drawOptions, 'mark size', ...
|
|
sprintf('%.1fpt', tikzMarkerSize));
|
|
end
|
|
|
|
markOptions = opts_new();
|
|
% make sure that the markers get painted in solid (and not dashed)
|
|
% if the 'lineStyle' is not solid (otherwise there is no problem)
|
|
if ~strcmpi(lineStyle, 'solid')
|
|
markOptions = opts_add(markOptions, 'solid');
|
|
end
|
|
|
|
% get the marker color right
|
|
markerInfo = getMarkerInfo(m2t, h, markOptions);
|
|
|
|
[m2t, markerInfo.options] = setColor(m2t, h, markerInfo.options, 'fill', markerInfo.FaceColor);
|
|
|
|
if ~strcmpi(markerInfo.EdgeColor,'auto')
|
|
[m2t, markerInfo.options] = setColor(m2t, h, markerInfo.options, 'draw', markerInfo.EdgeColor);
|
|
else
|
|
if isprop(h,'EdgeColor')
|
|
color = get(h, 'EdgeColor');
|
|
else
|
|
color = get(h, 'Color');
|
|
end
|
|
[m2t, markerInfo.options] = setColor(m2t, h, markerInfo.options, '', color);
|
|
end
|
|
|
|
% add it all to drawOptions
|
|
drawOptions = opts_add(drawOptions, 'mark', markerInfo.tikz);
|
|
|
|
if ~isempty(markOptions)
|
|
drawOptions = opts_addSubOpts(drawOptions, 'mark options', ...
|
|
markerInfo.options);
|
|
end
|
|
end
|
|
|
|
type = getOrDefault(h, 'Type', 'none');
|
|
if strcmp(type, 'errorbar')
|
|
%'capSize' -> errorbar marker size
|
|
capSize = getOrDefault(h, 'CapSize',get(h,'MarkerSize'));
|
|
lineWidth = get(h, 'LineWidth');
|
|
|
|
errStyleOptions = opts_new();
|
|
errStyleOptions = opts_add(errStyleOptions, 'line width',...
|
|
sprintf('%.1fpt', lineWidth));
|
|
|
|
drawOptions = opts_addSubOpts(drawOptions, ...
|
|
'error bar style', errStyleOptions);
|
|
|
|
errMarkOptions = opts_new();
|
|
errMarkOptions = opts_add(errMarkOptions, 'line width',...
|
|
sprintf('%.1fpt', lineWidth));
|
|
errMarkOptions = opts_add(errMarkOptions, 'mark size',...
|
|
sprintf('%.1fpt', capSize));
|
|
errMarkOptions = opts_add(errMarkOptions, 'rotate', '90');
|
|
|
|
drawOptions = opts_addSubOpts(drawOptions, ...
|
|
'error mark options', errMarkOptions);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [tikzMarkerSize, isDefault] = ...
|
|
translateMarkerSize(m2t, matlabMarker, matlabMarkerSize)
|
|
% The markersizes of Matlab and TikZ are related, but not equal. This
|
|
% is because
|
|
%
|
|
% 1.) MATLAB uses the MarkerSize property to describe something like
|
|
% the diameter of the mark, while TikZ refers to the 'radius',
|
|
% 2.) MATLAB and TikZ take different measures (e.g. the
|
|
% edge of a square vs. its diagonal).
|
|
if(~ischar(matlabMarker))
|
|
error('matlab2tikz:translateMarkerSize', ...
|
|
'Variable matlabMarker is not a string.');
|
|
end
|
|
|
|
if(~isnumeric(matlabMarkerSize))
|
|
error('matlab2tikz:translateMarkerSize', ...
|
|
'Variable matlabMarkerSize is not a numeral.');
|
|
end
|
|
|
|
% 6pt is the default MATLAB marker size for all markers
|
|
defaultMatlabMarkerSize = 6;
|
|
isDefault = abs(matlabMarkerSize(1)-defaultMatlabMarkerSize)<m2t.tol;
|
|
% matlabMarkerSize can be vector data, use first index to check the default
|
|
% marker size. When the script also handles different markers together with
|
|
% changing size and color, the test should be extended to a vector norm, e.g.
|
|
% sqrt(e^T*e) < tol, where e=matlabMarkerSize-defaultMatlabMarkerSize
|
|
|
|
switch (matlabMarker)
|
|
case 'none'
|
|
tikzMarkerSize = [];
|
|
case {'+','o','x','*','p','pentagram','h','hexagram'}
|
|
% In MATLAB, the marker size refers to the edge length of a
|
|
% square (for example) (~diameter), whereas in TikZ the
|
|
% distance of an edge to the center is the measure (~radius).
|
|
% Hence divide by 2.
|
|
tikzMarkerSize = matlabMarkerSize(:) / 2;
|
|
case '.'
|
|
% as documented on the Matlab help pages:
|
|
%
|
|
% Note that MATLAB draws the point marker (specified by the '.'
|
|
% symbol) at one-third the specified size.
|
|
% The point (.) marker type does not change size when the
|
|
% specified value is less than 5.
|
|
%
|
|
tikzMarkerSize = matlabMarkerSize(:) / 2 / 3;
|
|
case {'s','square'}
|
|
% Matlab measures the diameter, TikZ half the edge length
|
|
tikzMarkerSize = matlabMarkerSize(:) / 2 / sqrt(2);
|
|
case {'d','diamond'}
|
|
% MATLAB measures the width, TikZ the height of the diamond;
|
|
% the acute angle (at the top and the bottom of the diamond)
|
|
% is a manually measured 75 degrees (in TikZ, and MATLAB
|
|
% probably very similar); use this as a base for calculations
|
|
tikzMarkerSize = matlabMarkerSize(:) / 2 / atan(75/2 *pi/180);
|
|
case {'^','v','<','>'}
|
|
% for triangles, matlab takes the height
|
|
% and tikz the circumcircle radius;
|
|
% the triangles are always equiangular
|
|
tikzMarkerSize = matlabMarkerSize(:) / 2 * (2/3);
|
|
otherwise
|
|
error('matlab2tikz:translateMarkerSize', ...
|
|
'Unknown matlabMarker ''%s''.', matlabMarker);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [tikzMarker, markOptions] = ...
|
|
translateMarker(m2t, matlabMarker, markOptions, faceColorToggle)
|
|
% Translates MATLAB markers to their Tikz equivalents
|
|
% #COMPLEX: inherently large switch-case
|
|
if ~ischar(matlabMarker)
|
|
error('matlab2tikz:translateMarker:MarkerNotAString',...
|
|
'matlabMarker is not a string.');
|
|
end
|
|
|
|
switch (matlabMarker)
|
|
case 'none'
|
|
tikzMarker = '';
|
|
case '+'
|
|
tikzMarker = '+';
|
|
case 'o'
|
|
if faceColorToggle
|
|
tikzMarker = '*';
|
|
else
|
|
tikzMarker = 'o';
|
|
end
|
|
case '.'
|
|
tikzMarker = '*';
|
|
case 'x'
|
|
tikzMarker = 'x';
|
|
otherwise % the following markers are only available with PGF's
|
|
% plotmarks library
|
|
signalDependency(m2t, 'tikzlibrary', 'plotmarks');
|
|
hasFilledVariant = true;
|
|
switch (matlabMarker)
|
|
|
|
case '*'
|
|
tikzMarker = 'asterisk';
|
|
hasFilledVariant = false;
|
|
|
|
case {'s','square'}
|
|
tikzMarker = 'square';
|
|
|
|
case {'d','diamond'}
|
|
tikzMarker = 'diamond';
|
|
|
|
case '^'
|
|
tikzMarker = 'triangle';
|
|
|
|
case 'v'
|
|
tikzMarker = 'triangle';
|
|
markOptions = opts_add(markOptions, 'rotate', '180');
|
|
|
|
case '<'
|
|
tikzMarker = 'triangle';
|
|
markOptions = opts_add(markOptions, 'rotate', '90');
|
|
|
|
case '>'
|
|
tikzMarker = 'triangle';
|
|
markOptions = opts_add(markOptions, 'rotate', '270');
|
|
|
|
case {'p','pentagram'}
|
|
tikzMarker = 'star';
|
|
|
|
case {'h','hexagram'}
|
|
userWarning(m2t, 'MATLAB''s marker ''hexagram'' not available in TikZ. Replacing by ''star''.');
|
|
tikzMarker = 'star';
|
|
|
|
otherwise
|
|
error('matlab2tikz:translateMarker:unknownMatlabMarker',...
|
|
'Unknown matlabMarker ''%s''.',matlabMarker);
|
|
end
|
|
if faceColorToggle && hasFilledVariant
|
|
tikzMarker = [tikzMarker '*'];
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawConstantLine(m2t, h, custom)
|
|
% Draws a 'constantline' object such as those produced by 'yline()'
|
|
|
|
interceptaxis = get(h, 'InterceptAxis');
|
|
value = get(h, 'Value');
|
|
xLim = get(h.Parent, 'XLim');
|
|
yLim = get(h.Parent, 'Ylim');
|
|
|
|
% create line from object properties
|
|
switch interceptaxis
|
|
case 'x'
|
|
xdata = [value value];
|
|
ydata = yLim;
|
|
case 'y'
|
|
xdata = xLim;
|
|
ydata = [value value];
|
|
otherwise
|
|
warning('ConstantLine: invalid InterceptAxis. Ignoring...')
|
|
return
|
|
end
|
|
l = line(xdata, ydata);
|
|
|
|
% Pass on color and line options
|
|
l.Color = h.Color;
|
|
l.LineStyle = h.LineStyle;
|
|
l.LineWidth = h.LineWidth;
|
|
|
|
[m2t, str] = drawLine(m2t, l, custom);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawPatch(m2t, handle, custom)
|
|
% Draws a 'patch' graphics object (as found in contourf plots, for example).
|
|
%
|
|
str = '';
|
|
if ~isVisible(handle)
|
|
return
|
|
end
|
|
|
|
% This is for a quirky workaround for stacked bar plots.
|
|
m2t.axes{end}.nonbarPlotsPresent = true;
|
|
|
|
% Each row of the faces matrix represents a distinct patch
|
|
% NOTE: pgfplot uses zero-based indexing into vertices and interpolates
|
|
% counter-clockwise
|
|
Faces = get(handle,'Faces')-1;
|
|
Vertices = get(handle,'Vertices');
|
|
|
|
% 3D vs 2D
|
|
is3D = m2t.axes{end}.is3D;
|
|
if is3D
|
|
columnNames = {'x', 'y', 'z'};
|
|
plotCmd = 'addplot3';
|
|
Vertices = applyHgTransform(m2t, Vertices);
|
|
else
|
|
columnNames = {'x', 'y'};
|
|
plotCmd = 'addplot';
|
|
Vertices = Vertices(:,1:2);
|
|
end
|
|
|
|
% Process fill, edge colors and shader
|
|
[m2t,patchOptions, s] = shaderOpts(m2t,handle,'patch');
|
|
|
|
% Return empty axes if no face or edge colors
|
|
if isNone(s.plotType)
|
|
return
|
|
end
|
|
|
|
% -----------------------------------------------------------------------
|
|
% gather the draw options
|
|
% Make sure that legends are shown in area mode.
|
|
drawOptions = opts_add(opts_new,'area legend');
|
|
verticesTableOptions = opts_new();
|
|
|
|
% Marker options
|
|
[m2t, markerOptions] = getMarkerOptions(m2t, handle);
|
|
drawOptions = opts_merge(drawOptions, markerOptions);
|
|
|
|
% Line options
|
|
[m2t, lineOptions] = getLineOptions(m2t, handle);
|
|
drawOptions = opts_merge(drawOptions, lineOptions);
|
|
|
|
% If the line is not visible, set edgeColor to none. Otherwise pgfplots
|
|
% draws it by default
|
|
if ~isLineVisible(handle)
|
|
s.edgeColor = 'none';
|
|
end
|
|
|
|
% No patch: if one patch and single face/edge color
|
|
isFaceColorFlat = isempty(strfind(opts_get(patchOptions, 'shader'),'interp'));
|
|
if size(Faces,1) == 1 && s.hasOneEdgeColor && isFaceColorFlat
|
|
ptType = '';
|
|
cycle = conditionallyCyclePath(Vertices);
|
|
|
|
[m2t, drawOptions] = setColor(m2t, handle, drawOptions, 'draw', ...
|
|
s.edgeColor, 'none');
|
|
[m2t, drawOptions] = setColor(m2t, handle, drawOptions, 'fill', ...
|
|
s.faceColor);
|
|
|
|
[drawOptions] = opts_copy(patchOptions, 'draw opacity', drawOptions);
|
|
[drawOptions] = opts_copy(patchOptions, 'fill opacity', drawOptions);
|
|
|
|
else % Multiple patches
|
|
|
|
% Patch table type
|
|
ptType = 'patch table';
|
|
cycle = '';
|
|
drawOptions = opts_add(drawOptions,'table/row sep','crcr');
|
|
% TODO: is the above "crcr" compatible with pgfplots 1.12 ?
|
|
% TODO: is a "patch table" externalizable?
|
|
|
|
% Enforce 'patch' or cannot use 'patch table='
|
|
if strcmpi(s.plotType,'mesh')
|
|
drawOptions = opts_add(drawOptions,'patch');
|
|
end
|
|
drawOptions = opts_add(drawOptions,s.plotType); % Eventually add mesh, but after patch!
|
|
|
|
drawOptions = getPatchShape(m2t, handle, drawOptions, patchOptions);
|
|
|
|
[m2t, drawOptions, Vertices, Faces, verticesTableOptions, ptType, ...
|
|
columnNames] = setColorsOfPatches(m2t, handle, drawOptions, ...
|
|
Vertices, Faces, verticesTableOptions, ptType, columnNames, ...
|
|
isFaceColorFlat, s);
|
|
end
|
|
|
|
drawOptions = maybeShowInLegend(m2t.currentHandleHasLegend, drawOptions);
|
|
m2t = jumpAtUnboundCoords(m2t, Faces(:));
|
|
|
|
% Add Faces table
|
|
if ~isempty(ptType)
|
|
[m2t, facesTable] = makeTable(m2t, repmat({''},1,size(Faces,2)), Faces);
|
|
drawOptions = opts_add(drawOptions, ptType, sprintf('{%s}', facesTable));
|
|
end
|
|
|
|
% Plot the actual data.
|
|
[m2t, verticesTable, tableOptions] = makeTable(m2t, columnNames, Vertices);
|
|
tableOptions = opts_merge(tableOptions, verticesTableOptions);
|
|
|
|
% Add custom options
|
|
drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
|
|
|
|
% Print out
|
|
drawOpts = opts_print(drawOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\n\\%s[%s]\ntable[%s] {%s}%s;\n',...
|
|
plotCmd, drawOpts, tabOpts, verticesTable, cycle);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, drawOptions, Vertices, Faces, verticesTableOptions, ptType, ...
|
|
columnNames] = setColorsOfPatches(m2t, handle, drawOptions, ...
|
|
Vertices, Faces, verticesTableOptions, ptType, columnNames, isFaceColorFlat, s)
|
|
% this behemoth does the color setting for patches
|
|
|
|
% TODO: this function can probably be split further, just look at all those
|
|
% parameters being passed.
|
|
|
|
fvCData = get(handle,'FaceVertexCData');
|
|
rowsCData = size(fvCData,1);
|
|
|
|
% We have CData for either all faces or vertices
|
|
if rowsCData > 1
|
|
|
|
% Add the color map
|
|
m2t = m2t_addAxisOption(m2t, matlab2pgfplotsColormap(m2t, m2t.current.colormap));
|
|
|
|
% Determine if mapping is direct or scaled
|
|
CDataMapping = get(handle,'CDataMapping');
|
|
if strcmpi(CDataMapping, 'direct')
|
|
drawOptions = opts_add(drawOptions, 'colormap access','direct');
|
|
end
|
|
|
|
% Switch to face CData if not using interpolated shader
|
|
isVerticesCData = rowsCData == size(Vertices,1);
|
|
if isFaceColorFlat && isVerticesCData
|
|
% Take first vertex color (see FaceColor in Patch Properties)
|
|
fvCData = fvCData(Faces(:,1)+ 1,:);
|
|
rowsCData = size(fvCData,1);
|
|
isVerticesCData = false;
|
|
end
|
|
|
|
% Point meta as true color CData, i.e. RGB in [0,1]
|
|
if size(fvCData,2) == 3
|
|
% Create additional custom colormap
|
|
m2t.axes{end}.options(end+1,:) = ...
|
|
{matlab2pgfplotsColormap(m2t, fvCData, 'patchmap'), []};
|
|
drawOptions = opts_append(drawOptions, 'colormap name','patchmap');
|
|
|
|
% Index into custom colormap
|
|
fvCData = (0:rowsCData-1)';
|
|
end
|
|
|
|
% Add pointmeta data to vertices or faces
|
|
if isVerticesCData
|
|
columnNames{end+1} = 'c';
|
|
verticesTableOptions = opts_add(verticesTableOptions, 'point meta','\thisrow{c}');
|
|
Vertices = [Vertices, fvCData];
|
|
else
|
|
ptType = 'patch table with point meta';
|
|
Faces = [Faces fvCData];
|
|
end
|
|
|
|
else
|
|
% Scalar FaceVertexCData, i.e. one color mapping for all patches,
|
|
% used e.g. by Octave in drawing barseries
|
|
|
|
[m2t,xFaceColor] = getColor(m2t, handle, s.faceColor, 'patch');
|
|
drawOptions = opts_add(drawOptions, 'fill', xFaceColor);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [drawOptions] = maybeShowInLegend(showInLegend, drawOptions)
|
|
% sets the appropriate options to show/hide the plot in the legend
|
|
if ~showInLegend
|
|
% No legend entry found. Don't include plot in legend.
|
|
drawOptions = opts_add(drawOptions, 'forget plot');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, options] = setColor(m2t, handle, options, property, color, noneValue)
|
|
% assigns the MATLAB color of the object identified by "handle" to the LaTeX
|
|
% property stored in the options array. An optional "noneValue" can be provided
|
|
% that is set when the color == 'none' (if it is omitted, the property will not
|
|
% be set).
|
|
% TODO: probably this should be integrated with getAndCheckDefault etc.
|
|
if opts_has(options,property) && isNone(opts_get(options,property))
|
|
return
|
|
end
|
|
if ~isNone(color)
|
|
[m2t, xcolor] = getColor(m2t, handle, color, 'patch');
|
|
if ~isempty(xcolor)
|
|
% this may happen when color == 'flat' and CData is Nx3, e.g. in
|
|
% scatter plot or in patches
|
|
if isempty(property)
|
|
options = opts_add(options, xcolor);
|
|
else
|
|
options = opts_add(options, property, xcolor);
|
|
end
|
|
end
|
|
else
|
|
if exist('noneValue','var')
|
|
options = opts_add(options, property, noneValue);
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function drawOptions = getPatchShape(m2t, h, drawOptions, patchOptions)
|
|
% Retrieves the shape options (i.e. number of vertices) of patch objects
|
|
% Depending on the number of vertices, patches can be triangular, rectangular
|
|
% or polygonal
|
|
% See pgfplots 1.12 manual section 5.8.1 "Additional Patch Types" and the
|
|
% patchplots library
|
|
vertexCount = size(get(h, 'Faces'), 2);
|
|
|
|
switch vertexCount
|
|
case 3 % triangle (default)
|
|
% do nothing special
|
|
|
|
case 4 % rectangle
|
|
drawOptions = opts_add(drawOptions,'patch type', 'rectangle');
|
|
|
|
otherwise % generic polygon
|
|
userInfo(m2t, '\nMake sure to load \\usepgfplotslibrary{patchplots} in the preamble.\n');
|
|
|
|
% Default interpolated shader,not supported by polygon, to faceted
|
|
isFaceColorFlat = isempty(strfind(opts_get(patchOptions, 'shader'),'interp'));
|
|
if ~isFaceColorFlat
|
|
% NOTE: check if pgfplots supports this (or specify version)
|
|
userInfo(m2t, '\nPgfplots does not support interpolation for polygons.\n Use patches with at most 4 vertices.\n');
|
|
patchOptions = opts_remove(patchOptions, 'shader');
|
|
patchOptions = opts_add(patchOptions, 'shader', 'faceted');
|
|
end
|
|
|
|
% Add draw options
|
|
drawOptions = opts_add(drawOptions, 'patch type', 'polygon');
|
|
drawOptions = opts_add(drawOptions, 'vertex count', ...
|
|
sprintf('%d', vertexCount));
|
|
end
|
|
|
|
drawOptions = opts_merge(drawOptions, patchOptions);
|
|
end
|
|
% ==============================================================================
|
|
function [cycle] = conditionallyCyclePath(data)
|
|
% returns "--cycle" when the path should be cyclic in pgfplots
|
|
% Mostly, this is the case UNLESS the data record starts or ends with a NaN
|
|
% record (i.e. a break in the path)
|
|
if any(~isfinite(data([1 end],:)))
|
|
cycle = '';
|
|
else
|
|
cycle = '--cycle';
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function m2t = jumpAtUnboundCoords(m2t, data)
|
|
% signals the axis to allow discontinuities in the plot at unbounded
|
|
% coordinates (i.e. Inf and NaN).
|
|
% See also pgfplots 1.12 manual section 4.5.13 "Interrupted Plots".
|
|
if any(~isfinite(data(:)))
|
|
m2t = needsPgfplotsVersion(m2t, [1 4]);
|
|
m2t = m2t_addAxisOption(m2t, 'unbounded coords', 'jump');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawImage(m2t, handle, custom)
|
|
str = '';
|
|
if ~isVisible(handle)
|
|
return
|
|
end
|
|
|
|
% read x-, y-, and color-data
|
|
xData = get(handle, 'XData');
|
|
yData = get(handle, 'YData');
|
|
cData = get(handle, 'CData');
|
|
|
|
if isempty(cData)
|
|
return
|
|
end
|
|
|
|
if (m2t.args.imagesAsPng)
|
|
[m2t, str] = imageAsPNG(m2t, handle, xData, yData, cData, custom);
|
|
else
|
|
[m2t, str] = imageAsTikZ(m2t, handle, xData, yData, cData, custom);
|
|
end
|
|
|
|
% Make sure that the axes are still visible above the image.
|
|
m2t = m2t_addAxisOption(m2t, 'axis on top');
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = imageAsPNG(m2t, handle, xData, yData, cData, custom)
|
|
[m2t, fileNum] = incrementGlobalCounter(m2t, 'pngFile');
|
|
% ------------------------------------------------------------------------
|
|
% draw a png image
|
|
[pngFileName, pngReferencePath] = externalFilename(m2t, fileNum, '.png');
|
|
|
|
% Get color indices for indexed images and truecolor values otherwise
|
|
if ndims(cData) == 2 %#ok don't use ismatrix (cfr. #143)
|
|
[m2t, colorData] = cdata2colorindex(m2t, cData, handle);
|
|
else
|
|
colorData = cData;
|
|
end
|
|
|
|
m = size(cData, 1);
|
|
n = size(cData, 2);
|
|
|
|
alphaData = normalizedAlphaValues(m2t, get(handle,'AlphaData'), handle);
|
|
if numel(alphaData) == 1
|
|
alphaData = alphaData(ones(size(colorData(:,:,1))));
|
|
end
|
|
[colorData, alphaData] = flipImageIfAxesReversed(m2t, colorData, alphaData);
|
|
|
|
% Write an indexed or a truecolor image
|
|
hasAlpha = true;
|
|
if isfloat(alphaData) && all(alphaData(:) == 1)
|
|
alphaOpts = {};
|
|
hasAlpha = false;
|
|
else
|
|
alphaOpts = {'Alpha', double(alphaData)};
|
|
end
|
|
if (ndims(colorData) == 2) %#ok don't use ismatrix (cfr. #143)
|
|
if size(m2t.current.colormap, 1) <= 256 && ~hasAlpha
|
|
% imwrite supports maximum 256 values in a colormap (i.e. 8 bit)
|
|
% and no alpha channel for indexed PNG images.
|
|
imwrite(colorData, m2t.current.colormap, ...
|
|
pngFileName, 'png');
|
|
else % use true-color instead
|
|
imwrite(ind2rgb(colorData, m2t.current.colormap), ...
|
|
pngFileName, 'png', alphaOpts{:});
|
|
end
|
|
else
|
|
imwrite(colorData, pngFileName, 'png', alphaOpts{:});
|
|
end
|
|
% -----------------------------------------------------------------------
|
|
% dimensions of a pixel in axes units
|
|
if n == 1
|
|
xLim = get(m2t.current.gca, 'XLim');
|
|
xw = xLim(2) - xLim(1);
|
|
else
|
|
xw = (xData(end)-xData(1)) / (n-1);
|
|
end
|
|
if m == 1
|
|
yLim = get(m2t.current.gca, 'YLim');
|
|
yw = yLim(2) - yLim(1);
|
|
else
|
|
yw = (yData(end)-yData(1)) / (m-1);
|
|
end
|
|
|
|
opts = opts_new();
|
|
opts = opts_add(opts, 'xmin', sprintf(m2t.ff, xData(1 ) - xw/2));
|
|
opts = opts_add(opts, 'xmax', sprintf(m2t.ff, xData(end) + xw/2));
|
|
opts = opts_add(opts, 'ymin', sprintf(m2t.ff, yData(1 ) - yw/2));
|
|
opts = opts_add(opts, 'ymax', sprintf(m2t.ff, yData(end) + yw/2));
|
|
|
|
opts = opts_append_userdefined(opts, custom.extraOptions);
|
|
|
|
% Print out
|
|
drawOpts = opts_print(opts);
|
|
str = sprintf('\\addplot [forget plot] graphics [%s] {%s};\n', ...
|
|
drawOpts, pngReferencePath);
|
|
|
|
userInfo(m2t, ...
|
|
['\nA PNG file is stored at ''%s'' for which\n', ...
|
|
'the TikZ file contains a reference to ''%s''.\n', ...
|
|
'You may need to adapt this, depending on the relative\n', ...
|
|
'locations of the master TeX file and the included TikZ file.\n'], ...
|
|
pngFileName, pngReferencePath);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = imageAsTikZ(m2t, handle, xData, yData, cData, custom)
|
|
% writes an image as raw TikZ commands (STRONGLY DISCOURAGED)
|
|
|
|
% set up cData
|
|
if ndims(cData) == 3
|
|
cData = cData(end:-1:1,:,:);
|
|
else
|
|
cData = cData(end:-1:1,:);
|
|
end
|
|
|
|
% Generate uniformly distributed X, Y, although xData and yData may be
|
|
% non-uniform.
|
|
% This is MATLAB(R) behavior.
|
|
[X, hX] = constructUniformXYDataForImage(xData, size(cData, 2));
|
|
[Y, hY] = constructUniformXYDataForImage(yData, size(cData, 1));
|
|
[m2t, xcolor] = getColor(m2t, handle, cData, 'image');
|
|
|
|
% The following section takes pretty long to execute, although in
|
|
% principle it is discouraged to use TikZ for those; LaTeX will take
|
|
% forever to compile.
|
|
% Still, a bug has been filed on MathWorks to allow for one-line
|
|
% sprintf'ing with (string+num) cells (Request ID: 1-9WHK4W);
|
|
% <http://www.mathworks.de/support/service_requests/Service_Request_Detail.do?ID=183481&filter=&sort=&statusorder=0&dateorder=0>.
|
|
% An alternative approach could be to use 'surf' or 'patch' of pgfplots
|
|
% with inline tables.
|
|
str = '';
|
|
m = length(X);
|
|
n = length(Y);
|
|
imageString = cell(1, m);
|
|
for i = 1:m
|
|
subString = cell(1, n);
|
|
for j = 1:n
|
|
opts = opts_new();
|
|
opts = opts_add(opts, xcolor{n-j+1, i});
|
|
opts = opts_append_userdefined(opts, custom.extraOptions);
|
|
subString{j} = sprintf(['\t\\fill [%s] ', ...
|
|
'(axis cs:', m2t.ff, ',', m2t.ff, ') rectangle ', ...
|
|
'(axis cs:', m2t.ff, ',', m2t.ff, ');\n'], ...
|
|
opts_print(opts), ...
|
|
X(i)-hX/2, Y(j)-hY/2, ...
|
|
X(i)+hX/2, Y(j)+hY/2);
|
|
end
|
|
imageString{i} = join(m2t, subString, '');
|
|
end
|
|
str = join(m2t, [str, imageString], '');
|
|
end
|
|
function [XY, delta] = constructUniformXYDataForImage(XYData, expectedLength)
|
|
% Generate uniformly distributed X, Y, although xData/yData may be
|
|
% non-uniform. Dimension indicates the corresponding dimension in the cData matrix.
|
|
switch length(XYData)
|
|
case 2 % only the limits given; common for generic image plots
|
|
delta = 1;
|
|
case expectedLength % specific x/y-data is given
|
|
delta = (XYData(end)-XYData(1)) / (length(XYData)-1);
|
|
otherwise
|
|
error('drawImage:arrayLengthMismatch', ...
|
|
'CData length (%d) does not match X/YData length (%d).', ...
|
|
expectedLength, length(XYData));
|
|
end
|
|
XY = XYData(1):delta:XYData(end);
|
|
end
|
|
% ==============================================================================
|
|
function [colorData, alphaData] = flipImageIfAxesReversed(m2t, colorData, alphaData)
|
|
% flip the image if reversed
|
|
if m2t.xAxisReversed
|
|
colorData = colorData(:, end:-1:1, :);
|
|
alphaData = alphaData(:, end:-1:1);
|
|
end
|
|
if ~m2t.yAxisReversed % y-axis direction is reversed normally for images, flip otherwise
|
|
colorData = colorData(end:-1:1, :, :);
|
|
alphaData = alphaData(end:-1:1, :);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function alpha = normalizedAlphaValues(m2t, alpha, handle)
|
|
alphaDataMapping = getOrDefault(handle, 'AlphaDataMapping', 'none');
|
|
switch lower(alphaDataMapping)
|
|
case 'none' % no rescaling needed
|
|
case 'scaled'
|
|
ALim = get(m2t.current.gca, 'ALim');
|
|
AMax = ALim(2);
|
|
AMin = ALim(1);
|
|
if ~isfinite(AMax)
|
|
AMax = max(alpha(:)); %NOTE: is this right?
|
|
end
|
|
alpha = (alpha - AMin)./(AMax - AMin);
|
|
case 'direct'
|
|
alpha = ind2rgb(alpha, get(m2t.current.gcf, 'Alphamap'));
|
|
otherwise
|
|
error('matlab2tikz:UnknownAlphaMapping', ...
|
|
'Unknown alpha mapping "%s"', alphaMapping);
|
|
end
|
|
|
|
if isfloat(alpha) %important, alpha data can have integer type which should not be scaled
|
|
alpha = min(1,max(alpha,0)); % clip at range [0, 1]
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawContour(m2t, h, custom)
|
|
if isHG2()
|
|
[m2t, str] = drawContourHG2(m2t, h, custom);
|
|
else
|
|
% Save legend state for the contour group
|
|
hasLegend = m2t.currentHandleHasLegend;
|
|
|
|
% Plot children patches
|
|
children = allchild(h);
|
|
N = numel(children);
|
|
str = cell(N,1);
|
|
for ii = 1:N
|
|
% Plot in reverse order
|
|
child = children(N-ii+1);
|
|
isContourLabel = strcmpi(get(child,'type'),'text');
|
|
if isContourLabel
|
|
[m2t, str{ii}] = handleObject(m2t, child, @drawText);
|
|
else
|
|
[m2t, str{ii}] = handleObject(m2t, child, @drawPatch);
|
|
end
|
|
|
|
% Only first child can be in the legend
|
|
m2t.currentHandleHasLegend = false;
|
|
end
|
|
str = strcat(str,sprintf('\n'));
|
|
str = [str{:}];
|
|
|
|
% Restore group's legend state
|
|
m2t.currentHandleHasLegend = hasLegend;
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawContourHG2(m2t, h, custom)
|
|
str = '';
|
|
if ~isVisible(h)
|
|
return
|
|
end
|
|
|
|
% Retrieve ContourMatrix
|
|
contours = get(h,'ContourMatrix')';
|
|
[istart, nrows] = findStartOfContourData(contours);
|
|
|
|
% Scale negative contours one level down (for proper coloring)
|
|
Levels = contours(istart,1);
|
|
LevelList = get(h,'LevelList');
|
|
ineg = Levels < 0;
|
|
if any(ineg) && min(LevelList) < min(Levels)
|
|
[idx,pos] = ismember(Levels, LevelList);
|
|
idx = idx & ineg;
|
|
contours(istart(idx)) = LevelList(pos(idx)-1);
|
|
end
|
|
|
|
% Draw a contour group (MATLAB R2014b and newer only)
|
|
isFilled = isOn(get(h,'Fill'));
|
|
if isFilled
|
|
[m2t, str] = drawFilledContours(m2t, h, contours, istart, nrows, custom);
|
|
else
|
|
% Add colormap
|
|
cmap = m2t.current.colormap;
|
|
m2t = m2t_addAxisOption(m2t, matlab2pgfplotsColormap(m2t, cmap));
|
|
|
|
% Contour table in Matlab format
|
|
plotOptions = opts_new();
|
|
plotOptions = opts_add(plotOptions,'contour prepared');
|
|
plotOptions = opts_add(plotOptions,'contour prepared format','matlab');
|
|
|
|
% Labels
|
|
if isOff(get(h,'ShowText'))
|
|
plotOptions = opts_add(plotOptions,'contour/labels','false');
|
|
end
|
|
|
|
% Get line properties
|
|
[m2t, lineOptions] = getLineOptions(m2t, h);
|
|
|
|
% Check for special color settings
|
|
[lineColor, isDefaultColor] = getAndCheckDefault('contour', h, 'LineColor', 'flat');
|
|
if ~isDefaultColor
|
|
[m2t, lineOptions] = setColor(m2t, h, lineOptions, 'contour/draw color', lineColor, 'none');
|
|
end
|
|
|
|
% Merge the line options with the contour plot options
|
|
plotOptions = opts_merge(plotOptions, lineOptions);
|
|
plotOptions = opts_append_userdefined(plotOptions, custom.extraOptions);
|
|
|
|
% Make contour table
|
|
[m2t, table, tableOptions] = makeTable(m2t, {'',''}, contours);
|
|
|
|
% Print out
|
|
plotOpts = opts_print(plotOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\addplot[%s] table[%s] {%%\n%s};\n', ...
|
|
plotOpts, tabOpts, table);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [istart, nrows] = findStartOfContourData(contours)
|
|
% Index beginning of contour data (see contourc.m for details)
|
|
nrows = size(contours,1);
|
|
istart = false(nrows,1);
|
|
pos = 1;
|
|
while pos < nrows
|
|
istart(pos) = true;
|
|
pos = pos + contours(pos, 2) + 1;
|
|
end
|
|
istart = find(istart);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawFilledContours(m2t, h, contours, istart, nrows, custom)
|
|
% Loop each contour and plot a filled region
|
|
%
|
|
% NOTE:
|
|
% - we cannot plot from inner to outer contour since the last
|
|
% filled area will cover the inner regions. Therefore, we need to
|
|
% invert the plotting order in those cases.
|
|
% - we need to distinguish between contour groups. A group is
|
|
% defined by inclusion, i.e. its members are contained within one
|
|
% outer contour. The outer contours of two groups cannot include
|
|
% each other.
|
|
% FIXME: extract this logic to a function
|
|
str = '';
|
|
if ~isVisible(h)
|
|
return
|
|
end
|
|
|
|
% Split contours in cell array
|
|
cellcont = mat2cell(contours, diff([istart; nrows+1]));
|
|
ncont = numel(cellcont);
|
|
|
|
% Determine contour groups and the plotting order.
|
|
% The ContourMatrix lists the contours in ascending order by level.
|
|
% Hence, if the lowest (first) contour contains any others, then the
|
|
% group will be a peak. Otherwise, the group will be a valley, and
|
|
% the contours will have to be plotted in reverse order, i.e. from
|
|
% highest (largest) to lowest (narrowest).
|
|
|
|
%FIXME: close the contours over the border of the domain, see #723.
|
|
order = NaN(ncont,1);
|
|
ifree = true(ncont,1);
|
|
from = 1;
|
|
while any(ifree)
|
|
% Select peer with lowest level among the free contours, i.e.
|
|
% those which do not belong to any group yet
|
|
pospeer = find(ifree,1,'first');
|
|
peer = cellcont{pospeer};
|
|
igroup = false(ncont,1);
|
|
|
|
% Loop through all contours
|
|
for ii = 1:numel(cellcont)
|
|
if ~ifree(ii), continue, end
|
|
|
|
curr = cellcont{ii};
|
|
% Current contour contained in the peer
|
|
if inpolygon(curr(2,1),curr(2,2), peer(2:end,1),peer(2:end,2))
|
|
igroup(ii) = true;
|
|
isinverse = false;
|
|
% Peer contained in the current
|
|
elseif inpolygon(peer(2,1),peer(2,2),curr(2:end,1),curr(2:end,2))
|
|
igroup(ii) = true;
|
|
isinverse = true;
|
|
end
|
|
end
|
|
% Order members of group according to the inclusion principle
|
|
nmembers = nnz(igroup ~= 0);
|
|
if isinverse
|
|
order(igroup) = nmembers+from-1:-1:from;
|
|
else
|
|
order(igroup) = from:nmembers+from-1;
|
|
end
|
|
|
|
% Continue numbering
|
|
from = from + nmembers;
|
|
ifree = ifree & ~igroup;
|
|
end
|
|
|
|
% Reorder the contours
|
|
cellcont(order,1) = cellcont;
|
|
|
|
% Replace same level contours with hole, i.e. one level down
|
|
Levels = contours(istart,1);
|
|
LevelList = get(h,'LevelList');
|
|
ireplace = find([false; diff(Levels) == 0]);
|
|
[idx, pos] = ismember(Levels(ireplace), LevelList);
|
|
for ii = 1:numel(pos)
|
|
cellcont{ireplace(ii)}(1) = LevelList(pos(ii)-1);
|
|
end
|
|
|
|
% Add "contourless" backdrop
|
|
xdata = get(h,'XData');
|
|
ydata = get(h,'YData');
|
|
backdrop = [setdiff(LevelList, Levels), 4;
|
|
min(xdata(:)), min(ydata(:));
|
|
min(xdata(:)), max(ydata(:));
|
|
max(xdata(:)), max(ydata(:));
|
|
max(xdata(:)), min(ydata(:))];
|
|
cellcont = [backdrop; cellcont];
|
|
|
|
% Plot
|
|
columnNames = {'x','y'};
|
|
for ii = 1:ncont + 1
|
|
drawOptions = opts_new();
|
|
|
|
% Get fill color
|
|
zval = cellcont{ii}(1,1);
|
|
[m2t, xcolor] = getColor(m2t,h,zval,'image');
|
|
drawOptions = opts_add(drawOptions,'fill',xcolor);
|
|
|
|
% Get line properties
|
|
lineColor = get(h, 'LineColor');
|
|
|
|
[m2t, drawOptions] = setColor(m2t, h, drawOptions, 'draw', lineColor, 'none');
|
|
|
|
[m2t, lineOptions] = getLineOptions(m2t, h);
|
|
drawOptions = opts_merge(drawOptions, lineOptions);
|
|
|
|
% Toggle legend entry
|
|
hasLegend = ii == 1 && m2t.currentHandleHasLegend;
|
|
drawOptions = maybeShowInLegend(hasLegend, drawOptions);
|
|
drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
|
|
|
|
% Print table
|
|
[m2t, table, tableOptions] = makeTable(m2t, columnNames, cellcont{ii}(2:end,:));
|
|
|
|
% Print out
|
|
drawOpts = opts_print(drawOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('%s\\addplot[%s] table[%s] {%%\n%s};\n', ...
|
|
str, drawOpts, tabOpts, table);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawHggroup(m2t, h, custom)
|
|
% Continue according to the plot type. Since the function `handle` is
|
|
% not available in Octave, the plot type will be guessed or the fallback type
|
|
% 'unknown' used.
|
|
% #COMPLEX: big switch-case
|
|
warnDoesNotHandleCustomProperties(h, custom);
|
|
switch getEnvironment()
|
|
case 'MATLAB'
|
|
cl = class(handle(h));
|
|
|
|
case 'Octave'
|
|
% Function `handle` is not yet implemented in Octave
|
|
% Consequently the plot type needs to be guessed. See #645.
|
|
cl = guessOctavePlotType(h);
|
|
|
|
otherwise
|
|
errorUnknownEnvironment();
|
|
end
|
|
|
|
switch(cl)
|
|
case {'specgraph.barseries', 'matlab.graphics.chart.primitive.Bar'}
|
|
% hist plots and friends
|
|
[m2t, str] = handleObject(m2t, h, @drawBarseries);
|
|
|
|
case {'specgraph.stemseries', 'matlab.graphics.chart.primitive.Stem'}
|
|
% stem plots
|
|
[m2t, str] = handleObject(m2t, h, @drawStemSeries);
|
|
|
|
case {'specgraph.stairseries', 'matlab.graphics.chart.primitive.Stair'}
|
|
% stair plots
|
|
[m2t, str] = handleObject(m2t, h, @drawStairSeries);
|
|
|
|
case {'specgraph.areaseries', 'matlab.graphics.chart.primitive.Area'}
|
|
% scatter plots
|
|
[m2t, str] = handleObject(m2t, h, @drawAreaSeries);
|
|
|
|
case {'specgraph.quivergroup', 'matlab.graphics.chart.primitive.Quiver'}
|
|
% quiver arrows
|
|
[m2t, str] = handleObject(m2t, h, @drawQuiverGroup);
|
|
|
|
case {'specgraph.errorbarseries', 'matlab.graphics.chart.primitive.ErrorBar'}
|
|
% error bars
|
|
[m2t, str] = handleObject(m2t, h, @drawErrorBars);
|
|
|
|
case {'specgraph.scattergroup','matlab.graphics.chart.primitive.Scatter'}
|
|
% scatter plots
|
|
[m2t, str] = handleObject(m2t, h, @drawScatterPlot);
|
|
|
|
case {'specgraph.contourgroup', 'matlab.graphics.chart.primitive.Contour'}
|
|
[m2t, str] = handleObject(m2t, h, @drawContour);
|
|
|
|
case {'hggroup', 'matlab.graphics.primitive.Group'}
|
|
% handle all those the usual way
|
|
[m2t, str] = handleAllChildren(m2t, h);
|
|
|
|
case 'unknown'
|
|
% Octave only: plot type could not be determined
|
|
% Fall back to basic plotting
|
|
[m2t, str] = handleAllChildren(m2t, h);
|
|
|
|
otherwise
|
|
userWarning(m2t, 'Don''t know class ''%s''. Default handling.', cl);
|
|
try
|
|
m2tBackup = m2t;
|
|
[m2t, str] = handleAllChildren(m2t, h);
|
|
catch ME
|
|
userWarning(m2t, 'Default handling for ''%s'' failed. Continuing as if it did not occur. \n Original Message:\n %s', cl, getReport(ME));
|
|
[m2t, str] = deal(m2tBackup, ''); % roll-back
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
% Function `handle` is not yet implemented in Octave.
|
|
% Consequently the plot type needs to be guessed. See #645.
|
|
% If the type can not be determined reliably, 'unknown' will be set.
|
|
function cl = guessOctavePlotType(h)
|
|
% scatter plots
|
|
if hasProperties(h, {'marker','sizedata','cdata'}, {})
|
|
cl = 'specgraph.scattergroup';
|
|
|
|
% error bars
|
|
elseif hasProperties(h, {'udata','ldata'}, {})
|
|
cl = 'specgraph.errorbarseries';
|
|
|
|
% quiver plots
|
|
elseif hasProperties(h, {'udata','vdata'}, {'ldata'})
|
|
cl = 'specgraph.quivergroup';
|
|
|
|
% bar plots
|
|
elseif hasProperties(h, {'bargroup','barwidth', 'barlayout'}, {})
|
|
cl = 'specgraph.barseries';
|
|
% unknown plot type
|
|
else
|
|
cl = 'unknown';
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function bool = hasProperties(h, fieldsExpectedPresent, fieldsExpectedAbsent)
|
|
% Check if object has all of the given properties (case-insensitive).
|
|
% h handle to object (e.g. `gcf` or `gca`)
|
|
% fieldsExpectedPresent cell array of strings with property names to be present
|
|
% fieldsExpectedPresent cell array of strings with property names to be absent
|
|
fields = lower(fieldnames(get(h)));
|
|
present = all(ismember(lower(fieldsExpectedPresent), fields));
|
|
absent = ~any(ismember(lower(fieldsExpectedAbsent), fields));
|
|
bool = present && absent;
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = handleObject(m2t, h, actualHandler)
|
|
% this function wraps the actual drawing handlers and allows the user
|
|
% to specify custom options, code, comments or even handler via |m2tcustom|.
|
|
assert(isa(actualHandler,'function_handle'));
|
|
|
|
custom = m2tcustom(h); % retrieve custom settings
|
|
if isfield(custom, 'customHandler')
|
|
actualHandler = custom.customHandler;
|
|
end
|
|
|
|
[m2t, str] = feval(actualHandler, m2t, h, custom);
|
|
str = [texcomment(custom.commentsBefore), custom.codeBefore, ...
|
|
str, ...
|
|
texcomment(custom.commentsAfter), custom.codeAfter];
|
|
end
|
|
% ==============================================================================
|
|
function warnDoesNotHandleCustomProperties(h, custom, unhandledProperties)
|
|
% warns the user about unhandled custom properties
|
|
if ~exist('unhandledProperties','var')
|
|
unhandledProperties = {'codeInsideFirst', 'codeInsideLast', 'extraOptions'};
|
|
elseif ischar(unhandledProperties)
|
|
unhandledProperties = {unhandledProperties};
|
|
end
|
|
|
|
customProps = fieldnames(custom);
|
|
for iProperty = 1:numel(customProps)
|
|
prop = customProps{iProperty};
|
|
if ~isempty(custom.(prop)) && ismember(prop, unhandledProperties)
|
|
warning('matlab2tikz:unhandledCustomProperty', ...
|
|
'Custom property "%s" of %s has not been handled.',...
|
|
prop, get(h, 'Type'));
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawNothing(m2t, h, custom) %#ok some params unused
|
|
% draws nothing
|
|
% While it may seem useless, this allows a user to add comments/code
|
|
% for objects that matlab2tikz cannot translate (yet) thanks to the
|
|
% |handleObject| function together with |m2tcustom|.
|
|
str = '';
|
|
warnDoesNotHandleCustomProperties(h, custom);
|
|
end
|
|
% ==============================================================================
|
|
function m2t = drawAnnotations(m2t)
|
|
% Draws annotation in Matlab (Octave not supported).
|
|
|
|
% In HG1 annotations are children of an invisible axis called scribeOverlay.
|
|
% In HG2 annotations are children of annotationPane object which does not
|
|
% have any axis properties. Hence, we cannot simply handle it with a
|
|
% drawAxes() call.
|
|
|
|
% Octave
|
|
if strcmpi(getEnvironment,'Octave')
|
|
return
|
|
end
|
|
|
|
% Get annotation handles
|
|
if isHG2
|
|
annotPanes = findall(m2t.current.gcf,'Tag','scribeOverlay');
|
|
children = allchild(annotPanes);
|
|
%TODO: is this dead code?
|
|
if iscell(children)
|
|
children = [children{:}];
|
|
end
|
|
annotHandles = findall(children,'Visible','on');
|
|
else
|
|
annotHandles = findall(m2t.scribeLayer,'-depth',1,'Visible','on');
|
|
end
|
|
|
|
% There are no anotations
|
|
if isempty(annotHandles)
|
|
return
|
|
end
|
|
|
|
% Create fake simplified axes overlay (no children)
|
|
warning('off', 'matlab2tikz:NoChildren')
|
|
scribeLayer = axes('Units','Normalized','Position',[0,0,1,1],'Visible','off');
|
|
m2t = drawAxes(m2t, scribeLayer);
|
|
warning('on', 'matlab2tikz:NoChildren')
|
|
|
|
% Plot in reverse to preserve z-ordering and assign the converted
|
|
% annotations to the converted fake overlay
|
|
for ii = numel(annotHandles):-1:1
|
|
m2t = drawAnnotationsHelper(m2t, annotHandles(ii));
|
|
end
|
|
|
|
% Delete fake overlay graphics object
|
|
delete(scribeLayer)
|
|
end
|
|
% ==============================================================================
|
|
function m2t = drawAnnotationsHelper(m2t,h)
|
|
% Get class name
|
|
try
|
|
cl = class(handle(h));
|
|
catch
|
|
cl = 'unknown';
|
|
end
|
|
|
|
switch cl
|
|
|
|
% Line
|
|
case {'scribe.line', 'matlab.graphics.shape.Line'}
|
|
[m2t, str] = handleObject(m2t, h, @drawLine);
|
|
|
|
% Ellipse
|
|
case {'scribe.scribeellipse','matlab.graphics.shape.Ellipse'}
|
|
[m2t, str] = handleObject(m2t, h, @drawEllipse);
|
|
|
|
% Arrows
|
|
case {'scribe.arrow', 'scribe.doublearrow'}%,...
|
|
%'matlab.graphics.shape.Arrow', 'matlab.graphics.shape.DoubleEndArrow'}
|
|
% Annotation: single and double Arrow, line
|
|
% TODO:
|
|
% - write a drawArrow(). Handle all info info directly
|
|
% without using handleAllChildren() since HG2 does not have
|
|
% children (so no shortcut).
|
|
% - It would be good if drawArrow() was callable on a
|
|
% matlab.graphics.shape.TextArrow object to draw the arrow
|
|
% part.
|
|
[m2t, str] = handleAllChildren(m2t, h);
|
|
|
|
% Text box
|
|
case {'scribe.textbox','matlab.graphics.shape.TextBox'}
|
|
[m2t, str] = handleObject(m2t, h, @drawText);
|
|
|
|
% Arrow
|
|
case {'matlab.graphics.shape.Arrow','matlab.graphics.shape.DoubleEndArrow'}
|
|
[m2t, str] = handleObject(m2t, h, @drawArrow);
|
|
|
|
% Text arrow
|
|
case {'scribe.textarrow'}%,'matlab.graphics.shape.TextArrow'}
|
|
% TODO: rewrite drawTextarrow. Handle all info info directly
|
|
% without using handleAllChildren() since HG2 does not
|
|
% have children (so no shortcut) as used for
|
|
% scribe.textarrow.
|
|
[m2t, str] = handleObject(m2t, h, @drawTextarrow);
|
|
|
|
% Rectangle
|
|
case {'scribe.scriberect', 'matlab.graphics.shape.Rectangle'}
|
|
[m2t, str] = handleObject(m2t, h, @drawRectangle);
|
|
|
|
case guitypes()
|
|
% don't do anything for GUI objects and their children
|
|
[m2t, str] = handleObject(m2t, h, @drawNothing);
|
|
|
|
otherwise
|
|
userWarning(m2t, 'Don''t know annotation ''%s''.', cl);
|
|
return
|
|
end
|
|
|
|
% Add annotation to scribe overlay
|
|
m2t.axes{end} = addChildren(m2t.axes{end}, str);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawArrow(m2t, handle, custom)
|
|
str = '';
|
|
if ~isLineVisible(handle)
|
|
return; % there is nothing to plot
|
|
end
|
|
|
|
m2t = signalDependency(m2t, 'tikzlibrary', 'arrows.meta');
|
|
% Arrow Style
|
|
if isa(handle,'matlab.graphics.shape.DoubleEndArrow')
|
|
arrowSpec = '{Stealth}-{Stealth}';
|
|
else
|
|
arrowSpec = '-{Stealth}';
|
|
end
|
|
|
|
% Color
|
|
color = get(handle, 'Color');
|
|
[m2t, xcolor] = getColor(m2t, handle, color, 'patch');
|
|
|
|
% Line options
|
|
[m2t, lineOptions] = getLineOptions(m2t, handle);
|
|
|
|
drawOptions = opts_new();
|
|
drawOptions = opts_add(drawOptions, arrowSpec);
|
|
drawOptions = opts_add(drawOptions, 'color', xcolor);
|
|
drawOptions = opts_merge(drawOptions, lineOptions);
|
|
drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
|
|
|
|
[m2t, pos1, pos2] = getPositionOfArrow(m2t, handle);
|
|
|
|
styleOpts = opts_print(drawOptions);
|
|
str = sprintf('\\draw[%s] %s -- %s;\n', ...
|
|
styleOpts, pos1, pos2);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, pos1, pos2] = getPositionOfArrow(m2t, h)
|
|
% makes the tikz position string of an arrow
|
|
posX = get(h, 'X');
|
|
posY = get(h, 'Y');
|
|
|
|
type = 'axis cs:';
|
|
fmtUnit = '';
|
|
|
|
pos1{1} = formatDim(posX(1), fmtUnit);
|
|
pos1{2} = formatDim(posY(1), fmtUnit);
|
|
pos2{1} = formatDim(posX(2), fmtUnit);
|
|
pos2{2} = formatDim(posY(2), fmtUnit);
|
|
|
|
pos1 = sprintf('(%s%s)',type,join(m2t,pos1,','));
|
|
pos2 = sprintf('(%s%s)',type,join(m2t,pos2,','));
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawSurface(m2t, h, custom)
|
|
[m2t, opts, s] = shaderOpts(m2t, h,'surf');
|
|
tableOptions = opts_new();
|
|
|
|
% Allow for empty surf
|
|
if isNone(s.plotType)
|
|
str = '';
|
|
return
|
|
end
|
|
|
|
[dx, dy, dz, numrows] = getXYZDataFromSurface(h);
|
|
m2t = jumpAtUnboundCoords(m2t, [dx(:); dy(:); dz(:)]);
|
|
|
|
[m2t, opts] = addZBufferOptions(m2t, h, opts);
|
|
|
|
% Check if 3D
|
|
is3D = m2t.axes{end}.is3D;
|
|
if is3D
|
|
columnNames = {'x','y','z','c'};
|
|
plotCmd = 'addplot3';
|
|
data = applyHgTransform(m2t, [dx(:), dy(:), dz(:)]);
|
|
else
|
|
columnNames = {'x','y','c'};
|
|
plotCmd = 'addplot';
|
|
data = [dx(:), dy(:)];
|
|
end
|
|
|
|
% There are several possibilities of how colors are specified for surface
|
|
% plots:
|
|
% * explicitly by RGB-values,
|
|
% * implicitly through a color map with a point-meta coordinate,
|
|
% * implicitly through a color map with a given coordinate (e.g., z).
|
|
%
|
|
|
|
% Check if we need extra CData.
|
|
CData = get(h, 'CData');
|
|
if length(size(CData)) == 3 && size(CData, 3) == 3
|
|
|
|
% Create additional custom colormap
|
|
nrows = size(data,1);
|
|
CData = reshape(CData, nrows,3);
|
|
m2t.axes{end}.options(end+1,:) = ...
|
|
{matlab2pgfplotsColormap(m2t, CData, 'patchmap'), []};
|
|
|
|
% Index into custom colormap
|
|
color = (0:nrows-1)';
|
|
|
|
tableOptions = opts_add(tableOptions, 'colormap name','surfmap');
|
|
else
|
|
opts = opts_add(opts,matlab2pgfplotsColormap(m2t, m2t.current.colormap),'');
|
|
% If NaNs are present in the color specifications, don't use them for
|
|
% Pgfplots; they may be interpreted as strings there.
|
|
% Note:
|
|
% Pgfplots actually does a better job than MATLAB in determining what
|
|
% colors to use for the patches. The circular test case on
|
|
% http://www.mathworks.de/de/help/matlab/ref/pcolor.html, for example
|
|
% yields a symmetric setup in Pgfplots (and doesn't in MATLAB).
|
|
needsPointmeta = any(xor(isnan(dz(:)), isnan(CData(:)))) ...
|
|
|| any(abs(CData(:) - dz(:)) > 1.0e-10);
|
|
if needsPointmeta
|
|
color = CData(:);
|
|
else
|
|
color = dz(:); % Fallback on the z-values, especially if 2D view
|
|
end
|
|
end
|
|
tableOptions = opts_add(tableOptions, 'point meta','\thisrow{c}');
|
|
|
|
data = [data, color];
|
|
|
|
% Add mesh/rows=<num rows> for specifying the row data instead of empty
|
|
% lines in the data list below. This makes it possible to reduce the
|
|
% data writing to one single sprintf() call.
|
|
opts = opts_add(opts,'mesh/rows',sprintf('%d', numrows));
|
|
opts = opts_append_userdefined(opts, custom.extraOptions);
|
|
|
|
% Print the addplot options
|
|
str = sprintf('\n\\%s[%%\n%s,\n%s]', plotCmd, s.plotType, opts_print(opts));
|
|
|
|
% Print the data
|
|
[m2t, table, tabOptsExtra] = makeTable(m2t, columnNames, data);
|
|
tableOptions = opts_merge(tabOptsExtra, tableOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
|
|
% Here is where everything is put together
|
|
str = sprintf('%s\ntable[%s] {%%\n%s};\n', ...
|
|
str, tabOpts, table);
|
|
|
|
% TODO:
|
|
% - remove grids in spectrogram by either removing grid command
|
|
% or adding: 'grid=none' from/in axis options
|
|
% - handling of huge data amounts in LaTeX.
|
|
|
|
[m2t, labelString] = addLabel(m2t, h);
|
|
str = [str, labelString];
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, opts] = addZBufferOptions(m2t, h, opts)
|
|
% Enforce 'z buffer=sort' if shader is flat and is a 3D plot. It is to
|
|
% avoid overlapping e.g. sphere plots and to properly mimic Matlab's
|
|
% coloring of faces.
|
|
% NOTE:
|
|
% - 'z buffer=sort' is computationally more expensive for LaTeX, we
|
|
% could try to avoid it in some default situations, e.g. when dx and
|
|
% dy are rank-1-matrices.
|
|
% - hist3D plots should not be z-sorted or the highest bars will cover
|
|
% the shortest one even if positioned in the back
|
|
isShaderFlat = isempty(strfind(opts_get(opts, 'shader'), 'interp'));
|
|
isHist3D = strcmpi(get(h,'tag'), 'hist3');
|
|
is3D = m2t.axes{end}.is3D;
|
|
if is3D && isShaderFlat && ~isHist3D
|
|
opts = opts_add(opts, 'z buffer', 'sort');
|
|
% Pgfplots 1.12 contains a bug fix that fixes legend entries when
|
|
% 'z buffer=sort' has been set. So, it's easier to always require that
|
|
% version anyway. See #504 for more information.
|
|
m2t = needsPgfplotsVersion(m2t, [1,12]);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [dx, dy, dz, numrows] = getXYZDataFromSurface(h)
|
|
% retrieves X, Y and Z data from a Surface plot. The data gets returned in a
|
|
% wastefull format where the dimensions of these data vectors is equal, akin
|
|
% to the format used by meshgrid.
|
|
dx = get(h, 'XData');
|
|
dy = get(h, 'YData');
|
|
dz = get(h, 'ZData');
|
|
[numcols, numrows] = size(dz);
|
|
|
|
% If dx or dy are given as vectors, convert them to the (wasteful) matrix
|
|
% representation first. This makes sure we can treat the data with one
|
|
% single sprintf() command below.
|
|
if isvector(dx)
|
|
dx = ones(numcols,1) * dx(:)';
|
|
end
|
|
if isvector(dy)
|
|
dy = dy(:) * ones(1,numrows);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawVisibleText(m2t, handle, custom)
|
|
% Wrapper for drawText() that only draws visible text
|
|
|
|
% There may be some text objects floating around a MATLAB figure which are
|
|
% handled by other subfunctions (labels etc.) or don't need to be handled at
|
|
% all.
|
|
% The HandleVisibility says something about whether the text handle is
|
|
% visible as a data structure or not. Typically, a handle is hidden if the
|
|
% graphics aren't supposed to be altered, e.g., axis labels. Most of those
|
|
% entities are captured by matlab2tikz one way or another, but sometimes they
|
|
% are not. This is the case, for example, with polar plots and the axis
|
|
% descriptions therein. Also, Matlab treats text objects with a NaN in the
|
|
% position as invisible.
|
|
if any(isnan(get(handle, 'Position')) | isnan(get(handle, 'Rotation'))) ...
|
|
|| isOff(get(handle, 'Visible')) ...
|
|
|| (isOff(get(handle, 'HandleVisibility')) && ...
|
|
~m2t.args.showHiddenStrings)
|
|
|
|
str = '';
|
|
return;
|
|
end
|
|
|
|
[m2t, str] = drawText(m2t, handle, custom);
|
|
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawText(m2t, handle, custom)
|
|
% Adding text node anywhere in the axes environment.
|
|
% Not that, in Pgfplots, long texts get cut off at the axes. This is
|
|
% Different from the default MATLAB behavior. To fix this, one could use
|
|
% /pgfplots/after end axis/.code.
|
|
|
|
if ~exist('custom','var') || isempty(custom)
|
|
custom = m2tcustom([]); %FIXME: adjust at all call sites instead
|
|
end
|
|
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% get required properties
|
|
content = get(handle, 'String');
|
|
Interpreter = get(handle, 'Interpreter');
|
|
content = prettyPrint(m2t, content, Interpreter);
|
|
% Concatenate multiple lines
|
|
content = join(m2t, content, '\\');
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% translate them to pgf style
|
|
style = opts_new();
|
|
|
|
bgColor = get(handle,'BackgroundColor');
|
|
[m2t, style] = setColor(m2t, handle, style, 'fill', bgColor);
|
|
|
|
style = getXYAlignmentOfText(handle, style);
|
|
|
|
style = getRotationOfText(m2t, handle, style);
|
|
|
|
[m2t, fontStyle] = getFontStyle(m2t, handle);
|
|
style = opts_merge(style, fontStyle);
|
|
|
|
EdgeColor = get(handle, 'EdgeColor');
|
|
[m2t, style] = setColor(m2t, handle, style, 'draw', EdgeColor);
|
|
|
|
style = opts_append_userdefined(style, custom.extraOptions);
|
|
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% plot the thing
|
|
[m2t, posString] = getPositionOfText(m2t, handle);
|
|
|
|
styleOpts = opts_print(style);
|
|
str = sprintf('\\node[%s]\nat %s {%s};\n', ...
|
|
styleOpts, posString, content);
|
|
end
|
|
% ==============================================================================
|
|
function [style] = getXYAlignmentOfText(handle, style)
|
|
% sets the horizontal and vertical alignment options of a text object
|
|
VerticalAlignment = get(handle, 'VerticalAlignment');
|
|
HorizontalAlignment = get(handle, 'HorizontalAlignment');
|
|
|
|
horizontal = '';
|
|
vertical = '';
|
|
switch VerticalAlignment
|
|
case {'top', 'cap'}
|
|
vertical = 'below';
|
|
case {'baseline', 'bottom'}
|
|
vertical = 'above';
|
|
end
|
|
switch HorizontalAlignment
|
|
case 'left'
|
|
horizontal = 'right';
|
|
case 'right'
|
|
horizontal = 'left';
|
|
end
|
|
alignment = strtrim(sprintf('%s %s', vertical, horizontal));
|
|
if strcmp(VerticalAlignment, 'middle') && strcmp(HorizontalAlignment, 'center')
|
|
alignment = 'centered';
|
|
end
|
|
|
|
if ~isempty(alignment)
|
|
style = opts_add(style, alignment);
|
|
end
|
|
|
|
% Set 'align' option that is needed for multiline text
|
|
style = opts_add(style, 'align', HorizontalAlignment);
|
|
|
|
style = opts_add(style, 'inner sep', '0');
|
|
end
|
|
% ==============================================================================
|
|
function [style] = getRotationOfText(m2t, handle, style)
|
|
% Add rotation, if existing
|
|
defaultRotation = 0.0;
|
|
rot = getOrDefault(handle, 'Rotation', defaultRotation);
|
|
if rot ~= defaultRotation
|
|
style = opts_add(style, 'rotate', sprintf(m2t.ff, rot));
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t,posString] = getPositionOfText(m2t, h)
|
|
% makes the tikz position string of a text object
|
|
pos = get(h, 'Position');
|
|
units = get(h, 'Units');
|
|
is3D = m2t.axes{end}.is3D;
|
|
|
|
% Deduce if text or textbox
|
|
type = get(h,'type');
|
|
if isempty(type) || strcmpi(type,'hggroup')
|
|
type = get(h,'ShapeType'); % Undocumented property valid from 2008a
|
|
end
|
|
|
|
switch type
|
|
case 'text'
|
|
if is3D
|
|
pos = applyHgTransform(m2t, pos);
|
|
npos = 3;
|
|
else
|
|
pos = pos(1:2);
|
|
npos = 2;
|
|
end
|
|
case {'textbox','textboxshape'}
|
|
% TODO:
|
|
% - size of the box (e.g. using node attributes minimum width / height)
|
|
% - Alignment of the resized box
|
|
pos = pos(1:2);
|
|
npos = 2;
|
|
|
|
otherwise
|
|
error('matlab2tikz:drawText', 'Unrecognized text type: %s.', type);
|
|
end
|
|
|
|
% Format according to units
|
|
switch units
|
|
case 'normalized'
|
|
type = 'rel axis cs:';
|
|
fmtUnit = '';
|
|
case 'data'
|
|
type = 'axis cs:';
|
|
fmtUnit = '';
|
|
% Let Matlab do the conversion of any unit into cm
|
|
otherwise
|
|
type = '';
|
|
fmtUnit = 'cm';
|
|
if ~strcmpi(units, 'centimeters')
|
|
% Save old pos, set units to cm, query pos, reset
|
|
% NOTE: cannot use copyobj since it is buggy in R2014a, see
|
|
% http://www.mathworks.com/support/bugreports/368385
|
|
oldPos = get(h, 'Position');
|
|
set(h,'Units','centimeters')
|
|
pos = get(h, 'Position');
|
|
pos = pos(1:npos);
|
|
set(h,'Units',units,'Position',oldPos)
|
|
end
|
|
end
|
|
posString = cell(1,npos);
|
|
for ii = 1:npos
|
|
posString{ii} = formatDim(pos(ii), fmtUnit);
|
|
end
|
|
|
|
posString = sprintf('(%s%s)',type,join(m2t,posString,','));
|
|
m2t = disableClippingInCurrentAxes(m2t, pos);
|
|
|
|
end
|
|
% ==============================================================================
|
|
function m2t = disableClippingInCurrentAxes(m2t, pos)
|
|
% Disables clipping in the current axes if the `pos` vector lies outside
|
|
% the limits of the axes.
|
|
xlim = getOrDefault(m2t.current.gca, 'XLim',[-Inf +Inf]);
|
|
ylim = getOrDefault(m2t.current.gca, 'YLim',[-Inf +Inf]);
|
|
zlim = getOrDefault(m2t.current.gca, 'ZLim',[-Inf +Inf]);
|
|
is3D = m2t.axes{end}.is3D;
|
|
|
|
xOutOfRange = pos(1) < xlim(1) || pos(1) > xlim(2);
|
|
yOutOfRange = pos(2) < ylim(1) || pos(2) > ylim(2);
|
|
zOutOfRange = is3D && (pos(3) < zlim(1) || pos(3) > zlim(2));
|
|
if xOutOfRange || yOutOfRange || zOutOfRange
|
|
m2t = m2t_addAxisOption(m2t, 'clip', 'false');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawRectangle(m2t, h, custom)
|
|
str = '';
|
|
|
|
% there may be some text objects floating around a Matlab figure which
|
|
% are handled by other subfunctions (labels etc.) or don't need to be
|
|
% handled at all
|
|
if ~isVisible(h) || isOff(get(h, 'HandleVisibility'))
|
|
return;
|
|
end
|
|
|
|
% TODO handle Curvature = [0.8 0.4]
|
|
|
|
% Get draw options.
|
|
[m2t, lineOptions] = getLineOptions(m2t, h);
|
|
[m2t, lineOptions] = getRectangleFaceOptions(m2t, h, lineOptions);
|
|
[m2t, lineOptions] = getRectangleEdgeOptions(m2t, h, lineOptions);
|
|
lineOptions = opts_append_userdefined(lineOptions, custom.extraOptions);
|
|
|
|
pos = pos2dims(get(h, 'Position'));
|
|
% plot the thing
|
|
lineOpts = opts_print(lineOptions);
|
|
str = sprintf(['\\draw[%s] (axis cs:',m2t.ff,',',m2t.ff, ')', ...
|
|
' rectangle (axis cs:',m2t.ff,',',m2t.ff,');\n'], ...
|
|
lineOpts, pos.left, pos.bottom, pos.right, pos.top);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, drawOptions] = getRectangleFaceOptions(m2t, h, drawOptions)
|
|
% draws the face (i.e. fill) of a Rectangle
|
|
faceColor = get(h, 'FaceColor');
|
|
isAnnotation = strcmpi(get(h,'type'),'rectangleshape') || ...
|
|
strcmpi(getOrDefault(h,'ShapeType',''),'rectangle');
|
|
isFlatColor = strcmpi(faceColor, 'flat');
|
|
if ~(isNone(faceColor) || (isAnnotation && isFlatColor))
|
|
[m2t, xFaceColor] = getColor(m2t, h, faceColor, 'patch');
|
|
drawOptions = opts_add(drawOptions, 'fill', xFaceColor);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, drawOptions] = getRectangleEdgeOptions(m2t, h, drawOptions)
|
|
% draws the edges of a rectangle
|
|
edgeColor = get(h, 'EdgeColor');
|
|
lineStyle = get(h, 'LineStyle');
|
|
if isNone(lineStyle) || isNone(edgeColor)
|
|
drawOptions = opts_add(drawOptions, 'draw', 'none');
|
|
else
|
|
[m2t, drawOptions] = setColor(m2t, h, drawOptions, 'draw', edgeColor);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t,opts,s] = shaderOpts(m2t, handle, selectedType)
|
|
% SHADEROPTS Returns the shader, fill and draw options for patches, surfs and meshes
|
|
%
|
|
% SHADEROPTS(M2T, HANDLE, SELECTEDTYPE) Where SELECTEDTYPE should either
|
|
% be 'surf' or 'patch'
|
|
%
|
|
%
|
|
% [...,OPTS, S] = SHADEROPTS(...)
|
|
% OPTS is a M by 2 cell array with Key/Value pairs
|
|
% S is a struct with fields, e.g. 'faceColor', to be re-used by the
|
|
% caller
|
|
|
|
% Initialize
|
|
opts = opts_new;
|
|
s.hasOneEdgeColor = false;
|
|
s.hasOneFaceColor = false;
|
|
|
|
% Get relevant Face and Edge color properties
|
|
s.faceColor = get(handle, 'FaceColor');
|
|
s.edgeColor = get(handle, 'EdgeColor');
|
|
|
|
if isNone(s.faceColor) && isNone(s.edgeColor)
|
|
s.plotType = 'none';
|
|
s.hasOneEdgeColor = true;
|
|
elseif isNone(s.faceColor)
|
|
s.plotType = 'mesh';
|
|
s.hasOneFaceColor = true;
|
|
[m2t, opts, s] = shaderOptsMesh(m2t, handle, opts, s);
|
|
else
|
|
s.plotType = selectedType;
|
|
[m2t, opts, s] = shaderOptsSurfPatch(m2t, handle, opts, s);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, opts, s] = shaderOptsMesh(m2t, handle, opts, s)
|
|
|
|
% Edge 'interp'
|
|
if strcmpi(s.edgeColor, 'interp')
|
|
opts = opts_add(opts,'shader','flat');
|
|
|
|
% Edge RGB
|
|
else
|
|
s.hasOneEdgeColor = true;
|
|
[m2t, xEdgeColor] = getColor(m2t, handle, s.edgeColor, 'patch');
|
|
opts = opts_add(opts,'color',xEdgeColor);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, opts, s] = shaderOptsSurfPatch(m2t, handle, opts, s)
|
|
% gets the shader options for surface patches
|
|
|
|
% Set opacity if FaceAlpha < 1 in MATLAB
|
|
s.faceAlpha = get(handle, 'FaceAlpha');
|
|
if isnumeric(s.faceAlpha) && s.faceAlpha ~= 1.0
|
|
opts = opts_add(opts,'fill opacity',sprintf(m2t.ff,s.faceAlpha));
|
|
end
|
|
|
|
% Set opacity if EdgeAlpha < 1 in MATLAB
|
|
s.edgeAlpha = get(handle, 'EdgeAlpha');
|
|
if isnumeric(s.edgeAlpha) && s.edgeAlpha ~= 1.0
|
|
opts = opts_add(opts,'draw opacity',sprintf(m2t.ff,s.edgeAlpha));
|
|
end
|
|
|
|
if isNone(s.edgeColor) || isNone(get(handle,'LineStyle')) % Edge 'none'
|
|
[m2t, opts, s] = shaderOptsSurfPatchEdgeNone(m2t, handle, opts, s);
|
|
|
|
elseif strcmpi(s.edgeColor, 'interp') % Edge 'interp'
|
|
[m2t, opts, s] = shaderOptsSurfPatchEdgeInterp(m2t, handle, opts, s);
|
|
|
|
elseif strcmpi(s.edgeColor, 'flat') % Edge 'flat'
|
|
[m2t, opts, s] = shaderOptsSurfPatchEdgeFlat(m2t, handle, opts, s);
|
|
|
|
else % Edge RGB
|
|
[m2t, opts, s] = shaderOptsSurfPatchEdgeRGB(m2t, handle, opts, s);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, opts, s] = shaderOptsSurfPatchEdgeNone(m2t, handle, opts, s)
|
|
% gets the shader options for surface patches without edges
|
|
s.hasOneEdgeColor = true; % consider void as true
|
|
if strcmpi(s.faceColor, 'flat')
|
|
opts = opts_add(opts,'shader','flat');
|
|
elseif strcmpi(s.faceColor, 'interp')
|
|
opts = opts_add(opts,'shader','interp');
|
|
else
|
|
s.hasOneFaceColor = true;
|
|
[m2t,xFaceColor] = getColor(m2t, handle, s.faceColor, 'patch');
|
|
opts = opts_add(opts,'fill',xFaceColor);
|
|
end
|
|
end
|
|
function [m2t, opts, s] = shaderOptsSurfPatchEdgeInterp(m2t, handle, opts, s)
|
|
% gets the shader options for surface patches with interpolated edge colors
|
|
if strcmpi(s.faceColor, 'interp')
|
|
opts = opts_add(opts,'shader','interp');
|
|
elseif strcmpi(s.faceColor, 'flat')
|
|
opts = opts_add(opts,'shader','faceted');
|
|
else
|
|
s.hasOneFaceColor = true;
|
|
[m2t,xFaceColor] = getColor(m2t, handle, s.faceColor, 'patch');
|
|
opts = opts_add(opts,'fill',xFaceColor);
|
|
end
|
|
end
|
|
function [m2t, opts, s] = shaderOptsSurfPatchEdgeFlat(m2t, handle, opts, s)
|
|
% gets the shader options for surface patches with flat edge colors, i.e. the
|
|
% vertex color
|
|
if strcmpi(s.faceColor, 'flat')
|
|
opts = opts_add(opts,'shader','flat corner');
|
|
elseif strcmpi(s.faceColor, 'interp')
|
|
warnFacetedInterp(m2t);
|
|
opts = opts_add(opts,'shader','faceted interp');
|
|
else
|
|
s.hasOneFaceColor = true;
|
|
opts = opts_add(opts,'shader','flat corner');
|
|
[m2t,xFaceColor] = getColor(m2t, handle, s.faceColor, 'patch');
|
|
opts = opts_add(opts,'fill',xFaceColor);
|
|
end
|
|
end
|
|
function [m2t, opts, s] = shaderOptsSurfPatchEdgeRGB(m2t, handle, opts, s)
|
|
% gets the shader options for surface patches with fixed (RGB) edge color
|
|
s.hasOneEdgeColor = true;
|
|
[m2t, xEdgeColor] = getColor(m2t, handle, s.edgeColor, 'patch');
|
|
if isnumeric(s.faceColor)
|
|
s.hasOneFaceColor = true;
|
|
[m2t, xFaceColor] = getColor(m2t, handle, s.faceColor, 'patch');
|
|
opts = opts_add(opts,'fill',xFaceColor);
|
|
opts = opts_add(opts,'faceted color',xEdgeColor);
|
|
elseif strcmpi(s.faceColor,'interp')
|
|
warnFacetedInterp(m2t);
|
|
opts = opts_add(opts,'shader','faceted interp');
|
|
opts = opts_add(opts,'faceted color',xEdgeColor);
|
|
else
|
|
opts = opts_add(opts,'shader','flat corner');
|
|
opts = opts_add(opts,'draw',xEdgeColor);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function warnFacetedInterp(m2t)
|
|
% warn the user about the space implications of "shader=faceted interp"
|
|
userWarning(m2t, ...
|
|
['A 3D plot with "shader = faceted interp" is being produced.\n', ...
|
|
'This may produce big and sluggish PDF files.\n', ...
|
|
'See %s and Section 4.6.6 of the pgfplots manual for workarounds.'], ...
|
|
issueUrl(m2t, 693, true));
|
|
end
|
|
% ==============================================================================
|
|
function url = issueUrl(m2t, number, forOutput)
|
|
% Produces the URL for an issue report in the GitHub repository.
|
|
% When the `forOutput` flag is set, this format the URL for printing to the
|
|
% MATLAB terminal.
|
|
if ~exist('forOutput','var') || isempty(forOutput)
|
|
forOutput = false;
|
|
end
|
|
url = sprintf('%s/%d', m2t.about.issues, number);
|
|
if forOutput
|
|
url = clickableUrl(url, sprintf('#%d', number));
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function url = clickableUrl(url, title)
|
|
% Produce a clickable URL for outputting to the MATLAB terminal
|
|
if ~exist('title','var') || isempty(title)
|
|
title = url;
|
|
end
|
|
switch getEnvironment()
|
|
case 'MATLAB'
|
|
url = sprintf('<a href="%s">%s</a>', url, title);
|
|
case 'Octave'
|
|
% just use the URL and discard the title since Octave doesn't
|
|
% support HTML tags in its output.
|
|
otherwise
|
|
errorUnknownEnvironment();
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawScatterPlot(m2t, h, custom)
|
|
% DRAWSCATTERPLOT Draws a scatter plot
|
|
%
|
|
% A scatter plot is a plot containing only markers and where the
|
|
% size and/or color of each marker can be changed independently.
|
|
%
|
|
% References for TikZ code:
|
|
% - http://tex.stackexchange.com/questions/197270/how-to-plot-scatter-points-using-pgfplots-with-color-defined-from-table-rgb-valu
|
|
% - http://tex.stackexchange.com/questions/98646/multiple-different-meta-for-marker-color-and-marker-size
|
|
%
|
|
% See also: scatter
|
|
str = '';
|
|
if ~isVisible(h)
|
|
return; % there is nothing to plot
|
|
end
|
|
|
|
dataInfo = getDataInfo(h, 'X','Y','Z','C','Size');
|
|
markerInfo = getMarkerInfo(m2t, h);
|
|
|
|
if isempty(dataInfo.C) && strcmpi(getEnvironment(), 'Octave')
|
|
dataInfo.C = get(h, 'MarkerEdgeColor');
|
|
end
|
|
|
|
%TODO: check against getMarkerOptions() for duplicated code
|
|
|
|
dataInfo.Size = tryToMakeScalar(dataInfo.Size, m2t.tol);
|
|
|
|
% Rescale marker size (not definitive, follow discussion in #316)
|
|
% Prescale marker size for octave
|
|
if strcmpi(getEnvironment(), 'Octave')
|
|
dataInfo.Size = dataInfo.Size.^2/2;
|
|
end
|
|
dataInfo.Size = translateMarkerSize(m2t, markerInfo.style, sqrt(dataInfo.Size)/2);
|
|
|
|
drawOptions = opts_new();
|
|
|
|
%% Determine if we are drawing an actual scatter plot
|
|
hasDifferentSizes = numel(dataInfo.Size) ~= 1;
|
|
hasDifferentColors = numel(dataInfo.C) ~= 3;
|
|
isaScatter = hasDifferentSizes || hasDifferentColors;
|
|
if isaScatter
|
|
drawOptions = opts_add(drawOptions, 'scatter');
|
|
end
|
|
%TODO: we need to set the scatter source
|
|
drawOptions = opts_add(drawOptions, 'only marks');
|
|
drawOptions = opts_add(drawOptions, 'mark', markerInfo.tikz);
|
|
|
|
if length(dataInfo.C) == 3
|
|
% gets options specific to scatter plots with a single color
|
|
% No special treatment for the colors or markers are needed.
|
|
% All markers have the same color.
|
|
[m2t, xcolor, hasFaceColor] = getColorOfMarkers(m2t, h, 'MarkerFaceColor', dataInfo.C);
|
|
[m2t, ecolor, hasEdgeColor] = getColorOfMarkers(m2t, h, 'MarkerEdgeColor', dataInfo.C);
|
|
|
|
if length(dataInfo.Size) == 1;
|
|
drawOptions = opts_addSubOpts(drawOptions, 'mark options', ...
|
|
markerInfo.options);
|
|
drawOptions = opts_add(drawOptions, 'mark size', ...
|
|
sprintf('%.4fpt', dataInfo.Size)); % FIXME: investigate whether to use `m2t.ff`
|
|
if hasEdgeColor
|
|
drawOptions = opts_add(drawOptions, 'draw', ecolor);
|
|
else
|
|
drawOptions = opts_add(drawOptions, 'color', xcolor); %TODO: why do we even need this one?
|
|
end
|
|
if hasFaceColor
|
|
drawOptions = opts_add(drawOptions, 'fill', xcolor);
|
|
end
|
|
else % if changing marker size but same color on all marks
|
|
markerOptions = opts_new();
|
|
markerOptions = opts_addSubOpts(markerOptions, 'mark options', ...
|
|
markerInfo.options);
|
|
if hasEdgeColor
|
|
markerOptions = opts_add(markerOptions, 'draw', ecolor);
|
|
else
|
|
markerOptions = opts_add(markerOptions, 'draw', xcolor);
|
|
end
|
|
if hasFaceColor
|
|
markerOptions = opts_add(markerOptions, 'fill', xcolor);
|
|
end
|
|
% for changing marker size, the 'scatter' option has to be added
|
|
drawOptions = opts_add(drawOptions, 'color', xcolor);
|
|
drawOptions = opts_addSubOpts(drawOptions, 'mark options', ...
|
|
markerInfo.options);
|
|
|
|
if ~hasFaceColor
|
|
drawOptions = opts_add(drawOptions, ...
|
|
'scatter/use mapped color', xcolor);
|
|
else
|
|
drawOptions = opts_addSubOpts(drawOptions, ...
|
|
'scatter/use mapped color', markerOptions);
|
|
end
|
|
end
|
|
elseif size(dataInfo.C,2) == 3
|
|
% scatter plots with each marker a different RGB color (not yet supported!)
|
|
userWarning(m2t, 'Pgfplots cannot handle RGB scatter plots yet.');
|
|
% TODO Get this in order as soon as Pgfplots can do "scatter rgb".
|
|
% See e.g. http://tex.stackexchange.com/questions/197270 and #433
|
|
else
|
|
% scatter plot where the colors are set using a color map
|
|
markerOptions = opts_new();
|
|
markerOptions = opts_addSubOpts(markerOptions, 'mark options', ...
|
|
markerInfo.options);
|
|
if markerInfo.hasEdgeColor && markerInfo.hasFaceColor
|
|
[m2t, ecolor] = getColor(m2t, h, markerInfo.EdgeColor, 'patch');
|
|
markerOptions = opts_add(markerOptions, 'draw', ecolor);
|
|
else
|
|
markerOptions = opts_add(markerOptions, 'draw', 'mapped color');
|
|
end
|
|
if markerInfo.hasFaceColor
|
|
markerOptions = opts_add(markerOptions, 'fill', 'mapped color');
|
|
end
|
|
|
|
if numel(dataInfo.Size) == 1
|
|
drawOptions = opts_add(drawOptions, 'mark size', ...
|
|
sprintf('%.4fpt', dataInfo.Size)); % FIXME: investigate whether to use `m2t.ff`
|
|
else
|
|
%TODO: warn the user about this. It is not currently supported.
|
|
end
|
|
|
|
drawOptions = opts_add(drawOptions, 'scatter src', 'explicit');
|
|
drawOptions = opts_addSubOpts(drawOptions, 'scatter/use mapped color', ...
|
|
markerOptions);
|
|
% Add color map.
|
|
m2t = m2t_addAxisOption(m2t, matlab2pgfplotsColormap(m2t, m2t.current.colormap), []);
|
|
end
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% Plot the thing.
|
|
[env, data, metaPart, columns] = organizeScatterData(m2t, dataInfo);
|
|
|
|
if hasDifferentSizes
|
|
drawOptions = opts_append(drawOptions, 'visualization depends on', ...
|
|
'{\thisrow{size} \as \perpointmarksize}');
|
|
drawOptions = opts_add(drawOptions, ...
|
|
'scatter/@pre marker code/.append style', ...
|
|
'{/tikz/mark size=\perpointmarksize}');
|
|
end
|
|
% Add forget plot
|
|
drawOptions = maybeShowInLegend(m2t.currentHandleHasLegend, drawOptions);
|
|
|
|
% The actual printing.
|
|
[m2t, table, tableOptions] = makeTable(m2t, columns, data);
|
|
tableOptions = opts_merge(tableOptions, metaPart);
|
|
drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
|
|
|
|
% Print
|
|
drawOpts = opts_print(drawOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\%s[%s] table[%s]{%s};\n',...
|
|
env, drawOpts, tabOpts, table);
|
|
end
|
|
% ==============================================================================
|
|
function dataInfo = getDataInfo(h, varargin)
|
|
% retrieves the "*Data fields from a HG object
|
|
% When no names are specified, it assumes 'X','Y','Z' is requested
|
|
if nargin == 1
|
|
fields = {'X','Y','Z'};
|
|
else
|
|
fields = varargin;
|
|
end
|
|
dataInfo = struct();
|
|
for iField = 1:numel(fields)
|
|
name = fields{iField};
|
|
dataInfo.(name) = get(h, [name 'Data']);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function value = tryToMakeScalar(value, tolerance)
|
|
% make a vector into a scalar when all its components are equal
|
|
if ~exist('tolerance','var')
|
|
tolerance = 0; % do everything perfectly
|
|
end
|
|
if all(abs(value - value(1)) <= tolerance)
|
|
value = value(1);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function marker = getMarkerInfo(m2t, h, markOptions)
|
|
% gets marker-related options as a struct
|
|
if ~exist('markOptions','var') || isempty(markOptions)
|
|
markOptions = opts_new();
|
|
end
|
|
marker = struct();
|
|
marker.style = get(h, 'Marker');
|
|
marker.FaceColor = get(h, 'MarkerFaceColor');
|
|
marker.EdgeColor = get(h, 'MarkerEdgeColor');
|
|
marker.hasFaceColor = ~isNone(marker.FaceColor);
|
|
marker.hasEdgeColor = ~isNone(marker.EdgeColor);
|
|
[marker.tikz, marker.options] = translateMarker(m2t, marker.style, ...
|
|
markOptions, marker.hasFaceColor);
|
|
end
|
|
% ==============================================================================
|
|
function [env, data, metaOptions, columns] = organizeScatterData(m2t, dataInfo)
|
|
% reorganizes the {X,Y,Z,S} data into a single matrix
|
|
metaOptions = opts_new();
|
|
|
|
|
|
xData = dataInfo.X;
|
|
yData = dataInfo.Y;
|
|
zData = dataInfo.Z;
|
|
cData = dataInfo.C;
|
|
sData = dataInfo.Size;
|
|
|
|
% add the actual data
|
|
if ~m2t.axes{end}.is3D
|
|
env = 'addplot';
|
|
columns = {'x','y'};
|
|
data = [xData(:), yData(:)];
|
|
else
|
|
env = 'addplot3';
|
|
columns = {'x','y','z'};
|
|
data = applyHgTransform(m2t, [xData(:), yData(:), zData(:)]);
|
|
end
|
|
|
|
% add marker sizes
|
|
if length(sData) ~= 1
|
|
columns = [columns, {'size'}];
|
|
data = [data, sData(:)];
|
|
end
|
|
|
|
% add color data
|
|
if length(cData) == 3
|
|
% If size(cData,1)==1, then all the colors are the same and have
|
|
% already been accounted for above.
|
|
|
|
elseif size(cData,2) == 3
|
|
%TODO Hm, can't deal with this?
|
|
%[m2t, col] = rgb2colorliteral(m2t, cData(k,:));
|
|
%str = strcat(str, sprintf(' [%s]\n', col));
|
|
columns = [columns, {'R','G','B'}];
|
|
data = [data, cData(:,1), cData(:,2), cData(:,3)];
|
|
else
|
|
columns = [columns, {'color'}];
|
|
metaOptions = opts_add(metaOptions, 'meta', 'color');
|
|
data = [data, cData(:)];
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, xcolor, hasColor] = getColorOfMarkers(m2t, h, name, cData)
|
|
color = get(h, name);
|
|
hasColor = ~isNone(color);
|
|
if hasColor && ~strcmpi(color,'flat');
|
|
[m2t, xcolor] = getColor(m2t, h, color, 'patch');
|
|
else
|
|
[m2t, xcolor] = getColor(m2t, h, cData, 'patch');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawHistogram(m2t, h, custom)
|
|
% Takes care of plots like the ones produced by MATLAB's histogram function.
|
|
% The main pillar is Pgfplots's '{x,y}bar' plot.
|
|
%
|
|
% TODO Get rid of code duplication with 'drawAxes'.
|
|
|
|
% Do nothing if plot is invisible
|
|
str = '';
|
|
if ~isVisible(h)
|
|
return;
|
|
end
|
|
|
|
% Init drawOptions
|
|
drawOptions = opts_new();
|
|
|
|
% Data
|
|
binEdges = get(h, 'BinEdges');
|
|
binValue = get(h, 'Values');
|
|
data = [binEdges(:), [binValue(:); binValue(end)]];
|
|
|
|
% Check for orientation of the bars
|
|
isHorizontal = ~strcmpi(get(h, 'Orientation'), 'vertical');
|
|
if isHorizontal
|
|
drawOptions = opts_add(drawOptions, 'xbar interval');
|
|
data = fliplr(data);
|
|
else
|
|
drawOptions = opts_add(drawOptions, 'ybar interval');
|
|
end
|
|
|
|
% Get the draw options for the bars
|
|
[m2t, drawOptions] = getPatchDrawOptions(m2t, h, drawOptions);
|
|
drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
|
|
|
|
% Make table
|
|
[m2t, table, tableOptions] = makeTable(m2t, {'x','y'},data);
|
|
|
|
% Print out
|
|
drawOpts = opts_print(drawOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\addplot[%s] table[%s] {%s};\n', ...
|
|
drawOpts, tabOpts, table);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawBarseries(m2t, h, custom)
|
|
% Takes care of plots like the ones produced by MATLAB's bar function.
|
|
% The main pillar is Pgfplots's '{x,y}bar' plot.
|
|
%
|
|
% TODO Get rid of code duplication with 'drawAxes'.
|
|
|
|
% Do nothing if plot is invisible
|
|
str = '';
|
|
if ~isVisible(h)
|
|
return;
|
|
end
|
|
|
|
% Init drawOptions
|
|
drawOptions = opts_new();
|
|
|
|
% Check for orientation of the bars and their layout
|
|
isHorizontal = isOn(get(h, 'Horizontal'));
|
|
if isHorizontal
|
|
barType = 'xbar';
|
|
else
|
|
barType = 'ybar';
|
|
end
|
|
|
|
% Get the draw options for the layout
|
|
[m2t, drawOptions] = setBarLayoutOfBarSeries(m2t, h, barType, drawOptions);
|
|
|
|
% Get the draw options for the bars
|
|
[m2t, drawOptions] = getPatchDrawOptions(m2t, h, drawOptions);
|
|
|
|
% Add 'log origin = infty' if BaseValue differs from zero (log origin=0 is
|
|
% the default behaviour since Pgfplots v1.5).
|
|
baseValue = get(h, 'BaseValue');
|
|
if baseValue ~= 0.0
|
|
m2t = m2t_addAxisOption(m2t, 'log origin', 'infty');
|
|
%TODO: wait for pgfplots to implement other base values (see #438)
|
|
end
|
|
|
|
% Generate the tikz table
|
|
xData = get(h, 'XData');
|
|
yData = get(h, 'YData');
|
|
if isHorizontal
|
|
[yDataPlot, xDataPlot] = deal(xData, yData); % swap values
|
|
else
|
|
[xDataPlot, yDataPlot] = deal(xData, yData);
|
|
end
|
|
[m2t, table, tableOptions] = makeTable(m2t, '', xDataPlot, '', yDataPlot);
|
|
|
|
drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
|
|
% Print out
|
|
drawOpts = opts_print(drawOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\addplot[%s] table[%s] {%s};\n', ...
|
|
drawOpts, tabOpts, table);
|
|
% Add a baseline if appropriate
|
|
[m2t, baseline] = drawBaseline(m2t,h,isHorizontal);
|
|
str = [str, baseline];
|
|
end
|
|
% ==============================================================================
|
|
function BarWidth = getBarWidthInAbsolutUnits(h)
|
|
% determines the width of a bar in a bar plot
|
|
XData = get(h,'XData');
|
|
BarWidth = get(h, 'BarWidth');
|
|
if length(XData) > 1 && ~isCategoricalType(XData)
|
|
BarWidth = min(diff(XData)) * BarWidth;
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, drawOptions] = setBarLayoutOfBarSeries(m2t, h, barType, drawOptions)
|
|
% sets the options specific to a bar layour (grouped vs stacked)
|
|
barlayout = get(h, 'BarLayout');
|
|
|
|
switch barlayout
|
|
case 'grouped' % grouped bar plots
|
|
|
|
% Get number of bars series and bar series id
|
|
[numBarSeries, barSeriesId] = getNumBarAndId(h);
|
|
|
|
% Maximum group width relative to the minimum distance between two
|
|
% x-values. See <MATLAB>/toolbox/matlab/specgraph/makebars.m
|
|
maxGroupWidth = 0.8;
|
|
if numBarSeries == 1
|
|
groupWidth = 1.0;
|
|
else
|
|
groupWidth = min(maxGroupWidth, numBarSeries/(numBarSeries+1.5));
|
|
end
|
|
|
|
% Calculate the width of each bar and the center point shift as in
|
|
% makebars.m
|
|
% Get the shifts of the bar centers.
|
|
% In case of numBars==1, this returns 0,
|
|
% In case of numBars==2, this returns [-1/4, 1/4],
|
|
% In case of numBars==3, this returns [-1/3, 0, 1/3],
|
|
% and so forth.
|
|
% assumedBarWidth = groupWidth/numBarSeries; % assumption
|
|
% barShift = (barSeriesId - 0.5) * assumedBarWidth - groupWidth/2;
|
|
% FIXME #785: The previous version of barshift lead to
|
|
% regressions, as the bars were stacked.
|
|
% Instead remove the calculation of barShift and add x/ybar to
|
|
% the axis so that pgf determines it automatically.
|
|
|
|
% From http://www.mathworks.com/help/techdoc/ref/bar.html:
|
|
% bar(...,width) sets the relative bar width and controls the
|
|
% separation of bars within a group. The default width is 0.8, so if
|
|
% you do not specify X, the bars within a group have a slight
|
|
% separation. If width is 1, the bars within a group touch one
|
|
% another. The value of width must be a scalar.
|
|
assumedBarWidth = groupWidth/numBarSeries; % assumption
|
|
barWidth = getBarWidthInAbsolutUnits(h) * assumedBarWidth;
|
|
|
|
% Bar type
|
|
drawOptions = opts_add(drawOptions, barType);
|
|
|
|
% Bar width
|
|
% Relative width does not work with categorical data
|
|
if ~isCategoricalType(get(h, 'XData'))
|
|
drawOptions = opts_add(drawOptions, 'bar width', formatDim(barWidth, ''));
|
|
end
|
|
|
|
% The bar shift auto feature was introduced in pgfplots 1.13
|
|
m2t = needsPgfplotsVersion(m2t, [1,13]);
|
|
m2t = m2t_addAxisOption(m2t, 'bar shift auto');
|
|
case 'stacked' % stacked plots
|
|
% Pass option to parent axis & disallow anything but stacked plots
|
|
% Make sure this happens exactly *once*.
|
|
|
|
if ~m2t.axes{end}.barAddedAxisOption;
|
|
barWidth = getBarWidthInAbsolutUnits(h);
|
|
m2t = m2t_addAxisOption(m2t, 'bar width', formatDim(barWidth,''));
|
|
m2t.axes{end}.barAddedAxisOption = true;
|
|
end
|
|
|
|
% Somewhere between pgfplots 1.5 and 1.8 and starting
|
|
% again from 1.11, the option {x|y}bar stacked can be applied to
|
|
% \addplot instead of the figure and thus allows to combine stacked
|
|
% bar plots and other kinds of plots in the same axis.
|
|
% Thus, it is advisable to use pgfplots 1.11. In older versions, the
|
|
% plot will only contain a single bar series, but should compile fine.
|
|
m2t = needsPgfplotsVersion(m2t, [1,11]);
|
|
drawOptions = opts_add(drawOptions, [barType ' stacked']);
|
|
otherwise
|
|
error('matlab2tikz:drawBarseries', ...
|
|
'Don''t know how to handle BarLayout ''%s''.', barlayout);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [numBarSeries, barSeriesId] = getNumBarAndId(h)
|
|
% Get number of bars series and bar series id
|
|
prop = switchMatOct('BarPeers', 'bargroup');
|
|
bargroup = get(h, prop);
|
|
numBarSeries = numel(bargroup);
|
|
|
|
if isHG2
|
|
% In HG2, BarPeers are sorted in reverse order wrt HG1
|
|
bargroup = bargroup(end:-1:1);
|
|
|
|
elseif strcmpi(getEnvironment, 'MATLAB')
|
|
% In HG1, h is a double but bargroup a graphic object. Cast h to a
|
|
% graphic object
|
|
h = handle(h);
|
|
|
|
else
|
|
% In Octave, the bargroup is a replicated cell array. Pick first
|
|
if iscell(bargroup)
|
|
bargroup = bargroup{1};
|
|
end
|
|
end
|
|
|
|
% Get bar series Id
|
|
[dummy, barSeriesId] = ismember(h, bargroup);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t,str] = drawBaseline(m2t,hparent,isVertical)
|
|
% DRAWBASELINE Draws baseline for bar and stem plots
|
|
%
|
|
% Notes:
|
|
% - In HG2, the baseline is a specific object child of a bar or stem
|
|
% plot. So, handleAllChildren() won't find a line in the axes to plot as
|
|
% the baseline.
|
|
% - The baseline is horizontal for vertical bar and stem plots and is
|
|
% vertical for horixontal barplots. The ISVERTICAL input refers to the
|
|
% baseline.
|
|
% - We do not plot baselines with a BaseValue different from 0 because
|
|
% pgfplots does not support shifts in the BaseValue, e.g. see #438.
|
|
% We either implement our own data shifting or wait for pgfplots.
|
|
|
|
if ~exist('isVertical','var')
|
|
isVertical = false;
|
|
end
|
|
|
|
str = '';
|
|
baseValue = get(hparent, 'BaseValue');
|
|
if isOff(get(hparent,'ShowBaseLine')) || ~isHG2() || baseValue ~= 0
|
|
return
|
|
end
|
|
|
|
hBaseLine = get(hparent,'BaseLine');
|
|
|
|
% Line options of the baseline
|
|
[m2t, lineOptions] = getLineOptions(m2t, hparent);
|
|
color = get(hBaseLine, 'Color');
|
|
[m2t, lineColor] = getColor(m2t, hBaseLine, color, 'patch');
|
|
|
|
drawOptions = opts_new();
|
|
drawOptions = opts_add(drawOptions, 'forget plot');
|
|
drawOptions = opts_add(drawOptions, 'color', lineColor);
|
|
drawOptions = opts_merge(drawOptions, lineOptions);
|
|
|
|
% Get data
|
|
if isVertical
|
|
xData = repmat(baseValue,1,2);
|
|
yData = get(m2t.current.gca,'Ylim');
|
|
else
|
|
xData = get(m2t.current.gca,'Xlim');
|
|
yData = repmat(baseValue,1,2);
|
|
end
|
|
|
|
[m2t, table, tableOptions] = makeTable(m2t, '', xData, '', yData);
|
|
|
|
% Print out
|
|
drawOpts = opts_print(drawOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\addplot[%s] table[%s] {%s};\n', ...
|
|
drawOpts, tabOpts, table);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawAreaSeries(m2t, h, custom)
|
|
% Takes care of MATLAB's area plots.
|
|
%
|
|
% TODO Get rid of code duplication with 'drawAxes'.
|
|
|
|
% Do nothing if plot is invisible
|
|
str = '';
|
|
if ~isVisible(h)
|
|
return;
|
|
end
|
|
|
|
% Init drawOptions
|
|
drawOptions = opts_new();
|
|
|
|
% Get the draw options for the bars
|
|
[m2t, drawOptions] = getPatchDrawOptions(m2t, h, drawOptions);
|
|
|
|
if ~isfield(m2t, 'addedAreaOption') || isempty(m2t.addedAreaOption) || ~m2t.addedAreaOption
|
|
% Add 'area style' to axes options.
|
|
m2t = m2t_addAxisOption(m2t, 'area style');
|
|
m2t = m2t_addAxisOption(m2t, 'stack plots', 'y');
|
|
m2t.addedAreaOption = true;
|
|
end
|
|
|
|
% Toggle legend entry
|
|
drawOptions = maybeShowInLegend(m2t.currentHandleHasLegend, drawOptions);
|
|
|
|
% Generate the tikz table
|
|
xData = get(h, 'XData');
|
|
yData = get(h, 'YData');
|
|
[m2t, table, tableOptions] = makeTable(m2t, '', xData, '', yData);
|
|
|
|
drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
|
|
|
|
% Print out
|
|
drawOpts = opts_print(drawOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\addplot[%s] table[%s]{%s}\n\\closedcycle;\n',...
|
|
drawOpts, tabOpts, table);
|
|
%TODO: shouldn't this be "\addplot[] table[] {}" instead?
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawStemSeries(m2t, h, custom)
|
|
[m2t, str] = drawStemOrStairSeries_(m2t, h, 'ycomb', custom);
|
|
|
|
% TODO: handle baseplane with stem3()
|
|
if m2t.axes{end}.is3D
|
|
return
|
|
end
|
|
[m2t, baseline] = drawBaseline(m2t,h);
|
|
str = [str, baseline];
|
|
end
|
|
function [m2t, str] = drawStairSeries(m2t, h, custom)
|
|
[m2t, str] = drawStemOrStairSeries_(m2t, h, 'const plot', custom);
|
|
end
|
|
function [m2t, str] = drawStemOrStairSeries_(m2t, h, plotType, custom)
|
|
|
|
% Do nothing if plot is invisible
|
|
str = '';
|
|
if ~isLineVisible(h)
|
|
return % nothing to plot!
|
|
end
|
|
|
|
% deal with draw options
|
|
color = get(h, 'Color');
|
|
[m2t, plotColor] = getColor(m2t, h, color, 'patch');
|
|
|
|
[m2t, lineOptions] = getLineOptions(m2t, h);
|
|
[m2t, markerOptions] = getMarkerOptions(m2t, h);
|
|
|
|
drawOptions = opts_new();
|
|
drawOptions = opts_add(drawOptions, plotType);
|
|
drawOptions = opts_add(drawOptions, 'color', plotColor);
|
|
drawOptions = opts_merge(drawOptions, lineOptions, markerOptions);
|
|
|
|
% Toggle legend entry
|
|
drawOptions = maybeShowInLegend(m2t.currentHandleHasLegend, drawOptions);
|
|
drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
|
|
|
|
drawOpts = opts_print(drawOptions);
|
|
|
|
% Generate the tikz table
|
|
xData = get(h, 'XData');
|
|
yData = get(h, 'YData');
|
|
if m2t.axes{end}.is3D
|
|
% TODO: account for hgtransform
|
|
zData = get(h, 'ZData');
|
|
[m2t, table, tableOptions] = makeTable(m2t, '', xData, '', yData, '', zData);
|
|
% Print out
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\addplot3 [%s]\n table[%s] {%s};\n ', ...
|
|
drawOpts, tabOpts, table);
|
|
else
|
|
[m2t, table, tableOptions] = makeTable(m2t, '', xData, '', yData);
|
|
% Print out
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\addplot[%s] table[%s] {%s};\n', ...
|
|
drawOpts, tabOpts, table);
|
|
end
|
|
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawQuiverGroup(m2t, h, custom)
|
|
% Takes care of MATLAB's quiver plots.
|
|
str = '';
|
|
|
|
[x,y,z,u,v,w] = getAndRescaleQuivers(m2t,h);
|
|
is3D = m2t.axes{end}.is3D;
|
|
|
|
% prepare output
|
|
if is3D
|
|
name = 'addplot3';
|
|
else % 2D plotting
|
|
name = 'addplot';
|
|
end
|
|
|
|
variables = {'x', 'y', 'z', 'u', 'v', 'w'};
|
|
data = NaN(numel(x),6);
|
|
data(:,1) = x;
|
|
data(:,2) = y;
|
|
data(:,3) = z;
|
|
data(:,4) = u;
|
|
data(:,5) = v;
|
|
data(:,6) = w;
|
|
|
|
if ~is3D
|
|
data(:,[3 6]) = []; % remove Z-direction
|
|
variables([3 6]) = [];
|
|
end
|
|
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
% gather the arrow options
|
|
showArrowHead = get(h, 'ShowArrowHead');
|
|
if ~isLineVisible(h) && ~showArrowHead
|
|
return
|
|
end
|
|
|
|
plotOptions = opts_new();
|
|
if showArrowHead
|
|
plotOptions = opts_add(plotOptions, '-Straight Barb');
|
|
signalDependency(m2t, 'tikzlibrary', 'arrows.meta');
|
|
else
|
|
plotOptions = opts_add(plotOptions, '-');
|
|
end
|
|
|
|
% Append the arrow style to the TikZ options themselves.
|
|
color = get(h, 'Color');
|
|
[m2t, lineOptions] = getLineOptions(m2t, h);
|
|
[m2t, arrowcolor] = getColor(m2t, h, color, 'patch');
|
|
plotOptions = opts_add(plotOptions, 'color', arrowcolor);
|
|
plotOptions = opts_merge(plotOptions, lineOptions);
|
|
|
|
% Define the quiver settings
|
|
quiverOptions = opts_new();
|
|
quiverOptions = opts_add(quiverOptions, 'u', '\thisrow{u}');
|
|
quiverOptions = opts_add(quiverOptions, 'v', '\thisrow{v}');
|
|
if is3D
|
|
quiverOptions = opts_add(quiverOptions, 'w', '\thisrow{w}');
|
|
arrowLength = '{sqrt((\thisrow{u})^2+(\thisrow{v})^2+(\thisrow{w})^2)}';
|
|
else
|
|
arrowLength = '{sqrt((\thisrow{u})^2+(\thisrow{v})^2)}';
|
|
end
|
|
plotOptions = opts_add(plotOptions, 'point meta', arrowLength);
|
|
plotOptions = opts_add(plotOptions, 'point meta min', '0');
|
|
|
|
if showArrowHead
|
|
arrowHeadOptions = opts_new();
|
|
|
|
% In MATLAB (HG1), the arrow head is constructed to have an angle of
|
|
% approximately 18.263 degrees in 2D as can be derived from the
|
|
% |quiver| function.
|
|
% In 3D, the angle is no longer constant but it is approximately
|
|
% the same as for 2D quiver plots. So let's make our life easy.
|
|
% |test/examples/example_quivers.m| covers the calculations.
|
|
arrowHeadOptions = opts_add(arrowHeadOptions, 'angle''', '18.263');
|
|
|
|
%TODO: scale the arrows more rigorously to match MATLAB behavior
|
|
% Currently, this is quite hard to do, since the size of the arrows
|
|
% is defined in pgfplots in absolute units (here we specify that those
|
|
% should be scaled up/down according to the data), while the data itself
|
|
% is in axis coordinates (or some scaled variant). I.e. we need the
|
|
% physical dimensions of the axis to compute the correct scaling!
|
|
%
|
|
% There is a "MaxHeadSize" property that plays a role.
|
|
% MaxHeadSize is said to be relative to the length of the quiver in the
|
|
% MATLAB documentation. However, in practice, there seems to be a SQRT
|
|
% involved somewhere (e.g. if u.^2 + v.^2 == 2, all MHS values >
|
|
% 1/sqrt(2) are capped to 1/sqrt(2)).
|
|
%
|
|
% NOTE: `set(h, 'MaxHeadSize')` is bugged in HG1 (not in HG2 or Octave)
|
|
% according to http://www.mathworks.com/matlabcentral/answers/96754
|
|
|
|
userInfo(m2t, ['Please change the "arrowHeadSize" option', ...
|
|
' if the size of the arrows is incorrect.']);
|
|
arrowHeadSize = sprintf(m2t.ff, abs(m2t.args.arrowHeadSize));
|
|
|
|
% Write out the actual scaling for TikZ.
|
|
% `\pgfplotspointsmetatransformed` is in the range [0, 1000], so
|
|
% divide by this span (as is done in the pgfplots manual) to normalize
|
|
% the arrow head size. First divide to avoid overflows.
|
|
arrowHeadOptions = opts_add(arrowHeadOptions, 'scale', ...
|
|
['{' arrowHeadSize '/1000*\pgfplotspointmetatransformed}']);
|
|
|
|
headStyle = ['-{Straight Barb[' opts_print(arrowHeadOptions) ']}'];
|
|
quiverOptions = opts_add(quiverOptions, 'every arrow/.append style', ...
|
|
['{' headStyle '}']);
|
|
end
|
|
plotOptions = opts_addSubOpts(plotOptions, 'quiver', quiverOptions);
|
|
plotOptions = opts_append_userdefined(plotOptions, custom.extraOptions);
|
|
|
|
[m2t, table, tableOptions] = makeTable(m2t, variables, data);
|
|
|
|
% Print out
|
|
plotOpts = opts_print(plotOptions);
|
|
tabOpts = opts_print(tableOptions);
|
|
str = sprintf('\\%s[%s]\n table[%s] {%s};\n', ...
|
|
name, plotOpts, tabOpts, table);
|
|
end
|
|
% ==============================================================================
|
|
function [x,y,z,u,v,w] = getAndRescaleQuivers(m2t, h)
|
|
% get and rescale the arrows from a quivergroup object
|
|
x = get(h, 'XData');
|
|
y = get(h, 'YData');
|
|
z = getOrDefault(h, 'ZData', []);
|
|
|
|
u = get(h, 'UData');
|
|
v = get(h, 'VData');
|
|
w = getOrDefault(h, 'WData', []);
|
|
|
|
is3D = m2t.axes{end}.is3D;
|
|
if ~is3D
|
|
z = 0;
|
|
w = 0;
|
|
end
|
|
|
|
% MATLAB uses a scaling algorithm to determine the size of the arrows.
|
|
% Before R2014b, the processed coordinates were available. This is no longer
|
|
% the case, so we have to re-implement it. In MATLAB it is implemented in
|
|
% the |quiver3| (and |quiver|) function.
|
|
if any(size(x)==1)
|
|
nX = sqrt(numel(x)); nY = nX;
|
|
else
|
|
[nY, nX] = size(x);
|
|
end
|
|
range = @(xyzData)(max(xyzData(:)) - min(xyzData(:)));
|
|
euclid = @(x,y,z)(sqrt(x.^2 + y.^2 + z.^2));
|
|
dx = range(x)/nX;
|
|
dy = range(y)/nY;
|
|
dz = range(z)/max(nX,nY);
|
|
dd = euclid(dx, dy, dz);
|
|
if dd > 0
|
|
vectorLength = euclid(u/dd,v/dd,w/dd);
|
|
maxLength = max(vectorLength(:));
|
|
else
|
|
maxLength = 1;
|
|
end
|
|
if isOn(getOrDefault(h, 'AutoScale', 'on'))
|
|
scaleFactor = getOrDefault(h,'AutoScaleFactor', 0.9) / maxLength;
|
|
else
|
|
scaleFactor = 1;
|
|
end
|
|
x = x(:).'; u = u(:).'*scaleFactor;
|
|
y = y(:).'; v = v(:).'*scaleFactor;
|
|
z = z(:).'; w = w(:).'*scaleFactor;
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawErrorBars(m2t, h, custom)
|
|
% Takes care of MATLAB/Octave's error bar plots.
|
|
[m2t, str] = drawLine(m2t, h, custom);
|
|
% Even though this only calls |drawLine|, let's keep this wrapper
|
|
% such that the code is easier to read where it is called.
|
|
end
|
|
% ==============================================================================
|
|
function [xDeviations] = getXDeviations(h)
|
|
% Retrieves left/right uncertainty data
|
|
|
|
rightDev = getOrDefault(h, 'XPositiveDelta', []);
|
|
leftDev = getOrDefault(h, 'XNegativeDelta', []);
|
|
|
|
xDeviations = [rightDev(:), leftDev(:)];
|
|
end
|
|
% ==============================================================================
|
|
function [yDeviations] = getYDeviations(h)
|
|
% Retrieves upper/lower uncertainty data
|
|
|
|
upDev = getOrDefault(h, 'YPositiveDelta', []);
|
|
loDev = getOrDefault(h, 'YNegativeDelta', []);
|
|
|
|
if isempty(upDev)
|
|
upDev = getOrDefault(h, 'UData', []);
|
|
loDev = getOrDefault(h, 'LData', []);
|
|
end
|
|
|
|
yDeviations = [upDev(:), loDev(:)];
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawEllipse(m2t, handle, custom)
|
|
% Takes care of MATLAB's ellipse annotations.
|
|
|
|
drawOptions = opts_new();
|
|
|
|
p = get(handle,'position');
|
|
radius = p([3 4]) / 2;
|
|
center = p([1 2]) + radius;
|
|
|
|
color = get(handle, 'Color');
|
|
[m2t, xcolor] = getColor(m2t, handle, color, 'patch');
|
|
[m2t, lineOptions] = getLineOptions(m2t, handle);
|
|
|
|
filling = get(handle, 'FaceColor');
|
|
|
|
% Has a filling?
|
|
if isNone(filling)
|
|
drawOptions = opts_add(drawOptions, xcolor);
|
|
drawCommand = '\draw';
|
|
else
|
|
[m2t, xcolorF] = getColor(m2t, handle, filling, 'patch');
|
|
drawOptions = opts_add(drawOptions, 'draw', xcolor);
|
|
drawOptions = opts_add(drawOptions, 'fill', xcolorF);
|
|
|
|
drawCommand = '\filldraw';
|
|
end
|
|
drawOptions = opts_merge(drawOptions, lineOptions);
|
|
drawOptions = opts_append_userdefined(drawOptions, custom.extraOptions);
|
|
|
|
opt = opts_print(drawOptions);
|
|
|
|
str = sprintf('%s [%s] (axis cs:%g,%g) ellipse [x radius=%g, y radius=%g];\n', ...
|
|
drawCommand, opt, center, radius);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, str] = drawTextarrow(m2t, handle, custom)
|
|
% Takes care of MATLAB's textarrow annotations.
|
|
|
|
% handleAllChildren to draw the arrow
|
|
[m2t, str] = handleAllChildren(m2t, handle);
|
|
|
|
% handleAllChildren ignores the text, unless hidden strings are shown
|
|
if ~m2t.args.showHiddenStrings
|
|
child = findall(handle, 'type', 'text');
|
|
[m2t, str{end+1}] = drawText(m2t, child, custom);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, drawOptions] = getPatchDrawOptions(m2t, h, drawOptions)
|
|
% Determines the reoccurring draw options usually applied when drawing
|
|
% a patch/area/bar. These include EdgeColor, LineType, FaceColor/Alpha
|
|
|
|
% Get object for color;
|
|
if ~isempty(allchild(h))
|
|
% quite oddly, before MATLAB R2014b this value is stored in a child
|
|
% patch and not in the object itself
|
|
obj = allchild(h);
|
|
else % R2014b and newer
|
|
obj = h;
|
|
end
|
|
|
|
% Get the object type
|
|
type = get(h, 'Type');
|
|
|
|
% Face Color (inside of area)
|
|
faceColor = get(obj, 'FaceColor');
|
|
[m2t, drawOptions] = setColor(m2t, h, drawOptions, 'fill', faceColor, 'none');
|
|
|
|
% FaceAlpha (Not applicable for MATLAB2014a/b)
|
|
faceAlpha = getOrDefault(h, 'FaceAlpha', 'none');
|
|
if ~isNone(faceColor) && isnumeric(faceAlpha) && faceAlpha ~= 1.0
|
|
drawOptions = opts_add(drawOptions, 'fill opacity', sprintf(m2t.ff,faceAlpha));
|
|
end
|
|
|
|
% Define linestyle
|
|
[lineStyle, isDefaultLS] = getAndCheckDefault(type, h, 'LineStyle', '-');
|
|
if isNone(lineStyle)
|
|
drawOptions = opts_add(drawOptions, 'draw', 'none');
|
|
elseif ~isDefaultLS
|
|
drawOptions = opts_add(drawOptions, translateLineStyle(lineStyle));
|
|
end
|
|
|
|
% Check for the edge color. Only plot it if it is different from the
|
|
% face color and if there is a linestyle
|
|
edgeColor = get(h, 'EdgeColor');
|
|
if ~isNone(lineStyle) && ~isNone(edgeColor) && ~strcmpi(edgeColor,faceColor)
|
|
[m2t, drawOptions] = setColor(m2t, h, drawOptions, 'draw', edgeColor, 'none');
|
|
end
|
|
|
|
% Add 'area legend' to the options as otherwise the legend indicators
|
|
% will just highlight the edges.
|
|
if strcmpi(type, 'bar') || strcmpi(type, 'histogram')
|
|
drawOptions = opts_add(drawOptions, 'area legend');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function out = linearFunction(X, Y)
|
|
% Return the linear function that goes through (X[1], Y[1]), (X[2], Y[2]).
|
|
out = @(x) (Y(2,:)*(x-X(1)) + Y(1,:)*(X(2)-x)) / (X(2)-X(1));
|
|
end
|
|
% ==============================================================================
|
|
function matlabColormap = pgfplots2matlabColormap(points, rgb, numColors)
|
|
% Translates a Pgfplots colormap to a MATLAB color map.
|
|
matlabColormap = zeros(numColors, 3);
|
|
% Point indices between which to interpolate.
|
|
I = [1, 2];
|
|
f = linearFunction(points(I), rgb(I,:));
|
|
for k = 1:numColors
|
|
x = (k-1)/(numColors-1) * points(end);
|
|
if x > points(I(2))
|
|
I = I + 1;
|
|
f = linearFunction(points(I), rgb(I,:));
|
|
end
|
|
matlabColormap(k,:) = f(x);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function pgfplotsColormap = matlab2pgfplotsColormap(m2t, matlabColormap, name)
|
|
% Translates a MATLAB color map into a Pgfplots colormap.
|
|
|
|
if nargin < 3 || isempty(name), name = 'mymap'; end
|
|
|
|
% First check if we could use a default Pgfplots color map.
|
|
% Unfortunately, MATLAB and Pgfplots color maps will never exactly coincide
|
|
% except to the most simple cases such as blackwhite. This is because of a
|
|
% slight incompatibility of Pgfplots and MATLAB colormaps:
|
|
% In MATLAB, indexing goes from 1 through 64, whereas in Pgfplots you can
|
|
% specify any range, the default ones having something like
|
|
% (0: red, 1: yellow, 2: blue).
|
|
% To specify this exact color map in MATLAB, one would have to put 'red' at
|
|
% 1, blue at 64, and yellow in the middle of the two, 32.5 that is.
|
|
% Not really sure how MATLAB rounds here: 32, 33? Anyways, it will be
|
|
% slightly off and hence not match the Pgfplots color map.
|
|
% As a workaround, build the MATLAB-formatted colormaps of Pgfplots default
|
|
% color maps, and check if matlabColormap is close to it. If yes, take it.
|
|
|
|
% For now, comment out the color maps which haven't landed yet in Pgfplots.
|
|
pgfmaps = { %struct('name', 'colormap/autumn', ...
|
|
% 'points', [0,1], ...
|
|
% 'values', [[1,0,0];[1,1,0]]), ...
|
|
%struct('name', 'colormap/bled', ...
|
|
% 'points', 0:6, ...
|
|
% 'values', [[0,0,0];[43,43,0];[0,85,0];[0,128,128];[0,0,170];[213,0,213];[255,0,0]]/255), ...
|
|
%struct('name', 'colormap/bright', ...
|
|
% 'points', 0:7, ...
|
|
% 'values', [[0,0,0];[78,3,100];[2,74,255];[255,21,181];[255,113,26];[147,213,114];[230,255,0];[255,255,255]]/255), ...
|
|
%struct('name', 'colormap/bone', ...
|
|
% 'points', [0,3,6,8], ...
|
|
% 'values', [[0,0,0];[84,84,116];[167,199,199];[255,255,255]]/255), ...
|
|
%struct('name', 'colormap/cold', ...
|
|
% 'points', 0:3, ...
|
|
% 'values', [[0,0,0];[0,0,1];[0,1,1];[1,1,1]]), ...
|
|
%struct('name', 'colormap/copper', ...
|
|
% 'points', [0,4,5], ...
|
|
% 'values', [[0,0,0];[255,159,101];[255,199,127]]/255), ...
|
|
%struct('name', 'colormap/copper2', ...
|
|
% 'points', 0:4, ...
|
|
% 'values', [[0,0,0];[68,62,63];[170,112,95];[207,194,138];[255,255,255]]/255), ...
|
|
%struct('name', 'colormap/hsv', ...
|
|
% 'points', 0:6, ...
|
|
% 'values', [[1,0,0];[1,1,0];[0,1,0];[0,1,1];[0,0,1];[1,0,1];[1,0,0]]), ...
|
|
struct('name', 'colormap/hot', ...
|
|
'points', 0:3, ...
|
|
'values', [[0,0,1];[1,1,0];[1,0.5,0];[1,0,0]]), ... % TODO check this
|
|
struct('name', 'colormap/hot2', ...
|
|
'points', [0,3,6,8], ...
|
|
'values', [[0,0,0];[1,0,0];[1,1,0];[1,1,1]]), ...
|
|
struct('name', 'colormap/jet', ...
|
|
'points', [0,1,3,5,7,8], ...
|
|
'values', [[0,0,128];[0,0,255];[0,255,255];[255,255,0];[255,0,0];[128,0,0]]/255), ...
|
|
struct('name', 'colormap/blackwhite', ...
|
|
'points', [0,1], ...
|
|
'values', [[0,0,0];[1,1,1]]), ...
|
|
struct('name', 'colormap/bluered', ...
|
|
'points', 0:5, ...
|
|
'values', [[0,0,180];[0,255,255];[100,255,0];[255,255,0];[255,0,0];[128,0,0]]/255), ...
|
|
struct('name', 'colormap/cool', ...
|
|
'points', [0,1,2], ...
|
|
'values', [[255,255,255];[0,128,255];[255,0,255]]/255), ...
|
|
struct('name', 'colormap/greenyellow', ...
|
|
'points', [0,1], ...
|
|
'values', [[0,128,0];[255,255,0]]/255), ...
|
|
struct('name', 'colormap/redyellow', ...
|
|
'points', [0,1], ...
|
|
'values', [[255,0,0];[255,255,0]]/255), ...
|
|
struct('name', 'colormap/violet', ...
|
|
'points', [0,1,2], ...
|
|
'values', [[25,25,122];[255,255,255];[238,140,238]]/255) ...
|
|
};
|
|
|
|
% The tolerance is a subjective matter of course.
|
|
% Some figures:
|
|
% * The norm-distance between MATLAB's gray and bone is 6.8e-2.
|
|
% * The norm-distance between MATLAB's jet and Pgfplots's jet is 2.8e-2.
|
|
% * The norm-distance between MATLAB's hot and Pgfplots's hot2 is 2.1e-2.
|
|
tol = 5.0e-2;
|
|
|
|
for map = pgfmaps
|
|
numColors = size(matlabColormap, 1);
|
|
mmap = pgfplots2matlabColormap(map{1}.points, map{1}.values, numColors);
|
|
alpha = norm(matlabColormap - mmap) / sqrt(numColors);
|
|
if alpha < tol
|
|
userInfo(m2t, 'Found %s to be a pretty good match for your color map (||diff||=%g).', ...
|
|
map{1}.name, alpha);
|
|
pgfplotsColormap = map{1}.name;
|
|
return
|
|
end
|
|
end
|
|
|
|
% Build a custom color map.
|
|
% Loop over the data, stop at each spot where the linear
|
|
% interpolation is interrupted, and set a color mark there.
|
|
m = size(matlabColormap, 1);
|
|
steps = [1, 2];
|
|
% A colormap with a single color is valid in MATLAB but an error in
|
|
% pgfplots. Repeating the color produces the desired effect in this
|
|
% case.
|
|
if m==1
|
|
colors=[matlabColormap(1,:);matlabColormap(1,:)];
|
|
else
|
|
colors = [matlabColormap(1,:); matlabColormap(2,:)];
|
|
f = linearFunction(steps, colors);
|
|
k = 3;
|
|
while k <= m
|
|
if norm(matlabColormap(k,:) - f(k)) > 1.0e-10
|
|
% Add the previous step to the color list
|
|
steps(end) = k-1;
|
|
colors(end,:) = matlabColormap(k-1,:);
|
|
steps = [steps, k];
|
|
colors = [colors; matlabColormap(k,:)];
|
|
f = linearFunction(steps(end-1:end), colors(end-1:end,:));
|
|
end
|
|
k = k+1;
|
|
end
|
|
steps(end) = m;
|
|
colors(end,:) = matlabColormap(m,:);
|
|
end
|
|
|
|
% Get it in Pgfplots-readable form.
|
|
unit = 'pt';
|
|
colSpecs = cell(length(steps), 1);
|
|
for k = 1:length(steps)
|
|
x = steps(k)-1;
|
|
colSpecs{k} = sprintf('rgb(%d%s)=(%g,%g,%g)', x, unit, colors(k,:));
|
|
end
|
|
pgfplotsColormap = sprintf('colormap={%s}{[1%s] %s}',name, unit, join(m2t, colSpecs, '; '));
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, fontStyle] = getFontStyle(m2t, handle)
|
|
fontStyle = '';
|
|
if strcmpi(get(handle, 'FontWeight'),'Bold')
|
|
fontStyle = sprintf('%s\\bfseries',fontStyle);
|
|
end
|
|
if strcmpi(get(handle, 'FontAngle'), 'Italic')
|
|
fontStyle = sprintf('%s\\itshape',fontStyle);
|
|
end
|
|
if ~all(get(handle, 'Color')==0)
|
|
color = get(handle, 'Color');
|
|
[m2t, col] = getColor(m2t, handle, color, 'patch');
|
|
fontStyle = sprintf('%s\\color{%s}', fontStyle, col);
|
|
end
|
|
if m2t.args.strictFontSize
|
|
fontSize = get(handle,'FontSize');
|
|
fontUnits = matlab2texUnits(get(handle,'FontUnits'), 'pt');
|
|
fontStyle = sprintf('\\fontsize{%d%s}{1em}%s\\selectfont',fontSize,fontUnits,fontStyle);
|
|
else
|
|
% don't try to be smart and "translate" MATLAB font sizes to proper LaTeX
|
|
% ones: it cannot be done. LaTeX uses semantic sizes (e.g. \small)
|
|
% whose actual dimensions depend on the document style, context, ...
|
|
end
|
|
|
|
if ~isempty(fontStyle)
|
|
fontStyle = opts_add(opts_new, 'font', fontStyle);
|
|
else
|
|
fontStyle = opts_new();
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function axisOptions = getColorbarOptions(m2t, handle)
|
|
|
|
% begin collecting axes options
|
|
axisOptions = opts_new();
|
|
cbarStyleOptions = opts_new();
|
|
|
|
[cbarTemplate, cbarStyleOptions] = getColorbarPosOptions(handle, ...
|
|
cbarStyleOptions);
|
|
|
|
% axis label and direction
|
|
if isHG2
|
|
% VERSION: Starting from R2014b there is only one field `label`.
|
|
% The colorbar's position determines, if it should be a x- or y-label.
|
|
|
|
if strcmpi(cbarTemplate, 'horizontal')
|
|
labelOption = 'xlabel';
|
|
else
|
|
labelOption = 'ylabel';
|
|
end
|
|
[m2t, cbarStyleOptions] = getLabel(m2t, handle, cbarStyleOptions, labelOption);
|
|
|
|
% direction
|
|
dirString = get(handle, 'Direction');
|
|
if ~strcmpi(dirString, 'normal') % only if not 'normal'
|
|
if strcmpi(cbarTemplate, 'horizontal')
|
|
dirOption = 'x dir';
|
|
else
|
|
dirOption = 'y dir';
|
|
end
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, dirOption, dirString);
|
|
end
|
|
|
|
% TODO HG2: colorbar ticks and colorbar tick labels
|
|
|
|
else
|
|
% VERSION: Up to MATLAB R2014a and OCTAVE
|
|
[m2t, xo] = getAxisOptions(m2t, handle, 'x');
|
|
[m2t, yo] = getAxisOptions(m2t, handle, 'y');
|
|
xyo = opts_merge(xo, yo);
|
|
xyo = opts_remove(xyo, 'xmin','xmax','xtick','ymin','ymax','ytick');
|
|
|
|
cbarStyleOptions = opts_merge(cbarStyleOptions, xyo);
|
|
end
|
|
|
|
% title
|
|
[m2t, cbarStyleOptions] = getTitle(m2t, handle, cbarStyleOptions);
|
|
|
|
if m2t.args.strict
|
|
% Sampled colors.
|
|
numColors = size(m2t.current.colormap, 1);
|
|
axisOptions = opts_add(axisOptions, 'colorbar sampled');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'samples', ...
|
|
sprintf('%d', numColors+1));
|
|
|
|
if ~isempty(cbarTemplate)
|
|
userWarning(m2t, ...
|
|
- 'Pgfplots cannot deal with more than one colorbar option yet.');
|
|
%FIXME: can we get sampled horizontal color bars to work?
|
|
%FIXME: sampled colorbars should be inferred, not by using strict!
|
|
end
|
|
end
|
|
|
|
% Merge them together in axisOptions.
|
|
axisOptions = opts_add(axisOptions, strtrim(['colorbar ', cbarTemplate]));
|
|
|
|
if ~isempty(cbarStyleOptions)
|
|
axisOptions = opts_addSubOpts(axisOptions, ...
|
|
'colorbar style', cbarStyleOptions);
|
|
end
|
|
|
|
% do _not_ handle colorbar's children
|
|
end
|
|
% ==============================================================================
|
|
function [cbarTemplate, cbarStyleOptions] = getColorbarPosOptions(handle, cbarStyleOptions)
|
|
% set position, ticks etc. of a colorbar
|
|
loc = get(handle, 'Location');
|
|
cbarTemplate = '';
|
|
|
|
switch lower(loc) % case insensitive (MATLAB: CamelCase, Octave: lower case)
|
|
case 'north'
|
|
cbarTemplate = 'horizontal';
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
|
|
'{(0.5,0.97)}');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor',...
|
|
'north');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions,...
|
|
'xticklabel pos', 'lower');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'width',...
|
|
'0.97*\pgfkeysvalueof{/pgfplots/parent axis width}');
|
|
case 'south'
|
|
cbarTemplate = 'horizontal';
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
|
|
'{(0.5,0.03)}');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor', ...
|
|
'south');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, ...
|
|
'xticklabel pos','upper');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'width',...
|
|
'0.97*\pgfkeysvalueof{/pgfplots/parent axis width}');
|
|
case 'east'
|
|
cbarTemplate = 'right';
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
|
|
'{(0.97,0.5)}');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor', ...
|
|
'east');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, ...
|
|
'xticklabel pos','left');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'width',...
|
|
'0.97*\pgfkeysvalueof{/pgfplots/parent axis width}');
|
|
case 'west'
|
|
cbarTemplate = 'left';
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
|
|
'{(0.03,0.5)}');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor',...
|
|
'west');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions,...
|
|
'xticklabel pos', 'right');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'width',...
|
|
'0.97*\pgfkeysvalueof{/pgfplots/parent axis width}');
|
|
case 'eastoutside'
|
|
%cbarTemplate = 'right';
|
|
case 'westoutside'
|
|
cbarTemplate = 'left';
|
|
case 'northoutside'
|
|
% TODO move to top
|
|
cbarTemplate = 'horizontal';
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
|
|
'{(0.5,1.03)}');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor',...
|
|
'south');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions,...
|
|
'xticklabel pos', 'upper');
|
|
case 'southoutside'
|
|
cbarTemplate = 'horizontal';
|
|
case 'manual'
|
|
origUnits = get(handle,'Units');
|
|
assocAxes = get(handle,'Axes');
|
|
origAxesUnits = get(assocAxes,'Units');
|
|
set(handle,'Units','centimeters'); % Make sure we have
|
|
set(assocAxes,'Units','centimeters'); % same units
|
|
cbarDim = pos2dims(get(handle,'Position'));
|
|
cbarAxesDim = pos2dims(get(assocAxes,'Position'));
|
|
set(handle,'Units',origUnits); % Restore original
|
|
set(assocAxes,'Units',origAxesUnits); % units
|
|
|
|
center = @(dims) (dims.left + dims.right)/2;
|
|
centerCbar = center(cbarDim);
|
|
centerAxes = center(cbarAxesDim);
|
|
|
|
% Cases of colorbar axis locations (in or out) depending on center
|
|
% of colorbar relative to the center it's associated axes.
|
|
% According to matlab manual (R2016a) colorbars with Location 'manual'
|
|
% can only be vertical.
|
|
axisLoc = getOrDefault(handle, 'AxisLocation', 'out');
|
|
if centerCbar < centerAxes
|
|
if strcmp(axisLoc,'in')
|
|
cbarTemplate = 'right';
|
|
else
|
|
cbarTemplate = 'left';
|
|
end
|
|
else
|
|
if strcmp(axisLoc,'in')
|
|
cbarTemplate = 'left';
|
|
else
|
|
cbarTemplate = 'right';
|
|
end
|
|
end
|
|
|
|
% Using positions relative to associated axes
|
|
calcRelPos = @(pos1,pos2,ext2) (pos1-pos2)/ext2;
|
|
cbarRelPosX = calcRelPos(cbarDim.left,cbarAxesDim.left,cbarAxesDim.width);
|
|
cbarRelPosY = calcRelPos(cbarDim.bottom,cbarAxesDim.bottom,cbarAxesDim.height);
|
|
cbarRelHeight = cbarDim.height/cbarAxesDim.height;
|
|
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'anchor',...
|
|
'south west');
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'at',...
|
|
['{(' formatDim(cbarRelPosX) ','...
|
|
formatDim(cbarRelPosY) ')}']);
|
|
cbarStyleOptions = opts_add(cbarStyleOptions, 'height',...
|
|
[formatDim(cbarRelHeight),...
|
|
'*\pgfkeysvalueof{/pgfplots/parent axis height}']);
|
|
|
|
otherwise
|
|
error('matlab2tikz:getColorOptions:unknownLocation',...
|
|
'getColorbarOptions: Unknown ''Location'' %s.', loc)
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, xcolor] = getColor(m2t, handle, color, mode)
|
|
% Handles MATLAB colors and makes them available to TikZ.
|
|
% This includes translation of the color value as well as explicit
|
|
% definition of the color if it is not available in TikZ by default.
|
|
%
|
|
% The variable 'mode' essentially determines what format 'color' can
|
|
% have. Possible values are (as strings) 'patch' and 'image'.
|
|
|
|
% check if the color is straight given in rgb
|
|
% -- notice that we need the extra NaN test with respect to the QUIRK
|
|
% below
|
|
if isRGBTuple(color)
|
|
% everything alright: rgb color here
|
|
[m2t, xcolor] = rgb2colorliteral(m2t, color);
|
|
else
|
|
switch lower(mode)
|
|
case 'patch'
|
|
[m2t, xcolor] = patchcolor2xcolor(m2t, color, handle);
|
|
case 'image'
|
|
|
|
m = size(color,1);
|
|
n = size(color,2);
|
|
xcolor = cell(m, n);
|
|
|
|
if ndims(color) == 3
|
|
for i = 1:m
|
|
for j = 1:n
|
|
[m2t, xc] = rgb2colorliteral(m2t, color(i,j, :));
|
|
xcolor{i, j} = xc;
|
|
end
|
|
end
|
|
elseif ndims(color) <= 2
|
|
[m2t, colorindex] = cdata2colorindex(m2t, color, handle);
|
|
for i = 1:m
|
|
for j = 1:n
|
|
[m2t, xc] = rgb2colorliteral(m2t, m2t.current.colormap(colorindex(i,j), :));
|
|
xcolor{i, j} = xc;
|
|
end
|
|
end
|
|
else
|
|
error('matlab2tikz:getColor:image:colorDims',...
|
|
'Image color data cannot have more than 3 dimensions');
|
|
end
|
|
otherwise
|
|
error(['matlab2tikz:getColor', ...
|
|
'Argument ''mode'' has illegal value ''%s''.'], ...
|
|
mode);
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, xcolor] = patchcolor2xcolor(m2t, color, patchhandle)
|
|
% Transforms a color of the edge or the face of a patch to an xcolor literal.
|
|
if isnumeric(color)
|
|
[m2t, xcolor] = rgb2colorliteral(m2t, color);
|
|
elseif ischar(color)
|
|
switch color
|
|
case 'flat'
|
|
cdata = getCDataWithFallbacks(patchhandle);
|
|
color1 = cdata(1,1);
|
|
% RGB cdata
|
|
if ndims(cdata) == 3 && all(size(cdata) == [1,1,3])
|
|
[m2t,xcolor] = rgb2colorliteral(m2t, cdata);
|
|
% All same color
|
|
elseif all(isnan(cdata) | abs(cdata-color1)<1.0e-10)
|
|
[m2t, colorindex] = cdata2colorindex(m2t, color1, patchhandle);
|
|
[m2t, xcolor] = rgb2colorliteral(m2t, m2t.current.colormap(colorindex, :));
|
|
else
|
|
% Don't return anything meaningful and count on the caller
|
|
% to make something of it.
|
|
xcolor = [];
|
|
end
|
|
|
|
case 'auto'
|
|
try
|
|
color = get(patchhandle, 'Color');
|
|
catch
|
|
% From R2014b use an undocumented property if Color is
|
|
% not present
|
|
color = get(patchhandle, 'AutoColor');
|
|
end
|
|
[m2t, xcolor] = rgb2colorliteral(m2t, color);
|
|
|
|
case 'none'
|
|
% Before, we used to throw an error here. However, probably this
|
|
% is not necessary and actually harmful (#739).
|
|
xcolor = 'none';
|
|
|
|
otherwise
|
|
error('matlab2tikz:anycolor2rgb:UnknownColorModel',...
|
|
'Don''t know how to handle the color model ''%s''.',color);
|
|
end
|
|
else
|
|
error('patchcolor2xcolor:illegalInput', ...
|
|
'Input argument ''color'' not a string or numeric.');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function cdata = getCDataWithFallbacks(patchhandle)
|
|
% Looks for CData at different places
|
|
cdata = getOrDefault(patchhandle, 'CData', []);
|
|
|
|
if isempty(cdata) || ~isnumeric(cdata)
|
|
child = allchild(patchhandle);
|
|
cdata = get(child, 'CData');
|
|
end
|
|
if isempty(cdata) || ~isnumeric(cdata)
|
|
% R2014b+: CData is implicit by the ordering of the siblings
|
|
siblings = allchild(get(patchhandle, 'Parent'));
|
|
cdata = find(siblings(end:-1:1)==patchhandle);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, colorindex] = cdata2colorindex(m2t, cdata, imagehandle)
|
|
% Transforms a color in CData format to an index in the color map.
|
|
% Only does something if CDataMapping is 'scaled', really.
|
|
|
|
if ~isnumeric(cdata) && ~islogical(cdata)
|
|
error('matlab2tikz:cdata2colorindex:unknownCDataType',...
|
|
'Don''t know how to handle CData ''%s''.',cdata);
|
|
end
|
|
|
|
axeshandle = m2t.current.gca;
|
|
|
|
% -----------------------------------------------------------------------
|
|
% For the following, see, for example, the MATLAB help page for 'image',
|
|
% section 'Image CDataMapping'.
|
|
try
|
|
mapping = get(imagehandle, 'CDataMapping');
|
|
catch
|
|
mapping = 'scaled';
|
|
end
|
|
switch mapping
|
|
case 'scaled'
|
|
% need to scale within clim
|
|
% see MATLAB's manual page for caxis for details
|
|
clim = get(axeshandle, 'clim');
|
|
m = size(m2t.current.colormap, 1);
|
|
colorindex = zeros(size(cdata));
|
|
idx1 = cdata <= clim(1);
|
|
idx2 = cdata >= clim(2);
|
|
idx3 = ~idx1 & ~idx2;
|
|
colorindex(idx1) = 1;
|
|
colorindex(idx2) = m;
|
|
% cdata may be of type uint8. Convert to double to avoid
|
|
% getting binary indices
|
|
colorindex(idx3) = fix(double(cdata(idx3)-clim(1)) / (clim(2)-clim(1)) *m) ...
|
|
+ 1;
|
|
case 'direct'
|
|
% direct index
|
|
colorindex = cdata;
|
|
|
|
otherwise
|
|
error('matlab2tikz:anycolor2rgb:unknownCDataMapping',...
|
|
'Unknown CDataMapping ''%s''.',cdatamapping);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, key, legendOpts] = getLegendOpts(m2t, handle)
|
|
lStyle = opts_new();
|
|
|
|
lStyle = getLegendPosition(m2t, handle, lStyle);
|
|
lStyle = getLegendOrientation(m2t, handle, lStyle);
|
|
lStyle = getLegendEntryAlignment(m2t, handle, lStyle);
|
|
|
|
% If the plot has 'legend boxoff', we have the 'not visible'
|
|
% property, so turn off line and background fill.
|
|
if ~isVisible(handle) || isOff(get(handle,'box'))
|
|
lStyle = opts_add(lStyle, 'fill', 'none');
|
|
lStyle = opts_add(lStyle, 'draw', 'none');
|
|
else
|
|
% handle colors
|
|
[edgeColor, isDfltEdge] = getAndCheckDefault('Legend', handle, ...
|
|
'EdgeColor', [1 1 1]);
|
|
if isNone(edgeColor)
|
|
lStyle = opts_add(lStyle, 'draw', 'none');
|
|
|
|
elseif ~isDfltEdge
|
|
[m2t, col] = getColor(m2t, handle, edgeColor, 'patch');
|
|
lStyle = opts_add(lStyle, 'draw', col);
|
|
end
|
|
|
|
[fillColor, isDfltFill] = getAndCheckDefault('Legend', handle, ...
|
|
'Color', [1 1 1]);
|
|
if isNone(fillColor)
|
|
lStyle = opts_add(lStyle, 'fill', 'none');
|
|
|
|
elseif ~isDfltFill
|
|
[m2t, col] = getColor(m2t, handle, fillColor, 'patch');
|
|
lStyle = opts_add(lStyle, 'fill', col);
|
|
end
|
|
end
|
|
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
key = 'legend style';
|
|
legendOpts = opts_print(lStyle);
|
|
legendOpts = ['{', legendOpts, '}'];
|
|
%TODO: just pass out the `lStyle` instead of `legendOpts`
|
|
end
|
|
% ==============================================================================
|
|
function [lStyle] = getLegendOrientation(m2t, handle, lStyle)
|
|
% handle legend orientation
|
|
ori = get(handle, 'Orientation');
|
|
switch lower(ori)
|
|
case 'horizontal'
|
|
numLegendEntries = sprintf('%d',length(get(handle, 'String')));
|
|
lStyle = opts_add(lStyle, 'legend columns', numLegendEntries);
|
|
|
|
case 'vertical'
|
|
% Use default.
|
|
otherwise
|
|
userWarning(m2t, [' Unknown legend orientation ''',ori,'''' ...
|
|
'. Choosing default (vertical).']);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [lStyle] = getLegendPosition(m2t, handle, lStyle)
|
|
% handle legend location
|
|
% #COMPLEX: just a big switch-case
|
|
loc = get(handle, 'Location');
|
|
dist = 0.03; % distance to to axes in normalized coordinates
|
|
% MATLAB(R)'s keywords are camel cased (e.g., 'NorthOutside'), in Octave
|
|
% small cased ('northoutside'). Hence, use lower() for uniformity.
|
|
switch lower(loc)
|
|
case 'northeast'
|
|
return % don't do anything in this (default) case
|
|
case 'northwest'
|
|
position = [dist, 1-dist];
|
|
anchor = 'north west';
|
|
case 'southwest'
|
|
position = [dist, dist];
|
|
anchor = 'south west';
|
|
case 'southeast'
|
|
position = [1-dist, dist];
|
|
anchor = 'south east';
|
|
case 'north'
|
|
position = [0.5, 1-dist];
|
|
anchor = 'north';
|
|
case 'east'
|
|
position = [1-dist, 0.5];
|
|
anchor = 'east';
|
|
case 'south'
|
|
position = [0.5, dist];
|
|
anchor = 'south';
|
|
case 'west'
|
|
position = [dist, 0.5];
|
|
anchor = 'west';
|
|
case 'northoutside'
|
|
position = [0.5, 1+dist];
|
|
anchor = 'south';
|
|
case 'southoutside'
|
|
position = [0.5, -dist];
|
|
anchor = 'north';
|
|
case 'eastoutside'
|
|
position = [1+dist, 0.5];
|
|
anchor = 'west';
|
|
case 'westoutside'
|
|
position = [-dist, 0.5];
|
|
anchor = 'east';
|
|
case 'northeastoutside'
|
|
position = [1+dist, 1];
|
|
anchor = 'north west';
|
|
case 'northwestoutside'
|
|
position = [-dist, 1];
|
|
anchor = 'north east';
|
|
case 'southeastoutside'
|
|
position = [1+dist, 0];
|
|
anchor = 'south west';
|
|
case 'southwestoutside'
|
|
position = [-dist, 0];
|
|
anchor = 'south east';
|
|
case 'none'
|
|
legendPos = get(handle, 'Position');
|
|
unit = get(handle, 'Units');
|
|
if isequal(unit, 'normalized')
|
|
position = legendPos(1:2);
|
|
else
|
|
% Calculate where the legend is located w.r.t. the axes.
|
|
axesPos = get(m2t.current.gca, 'Position');
|
|
axesUnit = get(m2t.current.gca, 'Units');
|
|
% Convert to legend unit
|
|
axesPos = convertUnits(axesPos, axesUnit, unit);
|
|
% By default, the axes position is given w.r.t. to the figure,
|
|
% and so is the legend.
|
|
position = (legendPos(1:2)-axesPos(1:2)) ./ axesPos(3:4);
|
|
end
|
|
anchor = 'south west';
|
|
case {'best','bestoutside'}
|
|
% TODO: Implement these.
|
|
% The position could be determined by means of 'Position' and/or
|
|
% 'OuterPosition' of the legend handle; in fact, this could be made
|
|
% a general principle for all legend placements.
|
|
userWarning(m2t, [sprintf(' Option ''%s'' not yet implemented.',loc), ...
|
|
' Choosing default.']);
|
|
return % use defaults
|
|
|
|
otherwise
|
|
userWarning(m2t, [' Unknown legend location ''',loc,'''' ...
|
|
'. Choosing default.']);
|
|
return % use defaults
|
|
end
|
|
|
|
% set legend position
|
|
%TODO: shouldn't this include units?
|
|
lStyle = opts_add(lStyle, 'at', sprintf('{(%s,%s)}', ...
|
|
formatDim(position(1)), formatDim(position(2))));
|
|
lStyle = opts_add(lStyle, 'anchor', anchor);
|
|
|
|
end
|
|
% ==============================================================================
|
|
function [lStyle] = getLegendEntryAlignment(m2t, handle, lStyle)
|
|
% determines the text and picture alignment inside a legend
|
|
textalign = '';
|
|
pictalign = '';
|
|
switch getEnvironment
|
|
case 'Octave'
|
|
% Octave allows to change the alignment of legend text and
|
|
% pictograms using legend('left') and legend('right')
|
|
textpos = get(handle, 'textposition');
|
|
switch lower(textpos)
|
|
case 'left'
|
|
% pictogram right of flush right text
|
|
textalign = 'right';
|
|
pictalign = 'right';
|
|
case 'right'
|
|
% pictogram left of flush left text (default)
|
|
textalign = 'left';
|
|
pictalign = 'left';
|
|
otherwise
|
|
userWarning(m2t, ...
|
|
['Unknown legend text position ''',...
|
|
textpos, '''. Choosing default.']);
|
|
end
|
|
case 'MATLAB'
|
|
% does not specify text/pictogram alignment in legends
|
|
otherwise
|
|
errorUnknownEnvironment();
|
|
end
|
|
|
|
% set alignment of legend text and pictograms, if available
|
|
if ~isempty(textalign) && ~isempty(pictalign)
|
|
lStyle = opts_add(lStyle, 'legend cell align', textalign);
|
|
lStyle = opts_add(lStyle, 'align', textalign);
|
|
lStyle = opts_add(lStyle, 'legend plot pos', pictalign);
|
|
else
|
|
% Make sure the entries are flush left (default MATLAB behavior).
|
|
% This is also import for multiline legend entries: Without alignment
|
|
% specification, the TeX document won't compile.
|
|
% 'legend plot pos' is not set explicitly, since 'left' is default.
|
|
lStyle = opts_add(lStyle, 'legend cell align', 'left');
|
|
lStyle = opts_add(lStyle, 'align', 'left');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [pTicks, pTickLabels] = ...
|
|
matlabTicks2pgfplotsTicks(m2t, ticks, tickLabels, isLogAxis, tickLabelMode)
|
|
% Converts MATLAB style ticks and tick labels to pgfplots style (if needed)
|
|
if isempty(ticks)
|
|
pTicks = '\empty';
|
|
pTickLabels = [];
|
|
return
|
|
end
|
|
|
|
% set ticks + labels
|
|
pTicks = join(m2t, num2cell(ticks), ',');
|
|
|
|
% if there's no specific labels, return empty
|
|
if isempty(tickLabels) || (length(tickLabels)==1 && isempty(tickLabels{1}))
|
|
pTickLabels = '\empty';
|
|
return
|
|
end
|
|
|
|
% sometimes tickLabels are cells, sometimes plain arrays
|
|
% -- unify this to cells
|
|
if ischar(tickLabels)
|
|
tickLabels = strtrim(mat2cell(tickLabels, ...
|
|
ones(size(tickLabels,1), 1), ...
|
|
size(tickLabels, 2) ...
|
|
) ...
|
|
);
|
|
end
|
|
|
|
ticks = removeSuperfluousTicks(ticks, tickLabels);
|
|
|
|
isNeeded = isTickLabelsNecessary(m2t, ticks, tickLabels, isLogAxis);
|
|
|
|
pTickLabels = formatPgfTickLabels(m2t, isNeeded, tickLabels, ...
|
|
isLogAxis, tickLabelMode);
|
|
end
|
|
% ==============================================================================
|
|
function bool = isTickLabelsNecessary(m2t, ticks, tickLabels, isLogAxis)
|
|
% Check if tickLabels are really necessary (and not already covered by
|
|
% the tick values themselves).
|
|
bool = false;
|
|
|
|
k = find(ticks ~= 0.0, 1); % get an index with non-zero tick value
|
|
if isLogAxis || isempty(k) % only a 0-tick
|
|
scalingFactor = 1;
|
|
else
|
|
% When plotting axis, MATLAB might scale the axes by a factor of ten,
|
|
% say 10^n, and plot a 'x 10^k' next to the respective axis. This is
|
|
% common practice when the tick marks are really large or small
|
|
% numbers.
|
|
% Unfortunately, MATLAB doesn't contain the information about the
|
|
% scaling anywhere in the plot, and at the same time the {x,y}TickLabels
|
|
% are given as t*10^k, thus no longer corresponding to the actual
|
|
% value t.
|
|
% Try to find the scaling factor here. This is then used to check
|
|
% whether or not explicit {x,y}TickLabels are really necessary.
|
|
s = str2double(tickLabels{k});
|
|
scalingFactor = ticks(k)/s;
|
|
% check if the factor is indeed a power of 10
|
|
S = log10(scalingFactor);
|
|
if abs(round(S)-S) > m2t.tol
|
|
scalingFactor = 1.0;
|
|
end
|
|
end
|
|
|
|
for k = 1:min(length(ticks),length(tickLabels))
|
|
% Don't use str2num here as then, literal strings as 'pi' get
|
|
% legally transformed into 3.14... and the need for an explicit
|
|
% label will not be recognized. str2double returns a NaN for 'pi'.
|
|
if isLogAxis
|
|
s = 10^(str2double(tickLabels{k}));
|
|
else
|
|
s = str2double(tickLabels{k});
|
|
end
|
|
if isnan(s) || abs(ticks(k)-s*scalingFactor) > m2t.tol
|
|
bool = true;
|
|
return;
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function pTickLabels = formatPgfTickLabels(m2t, plotLabelsNecessary, ...
|
|
tickLabels, isLogAxis, tickLabelMode)
|
|
% formats the tick labels for pgfplots
|
|
if plotLabelsNecessary
|
|
for k = 1:length(tickLabels)
|
|
% Turn tickLabels from cells containing a cell into
|
|
% cells containing strings
|
|
if isnumeric(tickLabels{k})
|
|
tickLabels(k) = num2str(tickLabels{k});
|
|
elseif iscell(tickLabels{k})
|
|
tickLabels(k) = tickLabels{k};
|
|
end
|
|
% If the axis is logscaled, MATLAB does not store the labels,
|
|
% but the exponents to 10
|
|
if isLogAxis && strcmpi(tickLabelMode,'auto')
|
|
tickLabels{k} = sprintf('$10^{%s}$', str);
|
|
end
|
|
end
|
|
tickLabels = cellfun(@(l)(sprintf('{%s}',l)), tickLabels, ...
|
|
'UniformOutput', false);
|
|
pTickLabels = join(m2t, tickLabels, ',');
|
|
else
|
|
pTickLabels = [];
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function ticks = removeSuperfluousTicks(ticks, tickLabels)
|
|
% What MATLAB does when the number of ticks and tick labels is not the same,
|
|
% is somewhat unclear. Cut of the first entries to fix bug
|
|
% https://github.com/matlab2tikz/matlab2tikz/issues/161,
|
|
m = length(ticks);
|
|
n = length(tickLabels);
|
|
if n < m
|
|
ticks = ticks(m-n+1:end);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function tikzLineStyle = translateLineStyle(matlabLineStyle)
|
|
if(~ischar(matlabLineStyle))
|
|
error('matlab2tikz:translateLineStyle:NotAString',...
|
|
'Variable matlabLineStyle is not a string.');
|
|
end
|
|
|
|
switch (matlabLineStyle)
|
|
case 'none'
|
|
tikzLineStyle = '';
|
|
case '-'
|
|
tikzLineStyle = 'solid';
|
|
case '--'
|
|
tikzLineStyle = 'dashed';
|
|
case ':'
|
|
tikzLineStyle = 'dotted';
|
|
case '-.'
|
|
tikzLineStyle = 'dashdotted';
|
|
otherwise
|
|
error('matlab2tikz:translateLineStyle:UnknownLineStyle',...
|
|
'Unknown matlabLineStyle ''%s''.', matlabLineStyle);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, table, opts] = makeTable(m2t, varargin)
|
|
% [m2t,table,opts] = makeTable(m2t, 'name1', data1, 'name2', data2, ...)
|
|
% [m2t,table,opts] = makeTable(m2t, {'name1','name2',...}, {data1, data2, ...})
|
|
% [m2t,table,opts] = makeTable(m2t, {'name1','name2',...}, [data1(:), data2(:), ...])
|
|
%
|
|
% Returns m2t structure, formatted table and table options.
|
|
% When all the names are empty, no header is printed
|
|
[variables, data] = parseInputsForTable_(varargin{:});
|
|
opts = opts_new();
|
|
|
|
COLSEP = sprintf('\t');
|
|
if m2t.args.externalData
|
|
ROWSEP = sprintf('\n');
|
|
else
|
|
ROWSEP = sprintf('\\\\\n');
|
|
opts = opts_add(opts, 'row sep','crcr');
|
|
end
|
|
|
|
nColumns = numel(data);
|
|
nRows = cellfun(@numel, data);
|
|
if ~all(nRows==nRows(1))
|
|
error('matlab2tikz:makeTableDifferentNumberOfRows',...
|
|
'Different data lengths [%s].', num2str(nRows));
|
|
end
|
|
nRows = nRows(1);
|
|
|
|
FORMAT = repmat({m2t.ff}, 1, nColumns);
|
|
FORMAT(cellfun(@isCellOrChar, data)) = {'%s'};
|
|
FORMAT(cellfun(@isCategoricalType, data)) = {'%s'};
|
|
FORMAT = join(m2t, FORMAT, COLSEP);
|
|
if all(cellfun(@isempty, variables))
|
|
header = {};
|
|
else
|
|
header = {join(m2t, variables, COLSEP)};
|
|
end
|
|
|
|
table = cell(nRows,1);
|
|
for iRow = 1:nRows
|
|
thisData = cell(1,nColumns);
|
|
for jCol = 1:nColumns
|
|
thisData{1,jCol} = data{jCol}(iRow);
|
|
end
|
|
table{iRow} = sprintf(FORMAT, thisData{:});
|
|
end
|
|
% convert NaN and Inf to lower case for TikZ
|
|
table = regexprep(table, '\<NaN\>', 'nan');
|
|
table = regexprep(table, '\<Inf\>', 'inf');
|
|
table = [join(m2t, [header;table], ROWSEP) ROWSEP];
|
|
|
|
if m2t.args.externalData
|
|
% output data to external file
|
|
[m2t, fileNum] = incrementGlobalCounter(m2t, 'tsvFile');
|
|
[filename, latexFilename] = externalFilename(m2t, fileNum, '.tsv');
|
|
|
|
% write the data table to an external file
|
|
fid = fileOpenForWrite(m2t, filename);
|
|
finally_fclose_fid = onCleanup(@() fclose(fid));
|
|
|
|
fprintf(fid, '%s', table);
|
|
|
|
% put the filename in the TikZ output
|
|
table = latexFilename;
|
|
else
|
|
% output data with "%newline" prepended for formatting consistency
|
|
% do NOT prepend another newline in the output: LaTeX will crash.
|
|
table = sprintf('%%\n%s', table);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [variables, data] = parseInputsForTable_(varargin)
|
|
% parse input arguments for |makeTable|
|
|
if numel(varargin) == 2 % cell syntax
|
|
variables = varargin{1};
|
|
data = varargin{2};
|
|
if ischar(variables)
|
|
% one variable, one data vector -> (cell, cell)
|
|
variables = {variables};
|
|
data = {data};
|
|
elseif iscellstr(variables) && ~iscell(data)
|
|
% multiple variables, one data matrix -> (cell, cell) by column
|
|
data = num2cell(data, 1);
|
|
end
|
|
else % key-value syntax
|
|
variables = varargin(1:2:end-1);
|
|
data = varargin(2:2:end);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [path, texpath] = externalFilename(m2t, counter, extension)
|
|
% generates a file name for an external data file and its relative TeX path
|
|
|
|
[dummy, name] = fileparts(m2t.tikzFileName); %#ok
|
|
baseFilename = [name '-' num2str(counter) extension];
|
|
path = fullfile(m2t.dataPath, baseFilename);
|
|
texpath = TeXpath(fullfile(m2t.relativeDataPath, baseFilename));
|
|
end
|
|
% ==============================================================================
|
|
function [names,definitions] = dealColorDefinitions(mergedColorDefs)
|
|
if isempty(mergedColorDefs)
|
|
mergedColorDefs = {};
|
|
end
|
|
[names,definitions] = cellfun(@(x)(deal(x{:})), mergedColorDefs, ...
|
|
'UniformOutput', false);
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, colorLiteral] = rgb2colorliteral(m2t, rgb)
|
|
% Translates an rgb value to an xcolor literal
|
|
%
|
|
% Possible outputs:
|
|
% - xcolor literal color, e.g. 'blue'
|
|
% - mixture of 2 previously defined colors, e.g. 'red!70!green'
|
|
% - a newly defined color, e.g. 'mycolor10'
|
|
|
|
% Take a look at xcolor.sty for the color definitions.
|
|
% In xcolor.sty some colors are defined in CMYK space and approximated
|
|
% crudely for RGB color space. So it is better to redefine those colors
|
|
% instead of using xcolor's:
|
|
% 'cyan' , 'magenta', 'yellow', 'olive'
|
|
% [0,1,1], [1,0,1] , [1,1,0] , [0.5,0.5,0]
|
|
|
|
xcolColorNames = {'white', 'black', 'red', 'green', 'blue', ...
|
|
'brown', 'lime', 'orange', 'pink', ...
|
|
'purple', 'teal', 'violet', ...
|
|
'darkgray', 'gray', 'lightgray'};
|
|
xcolColorSpecs = {[1,1,1], [0,0,0], [1,0,0], [0,1,0], [0,0,1], ...
|
|
[0.75,0.5,0.25], [0.75,1,0], [1,0.5,0], [1,0.75,0.75], ...
|
|
[0.75,0,0.25], [0,0.5,0.5], [0.5,0,0.5], ...
|
|
[0.25,0.25,0.25], [0.5,0.5,0.5], [0.75,0.75,0.75]};
|
|
|
|
colorNames = [xcolColorNames, m2t.color.extraNames];
|
|
colorSpecs = [xcolColorSpecs, m2t.color.extraSpecs];
|
|
|
|
%% check if rgb is a predefined color
|
|
for kColor = 1:length(colorSpecs)
|
|
Ck = colorSpecs{kColor}(:);
|
|
if max(abs(Ck - rgb(:))) < m2t.color.precision
|
|
colorLiteral = colorNames{kColor};
|
|
return % exact color was predefined
|
|
end
|
|
end
|
|
|
|
%% check if the color is a linear combination of two already defined colors
|
|
for iColor = 1:length(colorSpecs)
|
|
for jColor = iColor+1:length(colorSpecs)
|
|
Ci = colorSpecs{iColor}(:);
|
|
Cj = colorSpecs{jColor}(:);
|
|
|
|
% solve color mixing equation `Ck = p * Ci + (1-p) * Cj` for p
|
|
p = (Ci-Cj) \ (rgb(:)-Cj);
|
|
p = round(100*p)/100; % round to a percentage
|
|
Ck = p * Ci + (1-p)*Cj; % approximated mixed color
|
|
|
|
if p <= 1 && p >= 0 && max(abs(Ck(:) - rgb(:))) < m2t.color.precision
|
|
colorLiteral = sprintf('%s!%d!%s', colorNames{iColor}, round(p*100), ...
|
|
colorNames{jColor});
|
|
return % linear combination found
|
|
end
|
|
end
|
|
end
|
|
|
|
%% Define colors that are not a linear combination of two known colors
|
|
colorLiteral = sprintf('mycolor%d', length(m2t.color.extraNames)+1);
|
|
m2t.color.extraNames{end+1} = colorLiteral;
|
|
m2t.color.extraSpecs{end+1} = rgb;
|
|
end
|
|
% ==============================================================================
|
|
function newstr = join(m2t, cellstr, delimiter)
|
|
% This function joins a cell of strings to a single string (with a
|
|
% given delimiter in between two strings, if desired).
|
|
%
|
|
% Example of usage:
|
|
% join(m2t, cellstr, ',')
|
|
newstr = m2tstrjoin(cellstr, delimiter, m2t.ff);
|
|
end
|
|
% ==============================================================================
|
|
function [width, height, unit] = getNaturalFigureDimension(m2t)
|
|
% Returns the size of figure (in inch)
|
|
% To stay compatible with getNaturalAxesDimensions, the unit 'in' is
|
|
% also returned.
|
|
|
|
% Get current figure size
|
|
figuresize = get(m2t.current.gcf, 'Position');
|
|
figuresize = figuresize([3 4]);
|
|
figureunit = get(m2t.current.gcf, 'Units');
|
|
|
|
% Convert Figure Size
|
|
unit = 'in';
|
|
figuresize = convertUnits(figuresize, figureunit, unit);
|
|
|
|
% Split size into width and height
|
|
width = figuresize(1);
|
|
height = figuresize(2);
|
|
|
|
end
|
|
% ==============================================================================
|
|
function dimension = getFigureDimensions(m2t, widthString, heightString)
|
|
% Returns the physical dimension of the figure.
|
|
|
|
[width, height, unit] = getNaturalFigureDimension(m2t);
|
|
|
|
% get the natural width-height ration of the plot
|
|
axesWidthHeightRatio = width / height;
|
|
% check matlab2tikz arguments
|
|
if ~isempty(widthString)
|
|
width = extractValueUnit(widthString);
|
|
end
|
|
if ~isempty(heightString)
|
|
height = extractValueUnit(heightString);
|
|
end
|
|
|
|
% prepare the output
|
|
if ~isempty(widthString) && ~isempty(heightString)
|
|
dimension.x.unit = width.unit;
|
|
dimension.x.value = width.value;
|
|
dimension.y.unit = height.unit;
|
|
dimension.y.value = height.value;
|
|
elseif ~isempty(widthString)
|
|
dimension.x.unit = width.unit;
|
|
dimension.x.value = width.value;
|
|
dimension.y.unit = width.unit;
|
|
dimension.y.value = width.value / axesWidthHeightRatio;
|
|
elseif ~isempty(heightString)
|
|
dimension.y.unit = height.unit;
|
|
dimension.y.value = height.value;
|
|
dimension.x.unit = height.unit;
|
|
dimension.x.value = height.value * axesWidthHeightRatio;
|
|
else % neither width nor height given
|
|
dimension.x.unit = unit;
|
|
dimension.x.value = width;
|
|
dimension.y.unit = unit;
|
|
dimension.y.value = height;
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function position = getAxesPosition(m2t, handle, widthString, heightString, axesBoundingBox)
|
|
% Returns the physical position of the axes. This includes - in difference
|
|
% to the Dimension - also an offset to shift the axes inside the figure
|
|
% An optional bounding box can be used to omit empty borders.
|
|
|
|
% Deal with optional parameter
|
|
if nargin < 4
|
|
axesBoundingBox = [0 0 1 1];
|
|
end
|
|
|
|
% First get the whole figures size
|
|
figDim = getFigureDimensions(m2t, widthString, heightString);
|
|
|
|
% Get the relative position of the axis
|
|
relPos = getRelativeAxesPosition(m2t, handle, axesBoundingBox);
|
|
|
|
position.x.value = relPos(1) * figDim.x.value;
|
|
position.x.unit = figDim.x.unit;
|
|
position.y.value = relPos(2) * figDim.y.value;
|
|
position.y.unit = figDim.y.unit;
|
|
position.w.value = relPos(3) * figDim.x.value;
|
|
position.w.unit = figDim.x.unit;
|
|
position.h.value = relPos(4) * figDim.y.value;
|
|
position.h.unit = figDim.y.unit;
|
|
end
|
|
% ==============================================================================
|
|
function [position] = getRelativeAxesPosition(m2t, axesHandles, axesBoundingBox)
|
|
% Returns the relative position of axes within the figure.
|
|
% Position is an (n,4) matrix with [minX, minY, width, height] for each
|
|
% handle. All these values are relative to the figure size, which means
|
|
% that [0, 0, 1, 1] covers the whole figure.
|
|
% It is possible to add a second parameter with the relative coordinates of
|
|
% a bounding box around all axes of the figure (see getRelevantAxes()). In
|
|
% this case, relative positions are rescaled so that the bounding box is
|
|
% [0, 0, 1, 1]
|
|
|
|
% Get Figure Dimension
|
|
[figWidth, figHeight, figUnits] = getNaturalFigureDimension(m2t);
|
|
|
|
% Initialize position
|
|
position = zeros(numel(axesHandles), 4);
|
|
% Iterate over all handles
|
|
for i = 1:numel(axesHandles)
|
|
axesHandle = axesHandles(i);
|
|
axesPos = get(axesHandle, 'Position');
|
|
axesUnits = get(axesHandle, 'Units');
|
|
if isequal(lower(axesUnits), 'normalized')
|
|
% Position is already relative
|
|
position(i,:) = axesPos;
|
|
else
|
|
% Convert figure size into axes units
|
|
figureSize = convertUnits([figWidth, figHeight], figUnits, axesUnits);
|
|
% Figure size into axes units to get the relative size
|
|
position(i,:) = axesPos ./ [figureSize, figureSize];
|
|
|
|
end
|
|
|
|
if strcmpi(get(axesHandle, 'DataAspectRatioMode'), 'manual') ...
|
|
|| strcmpi(get(axesHandle, 'PlotBoxAspectRatioMode'), 'manual')
|
|
|
|
if strcmpi(get(axesHandle,'Projection'),'Perspective')
|
|
userWarning(m2t,'Perspective projections are not currently supported')
|
|
end
|
|
|
|
% project vertices of 3d plot box (this results in 2d coordinates in
|
|
% an absolute coordinate system that is scaled proportionally by
|
|
% Matlab to fit the axes position box)
|
|
switch getEnvironment()
|
|
case 'MATLAB'
|
|
projection = view(axesHandle);
|
|
|
|
case 'Octave'
|
|
% Unfortunately, Octave does not have the full `view`
|
|
% interface implemented, but the projection matrices are
|
|
% available: http://octave.1599824.n4.nabble.com/Implementing-view-td3032041.html
|
|
|
|
projection = get(axesHandle, 'x_viewtransform');
|
|
|
|
otherwise
|
|
errorUnknownEnvironment();
|
|
end
|
|
|
|
|
|
vertices = projection * [0, 1, 0, 0, 1, 1, 0, 1;
|
|
0, 0, 1, 0, 1, 0, 1, 1;
|
|
0, 0, 0, 1, 0, 1, 1, 1;
|
|
1, 1, 1, 1, 1, 1, 1, 1];
|
|
|
|
% each of the columns of vertices represents a vertex of the 3D axes
|
|
% but we only need their XY coordinates
|
|
verticesXY = vertices([1 2], :);
|
|
|
|
% the size of the projected plot box is limited by the long diagonals
|
|
% The matrix A determines the connectivity, e.g. the first diagonal runs from vertices(:,3) -> vertices(:,4)
|
|
A = [ 0, 0, 0, -1, +1, 0, 0, 0;
|
|
0, 0, -1, 0, 0, +1, 0, 0;
|
|
0, -1, 0, 0, 0, 0, +1, 0;
|
|
-1, 0, 0, 0, 0, 0, 0, +1];
|
|
diagonals = verticesXY * A';
|
|
% each of the columns of this matrix contains a the X and Y distance of a diagonal
|
|
dimensions = max(abs(diagonals), [], 2);
|
|
|
|
% find limiting dimension and adjust position
|
|
aspectRatio = dimensions(2) * figWidth / (dimensions(1) * figHeight);
|
|
axesAspectRatio = position(i,4) / position(i,3);
|
|
if aspectRatio > axesAspectRatio
|
|
newWidth = position(i,4) / aspectRatio;
|
|
% Center Axis
|
|
offset = (position(i,3) - newWidth) / 2;
|
|
position(i,1) = position(i,1) + offset;
|
|
% Store new width
|
|
position(i,3) = newWidth;
|
|
else
|
|
newHeight = position(i,3) * aspectRatio;
|
|
offset = (position(i,4) - newHeight) / 2;
|
|
position(i,2) = position(i,2) + offset;
|
|
% Store new height
|
|
position(i,4) = newHeight;
|
|
end
|
|
end
|
|
end
|
|
|
|
%% Rescale if axesBoundingBox is given
|
|
if exist('axesBoundingBox','var')
|
|
% shift position so that [0, 0] is the lower left corner of the
|
|
% bounding box
|
|
position(:,1) = position(:,1) - axesBoundingBox(1);
|
|
position(:,2) = position(:,2) - axesBoundingBox(2);
|
|
% Recale
|
|
position(:,[1 3]) = position(:,[1 3]) / max(axesBoundingBox([3 4]));
|
|
position(:,[2 4]) = position(:,[2 4]) / max(axesBoundingBox([3 4]));
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function aspectRatio = getPlotBoxAspectRatio(axesHandle)
|
|
limits = axis(axesHandle);
|
|
if any(isinf(limits))
|
|
aspectRatio = get(axesHandle,'PlotBoxAspectRatio');
|
|
else
|
|
% DataAspectRatio has priority
|
|
dataAspectRatio = get(axesHandle,'DataAspectRatio');
|
|
nlimits = length(limits)/2;
|
|
limits = reshape(limits, 2, nlimits);
|
|
aspectRatio = abs(limits(2,:) - limits(1,:))./dataAspectRatio(1:nlimits);
|
|
aspectRatio = aspectRatio/min(aspectRatio);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function texUnits = matlab2texUnits(matlabUnits, fallbackValue)
|
|
switch matlabUnits
|
|
case 'pixels'
|
|
texUnits = 'px'; % only in pdfTex/LuaTeX
|
|
case 'centimeters'
|
|
texUnits = 'cm';
|
|
case 'characters'
|
|
texUnits = 'em';
|
|
case 'points'
|
|
texUnits = 'pt';
|
|
case 'inches'
|
|
texUnits = 'in';
|
|
otherwise
|
|
texUnits = fallbackValue;
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function dstValue = convertUnits(srcValue, srcUnit, dstUnit)
|
|
% Converts values between different units.
|
|
% srcValue stores a length (or vector of lengths) in srcUnit.
|
|
% The resulting dstValue is the converted length into dstUnit.
|
|
%
|
|
% Currently supported units are: in, cm, px, pt
|
|
|
|
% Use tex units, if possible (to make things simple)
|
|
srcUnit = matlab2texUnits(lower(srcUnit),lower(srcUnit));
|
|
dstUnit = matlab2texUnits(lower(dstUnit),lower(dstUnit));
|
|
|
|
if isequal(srcUnit, dstUnit)
|
|
dstValue = srcValue;
|
|
return % conversion to the same unit => factor = 1
|
|
end
|
|
|
|
units = {srcUnit, dstUnit};
|
|
factor = ones(1,2);
|
|
for ii = 1:numel(factor) % Same code for srcUnit and dstUnit
|
|
% Use inches as intermediate unit
|
|
% Compute the factor to convert an inch into another unit
|
|
switch units{ii}
|
|
case 'cm'
|
|
factor(ii) = 2.54;
|
|
case 'px'
|
|
factor(ii) = get(0, 'ScreenPixelsPerInch');
|
|
case 'in'
|
|
factor(ii) = 1;
|
|
case 'pt'
|
|
factor(ii) = 72;
|
|
otherwise
|
|
warning('MATLAB2TIKZ:UnknownPhysicalUnit',...
|
|
'Can not convert unit ''%s''. Using conversion factor 1.', units{ii});
|
|
end
|
|
end
|
|
|
|
dstValue = srcValue * factor(2) / factor(1);
|
|
end
|
|
% ==============================================================================
|
|
function out = extractValueUnit(str)
|
|
% Decompose m2t.args.width into value and unit.
|
|
|
|
% Regular expression to match '4.12cm', '\figurewidth', ...
|
|
fp_regex = '[-+]?\d*\.?\d*(?:e[-+]?\d+)?';
|
|
pattern = strcat('(', fp_regex, ')?', '(\\?[a-zA-Z]+)');
|
|
|
|
[dummy,dummy,dummy,dummy,t,dummy] = regexp(str, pattern, 'match'); %#ok
|
|
|
|
if length(t)~=1
|
|
error('getAxesDimensions:illegalLength', ...
|
|
'The width string ''%s'' could not be decomposed into value-unit pair.', str);
|
|
end
|
|
|
|
if length(t{1}) == 1
|
|
out.value = 1.0; % such as in '1.0\figurewidth'
|
|
out.unit = strtrim(t{1}{1});
|
|
elseif length(t{1}) == 2 && isempty(t{1}{1})
|
|
% MATLAB(R) does this:
|
|
% length(t{1})==2 always, but the first field may be empty.
|
|
out.value = 1.0;
|
|
out.unit = strtrim(t{1}{2});
|
|
elseif length(t{1}) == 2
|
|
out.value = str2double(t{1}{1});
|
|
out.unit = strtrim(t{1}{2});
|
|
else
|
|
error('getAxesDimensions:illegalLength', ...
|
|
'The width string ''%s'' could not be decomposed into value-unit pair.', str);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function str = escapeCharacters(str)
|
|
% Replaces "%" and "\" with respectively "%%" and "\\"
|
|
str = strrep(str, '%' , '%%');
|
|
str = strrep(str, '\' , '\\');
|
|
end
|
|
% ==============================================================================
|
|
function bool = isNone(value)
|
|
% Checks whether a value is 'none'
|
|
bool = strcmpi(value, 'none');
|
|
end
|
|
% ==============================================================================
|
|
function bool = isOn(value)
|
|
% Checks whether a value is 'on'
|
|
bool = strcmpi(value, 'on');
|
|
end
|
|
% ==============================================================================
|
|
function bool = isOff(value)
|
|
% Checks whether a value is 'off'.
|
|
% Note that some options are not be solely an on/off boolean, such that `isOn`
|
|
% and isOff don't always return the complement of each other and such that we
|
|
% need both functions to check the value.
|
|
% E.g. `set(0, 'HandleVisibility')` allows the value 'callback'.
|
|
bool = strcmpi(value, 'off');
|
|
end
|
|
% ==============================================================================
|
|
function val = getOrDefault(handle, key, default)
|
|
% gets the value or returns the default value if no such property exists
|
|
if all(isprop(handle, key))
|
|
val = get(handle, key);
|
|
else
|
|
val = default;
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function val = getFactoryOrDefault(type, key, fallback)
|
|
% get factory default value for a certain type of HG object
|
|
% this CANNOT be done using |getOrDefault| as |isprop| doesn't work for
|
|
% factory/default settings. Hence, we use a more expensive try-catch instead.
|
|
try
|
|
groot = 0;
|
|
val = get(groot, ['Factory' type key]);
|
|
catch
|
|
val = fallback;
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [val, isDefault] = getAndCheckDefault(type, handle, key, default)
|
|
% gets the value from a handle of certain type and check the default values
|
|
default = getFactoryOrDefault(type, key, default);
|
|
val = getOrDefault(handle, key, default);
|
|
isDefault = isequal(val, default);
|
|
end
|
|
% ==============================================================================
|
|
function bool = isVisible(handles)
|
|
% Determines whether an object is actually visible or not.
|
|
bool = isOn(get(handles,'Visible'));
|
|
% There's another handle property, 'HandleVisibility', that is unrelated
|
|
% to the "physical" visibility of an object. Rather, it sets whether an
|
|
% object should be visitable by |findobj|. Hence, it is often switched off
|
|
% for non-data objects such as custom axes/grid objects.
|
|
end
|
|
% ==============================================================================
|
|
function [m2t, axesBoundingBox] = getRelevantAxes(m2t, axesHandles)
|
|
% Returns relevant axes. These are defines as visible axes that are no
|
|
% colorbars. Function 'findPlotAxes()' ensures that 'axesHandles' does not
|
|
% contain colorbars. In addition, a bounding box around all relevant Axes is
|
|
% computed. This can be used to avoid undesired borders.
|
|
% This function is the remaining code of alignSubPlots() in the alternative
|
|
% positioning system.
|
|
|
|
% List only visible axes
|
|
N = numel(axesHandles);
|
|
idx = false(N,1);
|
|
for ii = 1:N
|
|
idx(ii) = isVisibleContainer(axesHandles(ii));
|
|
end
|
|
% Store the relevant axes in m2t to simplify querying e.g. positions
|
|
% of subplots
|
|
m2t.relevantAxesHandles = axesHandles(idx);
|
|
|
|
% Compute the bounding box if width or height of the figure are set by
|
|
% parameter
|
|
if ~isempty(m2t.args.width) || ~isempty(m2t.args.height)
|
|
% TODO: check if relevant Axes or all Axes are better.
|
|
axesBoundingBox = getRelativeAxesPosition(m2t, m2t.relevantAxesHandles);
|
|
% Compute second corner from width and height for each axes
|
|
axesBoundingBox(:,[3 4]) = axesBoundingBox(:,[1 2]) + axesBoundingBox(:,[3 4]);
|
|
% Combine axes corners to get the bounding box
|
|
axesBoundingBox = [min(axesBoundingBox(:,[1 2]),[],1), max(axesBoundingBox(:,[3 4]), [], 1)];
|
|
% Compute width and height of the bounding box
|
|
axesBoundingBox(:,[3 4]) = axesBoundingBox(:,[3 4]) - axesBoundingBox(:,[1 2]);
|
|
else
|
|
% Otherwise take the whole figure as bounding box => lengths are
|
|
% not changed in tikz
|
|
axesBoundingBox = [0, 0, 1, 1];
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function userInfo(m2t, message, varargin)
|
|
% Display usage information.
|
|
if m2t.args.showInfo
|
|
mess = sprintf(message, varargin{:});
|
|
|
|
mess = strrep(mess, sprintf('\n'), sprintf('\n *** '));
|
|
fprintf(' *** %s\n', mess);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function userWarning(m2t, message, varargin)
|
|
% Drop-in replacement for warning().
|
|
if m2t.args.showWarnings
|
|
warning('matlab2tikz:userWarning', message, varargin{:});
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function m2t = signalDependency(m2t, dependencyType, name)
|
|
% Signals an (optional) dependency to the user
|
|
switch lower(dependencyType)
|
|
case 'tikzlibrary'
|
|
message = 'Make sure to add "\\usetikzlibrary{%s}" to the preamble.';
|
|
otherwise
|
|
message = 'Please make sure to load the "%s" dependency';
|
|
end
|
|
userInfo(m2t, message, name);
|
|
end
|
|
% ==============================================================================
|
|
function warnAboutParameter(m2t, parameter, isActive, message)
|
|
% warn the user about the use of a dangerous parameter
|
|
line = ['\n' repmat('=',1,80) '\n'];
|
|
if isActive(m2t.args.(parameter))
|
|
userWarning(m2t, [line, 'You are using the "%s" parameter.\n', ...
|
|
message line], parameter);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function parent = addChildren(parent, children)
|
|
if isempty(children)
|
|
return;
|
|
elseif iscell(children)
|
|
for k = 1:length(children)
|
|
parent = addChildren(parent, children{k});
|
|
end
|
|
else
|
|
if isempty(parent.children)
|
|
parent.children = {children};
|
|
else
|
|
parent.children = [parent.children children];
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function printAll(m2t, env, fid)
|
|
if isfield(env, 'colors') && ~isempty(env.colors)
|
|
fprintf(fid, '%s', env.colors);
|
|
end
|
|
|
|
if isempty(env.options)
|
|
fprintf(fid, '\\begin{%s}\n', env.name);
|
|
else
|
|
fprintf(fid, '\\begin{%s}[%%\n%s\n]\n', env.name, ...
|
|
opts_print(env.options, sprintf(',\n')));
|
|
end
|
|
|
|
for item = env.content
|
|
fprintf(fid, '%s', char(item));
|
|
end
|
|
|
|
for k = 1:length(env.children)
|
|
if ischar(env.children{k})
|
|
fprintf(fid, escapeCharacters(env.children{k}));
|
|
else
|
|
fprintf(fid, '\n');
|
|
printAll(m2t, env.children{k}, fid);
|
|
end
|
|
end
|
|
|
|
% End the tikzpicture environment with an empty comment and no newline
|
|
% so no additional space is generated after the tikzpicture in TeX.
|
|
if strcmp(env.name, 'tikzpicture') % LaTeX is case sensitive
|
|
fprintf(fid, '\\end{%s}%%', env.name);
|
|
else
|
|
fprintf(fid, '\\end{%s}\n', env.name);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function c = prettyPrint(m2t, strings, interpreter)
|
|
% Some resources on how MATLAB handles rich (TeX) markup:
|
|
% http://www.mathworks.com/help/techdoc/ref/text_props.html#String
|
|
% http://www.mathworks.com/help/techdoc/creating_plots/f0-4741.html#f0-28104
|
|
% http://www.mathworks.com/help/techdoc/ref/text_props.html#Interpreter
|
|
% http://www.mathworks.com/help/techdoc/ref/text.html#f68-481120
|
|
|
|
% If the user set the matlab2tikz parameter 'parseStrings' to false, no
|
|
% parsing of strings takes place, thus making the user 100% responsible.
|
|
if ~m2t.args.parseStrings
|
|
% If strings is an actual string (labels etc) we need to return a
|
|
% cell containing the string
|
|
c = cellstr(strings);
|
|
return
|
|
end
|
|
|
|
% Make sure we have a valid interpreter set up
|
|
if ~any(strcmpi(interpreter, {'latex', 'tex', 'none'}))
|
|
userWarning(m2t, 'Don''t know interpreter ''%s''. Default handling.', interpreter);
|
|
interpreter = 'tex';
|
|
end
|
|
|
|
strings = cellstrOneLinePerCell(strings);
|
|
|
|
% Now loop over the strings and return them pretty-printed in c.
|
|
c = cell(1, length(strings));
|
|
for k = 1:length(strings)
|
|
% linear indexing for independence of cell array dimensions
|
|
s = strings{k};
|
|
|
|
% The interpreter property of the text element defines how the string
|
|
% is parsed
|
|
switch lower(interpreter)
|
|
case 'latex' % Basic subset of the LaTeX markup language
|
|
|
|
% Replace $$...$$ with $...$ for groups, but otherwise leave
|
|
% untouched.
|
|
% Displaymath \[...\] seems to be unsupported by TikZ/PGF.
|
|
% If this changes, use '\\[$2\\]' as replacement below.
|
|
% Do not escape dollar in replacement string (e.g., "\$$2\$"),
|
|
% since this is not properly handled by octave 3.8.2.
|
|
string = regexprep(s, '(\$\$)(.*?)(\$\$)', '$$2$');
|
|
|
|
case 'tex' % Subset of plain TeX markup language
|
|
|
|
% Deal with UTF8 characters.
|
|
string = s;
|
|
|
|
% degree symbol following "^" or "_" needs to be escaped
|
|
string = regexprep(string, '([\^\_])°', '$1{{}^\\circ}');
|
|
string = strrep(string, '°', '^\circ');
|
|
string = strrep(string, '∞', '\infty');
|
|
|
|
% Parse string piece-wise in a separate function.
|
|
string = parseTexString(m2t, string);
|
|
|
|
case 'none' % Literal characters
|
|
% Make special characters TeX compatible
|
|
|
|
string = strrep(s, '\', '\textbackslash{}');
|
|
% Note: '{' and '}' can't be converted to '\{' and '\}',
|
|
% respectively, via strrep(...) as this would lead to
|
|
% backslashes converted to '\textbackslash\{\}' because
|
|
% the backslash was converted to '\textbackslash{}' in
|
|
% the previous step. Using regular expressions with
|
|
% negative look-behind makes sure any braces in 'string'
|
|
% were not introduced by escaped backslashes.
|
|
% Also keep in mind that escaping braces before backslashes
|
|
% would not remedy the issue -- in that case 'string' would
|
|
% contain backslashes introduced by brace escaping that are
|
|
% not supposed to be printable characters.
|
|
repl = switchMatOct('\\{', '\{');
|
|
string = regexprep(string, '(?<!\\textbackslash){', repl);
|
|
repl = switchMatOct('\\}', '\}');
|
|
string = regexprep(string, '(?<!\\textbackslash{)}', repl);
|
|
string = strrep(string, '$', '\$');
|
|
string = strrep(string, '%', '\%');
|
|
string = strrep(string, '_', '\_');
|
|
string = strrep(string, '^', '\textasciicircum{}');
|
|
string = strrep(string, '#', '\#');
|
|
string = strrep(string, '&', '\&');
|
|
string = strrep(string, '~', '\textasciitilde{}'); % or '\~{}'
|
|
% Clean up: remove superfluous '{}' if it's followed by a backslash
|
|
string = strrep(string, '{}\', '\');
|
|
% Clean up: remove superfluous '{}' at the end of 'string'
|
|
string = regexprep(string, '\{\}$', '');
|
|
|
|
% Make sure to return a string and not a cellstr.
|
|
if iscellstr(string)
|
|
string = string{1};
|
|
end
|
|
otherwise
|
|
error('matlab2tikz:prettyPrint', 'Unknown interpreter');
|
|
end
|
|
c{k} = string;
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function strings = cellstrOneLinePerCell(strings)
|
|
% convert to cellstr that contains only one-line strings
|
|
if ischar(strings)
|
|
strings = cellstr(strings);
|
|
elseif iscellstr(strings)
|
|
cs = cell(1, length(strings));
|
|
for s = 1:length(strings)
|
|
tmp = cellstr(strings{s});
|
|
cs{s} = tmp;
|
|
end
|
|
strings = cs;
|
|
else
|
|
error('matlab2tikz:cellstrOneLinePerCell', ...
|
|
'Data type not understood.');
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function parsed = parseTexString(m2t, string)
|
|
if iscellstr(string)
|
|
% Convert cell string to regular string, otherwise MATLAB complains
|
|
string = string{:};
|
|
end
|
|
|
|
% Get the position of all braces
|
|
bracesPos = regexp(string, '\{|\}');
|
|
|
|
% Exclude braces that are part of any of these MATLAB-supported TeX commands:
|
|
% \color{...} \color[...]{...} \fontname{...} \fontsize{...}
|
|
[sCmd, eCmd] = regexp(string, '\\(color(\[[^\]]*\])?|fontname|fontsize)\{[^}]*\}');
|
|
for i = 1:length(sCmd)
|
|
bracesPos(bracesPos >= sCmd(i) & bracesPos <= eCmd(i)) = [];
|
|
end
|
|
|
|
% Exclude braces that are preceded by an odd number of backslashes which
|
|
% means the brace is escaped and thus to be printed, not a grouping brace
|
|
expr = '(?<!\\)(\\\\)*\\(\{|\})';
|
|
escaped = regexp(string, expr, 'end');
|
|
% It's necessary to go over 'string' with the same RegEx again to catch
|
|
% overlapping matches, e.g. string == '\{\}'. In such a case the simple
|
|
% regexp(...) above only finds the first brace. What we have to do is look
|
|
% only at the part of 'string' that starts with the first brace but doesn't
|
|
% encompass its escaping backslash. Iterating over all previously found
|
|
% matches makes sure all overlapping matches are found, too. That way even
|
|
% cases like string == '\{\} \{\}' are handled correctly.
|
|
% The call to unique(...) is not necessary to get the behavior described, but
|
|
% by removing duplicates in 'escaped' it's cleaner than without.
|
|
for i = escaped
|
|
escaped = unique([escaped, regexp(string(i:end), expr, 'end') + i-1]);
|
|
end
|
|
% Now do the actual removal of escaped braces
|
|
for i = 1:length(escaped)
|
|
bracesPos(bracesPos == escaped(i)) = [];
|
|
end
|
|
|
|
parsed = '';
|
|
% Have a virtual brace one character left of where the actual string
|
|
% begins (remember, MATLAB strings start counting at 1, not 0). This is
|
|
% to make sure substrings left of the first brace get parsed, too.
|
|
prevBracePos = 0;
|
|
% Iterate over all the brace positions in order to split up 'string'
|
|
% at those positions and then parse the substrings. A virtual brace is
|
|
% added right of where the actual string ends to make sure substrings
|
|
% right of the right-most brace get parsed as well.
|
|
for currBracePos = [bracesPos, length(string)+1]
|
|
if (prevBracePos + 1) < currBracePos
|
|
% Parse the substring between (but not including) prevBracePos
|
|
% and currBracePos, i.e. between the previous brace and the
|
|
% current one (but only if there actually is a non-empty
|
|
% substring). Then append it to the output string.
|
|
substring = string(prevBracePos+1 : currBracePos-1);
|
|
parsed = [parsed, parseTexSubstring(m2t, substring)];
|
|
end
|
|
if currBracePos <= length(string)
|
|
% Append the brace itself to the output string, but only if the
|
|
% current brace position is within the limits of the string, i.e.
|
|
% don't append anything for the last, virtual brace that is only
|
|
% there to enable parsing of substrings beyond the right-most
|
|
% actual brace.
|
|
brace = string(currBracePos);
|
|
parsed = [parsed, brace];
|
|
end
|
|
% The current brace position will be next iteration's previous one
|
|
prevBracePos = currBracePos;
|
|
end
|
|
|
|
% Enclose everything in $...$ to use math mode
|
|
parsed = ['$' parsed '$'];
|
|
% ...except when everything is text
|
|
parsed = regexprep(parsed, '^\$\\text\{([^}]*)\}\$$', '$1');
|
|
% start-> $ \text {(non-}) } $<-end
|
|
% ...or when the parsed string is empty
|
|
parsed = regexprep(parsed, '^\$\$$', '');
|
|
|
|
% Ensure math mode for pipe symbol (issue #587)
|
|
parsed = strrep(parsed, '|', '$|$');
|
|
end
|
|
% ==============================================================================
|
|
function string = parseTexSubstring(m2t, string)
|
|
origstr = string; % keep this for warning messages
|
|
|
|
% Font families (italic, bold, etc.) get a trailing '{}' because they may be
|
|
% followed by a letter which would produce an error in (La)TeX.
|
|
for i = {'it', 'bf', 'rm', 'sl'}
|
|
string = strrep(string, ['\' i{:}], ['\' i{:} '{}']);
|
|
end
|
|
|
|
% The same holds true for special characters like \alpha
|
|
% The list of MATLAB-supported TeX characters was taken from
|
|
% http://www.mathworks.com/help/techdoc/ref/text_props.html#String
|
|
named = {'alpha', 'angle', 'ast', 'beta', 'gamma', 'delta', ...
|
|
'epsilon', 'zeta', 'eta', 'theta', 'vartheta', 'iota', ...
|
|
'kappa', 'lambda', 'mu', 'nu', 'xi', 'pi', 'rho', ...
|
|
'sigma', 'varsigma', 'tau', 'equiv', 'Im', 'otimes', ...
|
|
'cap', 'int', 'rfloor', 'lfloor', 'perp', 'wedge', ...
|
|
'rceil', 'vee', 'langle', 'upsilon', 'phi', 'chi', ...
|
|
'psi', 'omega', 'Gamma', 'Delta', 'Theta', 'Lambda', ...
|
|
'Xi', 'Pi', 'Sigma', 'Upsilon', 'Phi', 'Psi', 'Omega', ...
|
|
'forall', 'exists', 'ni', 'cong', 'approx', 'Re', ...
|
|
'oplus', 'cup', 'subseteq', 'lceil', 'cdot', 'neg', ...
|
|
'times', 'surd', 'varpi', 'rangle', 'sim', 'leq', ...
|
|
'infty', 'clubsuit', 'diamondsuit', 'heartsuit', ...
|
|
'spadesuit', 'leftrightarrow', 'leftarrow', ...
|
|
'Leftarrow', 'uparrow', 'rightarrow', 'Rightarrow', ...
|
|
'downarrow', 'circ', 'pm', 'geq', 'propto', 'partial', ...
|
|
'bullet', 'div', 'neq', 'aleph', 'wp', 'oslash', ...
|
|
'supseteq', 'nabla', 'ldots', 'prime', '0', 'mid', ...
|
|
'copyright' };
|
|
for i = named
|
|
string = strrep(string, ['\' i{:}], ['\' i{:} '{}']);
|
|
% FIXME: Only append '{}' if there's an odd number of backslashes
|
|
% in front of the items from 'named'. If it's an even
|
|
% number instead, that means there's an escaped (printable)
|
|
% backslash and some text like "alpha" after that.
|
|
end
|
|
% Some special characters' names are subsets of others, e.g. '\o' is
|
|
% a subset of '\omega'. This would produce undesired double-escapes.
|
|
% For example if '\o' was converted to '\o{}' after '\omega' has been
|
|
% converted to '\omega{}' this would result in '\o{}mega{}' instead of
|
|
% '\omega{}'. Had '\o' been converted to '\o{}' _before_ '\omega' is
|
|
% converted then the result would be '\o{}mega' and thus also wrong.
|
|
% To circumvent the problem all those special character names that are
|
|
% subsets of others are now converted using a regular expression that
|
|
% uses negative lookahead. The special handling of the backslash is
|
|
% required for MATLAB/Octave compatibility.
|
|
string = regexprep(string, '(\\)o(?!mega|times|plus|slash)', '$1o{}');
|
|
string = regexprep(string, '(\\)in(?!t|fty)', '$1in{}');
|
|
string = regexprep(string, '(\\)subset(?!eq)', '$1subset{}');
|
|
string = regexprep(string, '(\\)supset(?!eq)', '$1supset{}');
|
|
|
|
% Convert '\0{}' (TeX text mode) to '\emptyset{}' (TeX math mode)
|
|
string = strrep(string, '\0{}', '\emptyset{}');
|
|
|
|
% Add skip to \fontsize
|
|
% This is required for a successful LaTeX run on the output as in contrast
|
|
% to MATLAB/Octave it requires the skip parameter (even if it's zero)
|
|
string = regexprep(string, '(\\fontsize\{[^}]*\})', '$1{0}');
|
|
|
|
% Put '\o{}' inside \text{...} as it is a text mode symbol that does not
|
|
% exist in math mode (and LaTeX gives a warning if you use it in math mode)
|
|
string = strrep(string, '\o{}', '\text{\o{}}');
|
|
|
|
% Put everything that isn't a TeX command inside \text{...}
|
|
expr = '(\\[a-zA-Z]+(\[[^\]]*\])?(\{[^}]*\}){1,2})';
|
|
% |( \cmd )( [...]? )( {...}{1,2} )|
|
|
% ( subset $1 )
|
|
repl = '}$1\\text{';
|
|
string = regexprep(string, expr, repl);
|
|
% ...\alpha{}... -> ...}\alpha{}\text{...
|
|
string = ['\text{' string '}'];
|
|
% ...}\alpha{}\text{... -> \text{...}\alpha{}\text{...}
|
|
|
|
% '_' has to be in math mode so long as it's not escaped as '\_' in which
|
|
% case it remains as-is. Extra care has to be taken to make sure any
|
|
% backslashes in front of the underscore are not themselves escaped and
|
|
% thus printable backslashes. This is the case if there's an even number
|
|
% of backslashes in a row.
|
|
repl = '$1}_\\text{';
|
|
string = regexprep(string, '(?<!\\)((\\\\)*)_', repl);
|
|
|
|
% '^' has to be in math mode so long as it's not escaped as '\^' in which
|
|
% case it is expressed as '\textasciicircum{}' for compatibility with
|
|
% regular TeX. Same thing here regarding even/odd number of backslashes
|
|
% as in the case of underscores above.
|
|
repl = '$1\\textasciicircum{}';
|
|
string = regexprep(string, '(?<!\\)((\\\\)*)\\\^', repl);
|
|
repl = '$1}^\\text{';
|
|
string = regexprep(string, '(?<!\\)((\\\\)*)\^', repl);
|
|
|
|
% '<' and '>' has to be either in math mode or needs to be typeset as
|
|
% '\textless' and '\textgreater' in textmode
|
|
% This is handled better, if 'parseStringsAsMath' is activated
|
|
if m2t.args.parseStringsAsMath == 0
|
|
string = regexprep(string, '<', '\\textless{}');
|
|
string = regexprep(string, '>', '\\textgreater{}');
|
|
end
|
|
|
|
% Move font styles like \bf into the \text{} command.
|
|
expr = '(\\bf|\\it|\\rm|\\fontname)({\w*})+(\\text{)';
|
|
while regexp(string, expr)
|
|
string = regexprep(string, expr, '$3$1$2');
|
|
end
|
|
|
|
% Replace Fontnames
|
|
[dummy, dummy, dummy, dummy, fonts, dummy, subStrings] = regexp(string, '\\fontname{(\w*)}'); %#ok
|
|
fonts = fonts2tex(fonts);
|
|
subStrings = [subStrings; fonts, {''}];
|
|
string = cell2mat(subStrings(:)');
|
|
|
|
% Merge adjacent \text fields:
|
|
string = mergeAdjacentTexCmds(string, '\text');
|
|
|
|
% '\\' has to be escaped to '\textbackslash{}'
|
|
% This cannot be done with strrep(...) as it would replace e.g. 4 backslashes
|
|
% with three times the replacement string because it finds overlapping matches
|
|
% (see http://www.mathworks.de/help/techdoc/ref/strrep.html)
|
|
% Note: Octave's backslash handling is broken. Even though its output does
|
|
% not resemble MATLAB's, the same m2t code is used for either software. That
|
|
% way MATLAB-compatible code produces the same matlab2tikz output no matter
|
|
% which software it's executed in. So long as this MATLAB incompatibility
|
|
% remains in Octave you're probably better off not using backslashes in TeX
|
|
% text anyway.
|
|
string = regexprep(string, '(\\)\\', '$1textbackslash{}');
|
|
|
|
% '_', '^', '{', and '}' are already escaped properly, even in MATLAB's TeX
|
|
% dialect (and if they're not, that's intentional)
|
|
|
|
% Escape "$", "%", and "#" to make them compatible to true TeX while in
|
|
% MATLAB/Octave they are not escaped
|
|
string = strrep(string, '$', '\$');
|
|
string = strrep(string, '%', '\%');
|
|
string = strrep(string, '#', '\#');
|
|
|
|
% Escape "§" as "\S" since it can give UTF-8 problems otherwise.
|
|
% The TeX string 'a_§' in particular lead to problems in Octave 3.6.0.
|
|
% m2t transcoded that string into '$\text{a}_\text{*}\text{#}$' with
|
|
% * = 0xC2 and # = 0xA7 which corresponds with the two-byte UTF-8
|
|
% encoding. Even though this looks like an Octave bug that shows
|
|
% during the '..._\text{abc}' to '..._\text{a}\text{bc}' conversion,
|
|
% it's best to include the workaround here.
|
|
string = strrep(string, '§', '\S{}');
|
|
|
|
string = escapeAmpersands(m2t, string, origstr);
|
|
string = escapeTildes(m2t, string, origstr);
|
|
|
|
% Convert '..._\text{abc}' and '...^\text{abc}' to '..._\text{a}\text{bc}'
|
|
% and '...^\text{a}\text{bc}', respectively.
|
|
% Things get a little more complicated if instead of 'a' it's e.g. '$'. The
|
|
% latter has been converted to '\$' by now and simply extracting the first
|
|
% character from '\text{\$bc}' would result in '\text{$}\text{$bc}' which
|
|
% is syntactically wrong. Instead the whole command '\$' has to be moved in
|
|
% front of the \text{...} block, e.g. '..._\text{\$bc}' -> '..._\$\text{bc}'.
|
|
% Note that the problem does not occur for the majority of special characters
|
|
% like '\alpha' because they use math mode and therefore are never inside a
|
|
% \text{...} block to begin with. This means that the number of special
|
|
% characters affected by this issue is actually quite small:
|
|
% $ # % & _ { } \o § ~ \ ^
|
|
expr = ['(_|\^)(\\text)\{([^}\\]|\\\$|\\#|\\%|\\&|\\_|\\\{|\\\}|', ...
|
|
... % (_/^)(\text) {(non-}\| \$ | \#| \%| \&| \_| \{ | \} |
|
|
... % ($1)( $2 ) ( $3 ->
|
|
'\\o\{\}|\\S\{\}|\\textasciitilde\{\}|\\textbackslash\{\}|', ...
|
|
... % \o{} | \S{} | \textasciitilde{} | \textbackslash{} |
|
|
... % <- $3 ->
|
|
'\\textasciicircum\{\})'];
|
|
% \textasciicircum{} )
|
|
% <- $3 )
|
|
string = regexprep(string, expr, '$1$2{$3}$2{');
|
|
|
|
string = parseStringsAsMath(m2t, string);
|
|
|
|
% Clean up: remove empty \text{}
|
|
string = strrep(string, '\text{}', '');
|
|
% \text{}\alpha{}\text{...} -> \alpha{}\text{...}
|
|
|
|
% Clean up: convert '{}\' to '\' unless it's prefixed by a backslash which
|
|
% means the opening brace is escaped and thus a printable character instead
|
|
% of a grouping brace.
|
|
string = regexprep(string, '(?<!\\)\{\}(\\)', '$1');
|
|
% \alpha{}\text{...} -> \alpha\text{...}
|
|
|
|
% Clean up: convert '{}}' to '}' unless it's prefixed by a backslash
|
|
string = regexprep(string, '(?<!\\)\{\}\}', '}');
|
|
|
|
% Clean up: remove '{}' at the end of 'string' unless it's prefixed by a
|
|
% backslash
|
|
string = regexprep(string, '(?<!\\)\{\}$', '');
|
|
end
|
|
% ==============================================================================
|
|
function string = escapeTildes(m2t, string, origstr)
|
|
% Escape plain "~" in MATLAB and replace escaped "\~" in Octave with a proper
|
|
% escape sequence. An un-escaped "~" produces weird output in Octave, thus
|
|
% give a warning in that case
|
|
switch getEnvironment
|
|
case 'MATLAB'
|
|
string = strrep(string, '~', '\textasciitilde{}'); % or '\~{}'
|
|
case 'Octave'
|
|
string = strrep(string, '\~', '\textasciitilde{}'); % ditto
|
|
if regexp(string, '(?<!\\)~')
|
|
userWarning(m2t, ...
|
|
['TeX string ''%s'' contains un-escaped ''~''. ' ...
|
|
'For proper display in Octave you probably ' ...
|
|
'want to escape it even though that''s ' ...
|
|
'incompatible with MATLAB. ' ...
|
|
'In the matlab2tikz output it will have its ' ...
|
|
'usual TeX function as a non-breaking space.'], ...
|
|
origstr)
|
|
end
|
|
otherwise
|
|
errorUnknownEnvironment();
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function string = escapeAmpersands(m2t, string, origstr)
|
|
% Escape plain "&" in MATLAB and replace it and the following character with
|
|
% a space in Octave unless the "&" is already escaped
|
|
switch getEnvironment
|
|
case 'MATLAB'
|
|
string = strrep(string, '&', '\&');
|
|
case 'Octave'
|
|
% Ampersands should already be escaped in Octave.
|
|
% Octave (tested with 3.6.0) handles un-escaped ampersands a little
|
|
% funny in that it removes the following character, if there is one:
|
|
% 'abc&def' -> 'abc ef'
|
|
% 'abc&\deltaef' -> 'abc ef'
|
|
% 'abc&$ef' -> 'abc ef'
|
|
% 'abcdef&' -> 'abcdef'
|
|
% Don't remove closing brace after '&' as this would result in
|
|
% unbalanced braces
|
|
string = regexprep(string, '(?<!\\)&(?!})', ' ');
|
|
string = regexprep(string, '(?<!\\)&}', '}');
|
|
if regexp(string, '(?<!\\)&\\')
|
|
% If there's a backslash after the ampersand, that means not only
|
|
% the backslash should be removed but the whole escape sequence,
|
|
% e.g. '\delta' or '\$'. Actually the '\delta' case is the
|
|
% trickier one since by now 'string' would have been turned from
|
|
% 'abc&\deltaef' into '\text{abc&}\delta{}\text{ef}', i.e. after
|
|
% the ampersand first comes a closing brace and then '\delta';
|
|
% the latter as well as the ampersand itself should be removed
|
|
% while the brace must remain in place to avoid unbalanced braces.
|
|
userWarning(m2t, ...
|
|
['TeX string ''%s'' contains a special character ' ...
|
|
'after an un-escaped ''&''. The output generated ' ...
|
|
'by matlab2tikz will not precisely match that ' ...
|
|
'which you see in Octave itself in that the ' ...
|
|
'special character and the preceding ''&'' is ' ...
|
|
'not replaced with a space.'], origstr)
|
|
end
|
|
otherwise
|
|
errorUnknownEnvironment();
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [string] = parseStringsAsMath(m2t, string)
|
|
% Some further processing makes the output behave more like TeX math mode,
|
|
% but only if the matlab2tikz parameter parseStringsAsMath=true.
|
|
if m2t.args.parseStringsAsMath
|
|
|
|
% Some characters should be in math mode: =-+/,.()<>0-9
|
|
expr = '(\\text)\{([^}=\-+/,.()<>0-9]*)([=\-+/,.()<>0-9]+)([^}]*)\}';
|
|
% \text {(any non-"x"/'}'char)( any "x" char )(non-}) }
|
|
% ( $1 ) ( $2 )( $3 )( $4)
|
|
while regexp(string, expr)
|
|
% Iterating is necessary to catch all occurrences. See above.
|
|
string = regexprep(string, expr, '$1{$2}$3$1{$4}');
|
|
end
|
|
|
|
% \text{ } should be a math-mode space
|
|
string = regexprep(string, '\\text\{(\s+)}', '$1');
|
|
|
|
% '<<' probably means 'much smaller than', i.e. '\ll'
|
|
repl = switchMatOct('$1\\ll{}$2', '$1\ll{}$2');
|
|
string = regexprep(string, '([^<])<<([^<])', repl);
|
|
|
|
% '>>' probably means 'much greater than', i.e. '\gg'
|
|
repl = switchMatOct('$1\\gg{}$2', '$1\gg{}$2');
|
|
string = regexprep(string, '([^>])>>([^>])', repl);
|
|
|
|
% Single letters are most likely variables and thus should be in math mode
|
|
string = regexprep(string, '\\text\{([a-zA-Z])\}', '$1');
|
|
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function tex = fonts2tex(fonts)
|
|
% Returns a tex command for each fontname in the cell array fonts.
|
|
if ~iscell(fonts)
|
|
error('matlab2tikz:fonts2tex', ...
|
|
'Expecting a cell array as input.');
|
|
end
|
|
tex = cell(size(fonts));
|
|
|
|
for ii = 1:numel(fonts)
|
|
font = fonts{ii}{1};
|
|
|
|
% List of known fonts.
|
|
switch lower(font)
|
|
case 'courier'
|
|
tex{ii} = '\ttfamily{}';
|
|
case 'times'
|
|
tex{ii} = '\rmfamily{}';
|
|
case {'arial', 'helvetica'}
|
|
tex{ii} = '\sffamily{}';
|
|
otherwise
|
|
warning('matlab2tikz:fonts2tex', ...
|
|
'Unknown font ''%s''. Using tex default font.',font);
|
|
% Unknown font -> Switch to standard font.
|
|
tex{ii} = '\rm{}';
|
|
end
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function string = mergeAdjacentTexCmds(string, cmd)
|
|
% Merges adjacent tex commands like \text into one command
|
|
% If necessary, add a backslash
|
|
if cmd(1) ~= '\'
|
|
cmd = ['\' cmd];
|
|
end
|
|
% Link each bracket to the corresponding bracket
|
|
link = zeros(size(string));
|
|
pos = [regexp([' ' string], '([^\\]{)'), ...
|
|
regexp([' ' string], '([^\\]})')];
|
|
pos = sort(pos);
|
|
ii = 1;
|
|
while ii <= numel(pos)
|
|
if string(pos(ii)) == '}'
|
|
link(pos(ii-1)) = pos(ii);
|
|
link(pos(ii)) = pos(ii - 1);
|
|
pos([ii-1, ii]) = [];
|
|
ii = ii - 1;
|
|
else
|
|
ii = ii + 1;
|
|
end
|
|
end
|
|
% Find dispensable commands
|
|
pos = regexp(string, ['}\' cmd '{']);
|
|
delete = zeros(0,1);
|
|
len = numel(cmd);
|
|
for p = pos
|
|
l = link(p);
|
|
if l > len && isequal(string(l-len:l-1), cmd)
|
|
delete(end+1,1) = p;
|
|
end
|
|
end
|
|
% 3. Remove these commands (starting from the back
|
|
delete = repmat(delete, 1, len+2) + repmat(0:len+1,numel(delete), 1);
|
|
string(delete(:)) = [];
|
|
end
|
|
function dims = pos2dims(pos)
|
|
% Position quadruplet [left, bottom, width, height] to dimension structure
|
|
dims = struct('left' , pos(1), 'bottom', pos(2));
|
|
if numel(pos) == 4
|
|
dims.width = pos(3);
|
|
dims.height = pos(4);
|
|
dims.right = dims.left + dims.width;
|
|
dims.top = dims.bottom + dims.height;
|
|
end
|
|
end
|
|
% OPTION ARRAYS ================================================================
|
|
function opts = opts_new()
|
|
% create a new options array
|
|
opts = cell(0,2);
|
|
end
|
|
function opts = opts_add(opts, key, value)
|
|
% add a key-value pair to an options array (with duplication check)
|
|
if ~exist('value','var')
|
|
value = '';
|
|
end
|
|
value = char(value);
|
|
|
|
% Check if the key already exists.
|
|
if opts_has(opts, key)
|
|
oldValue = opts_get(opts, key);
|
|
if isequal(value, oldValue)
|
|
return; % no action needed: value already present
|
|
else
|
|
error('matlab2tikz:opts_add', ...
|
|
['Trying to add (%s, %s) to options, but it already ' ...
|
|
'contains the conflicting key-value pair (%s, %s).'], ...
|
|
key, value, key, oldValue);
|
|
end
|
|
end
|
|
opts = opts_append(opts, key, value);
|
|
end
|
|
function opts = opts_addSubOpts(opts, key, subOpts)
|
|
% add a key={Opts} pair to an options array
|
|
formatted = ['{' opts_print(subOpts) '}'];
|
|
opts = opts_add(opts, key, formatted);
|
|
end
|
|
function bool = opts_has(opts, key)
|
|
% returns true if the options array contains the key
|
|
bool = ~isempty(opts) && ismember(key, opts(:,1));
|
|
end
|
|
function value = opts_get(opts, key)
|
|
% returns the value(s) stored for a key in an options array
|
|
idx = find(ismember(opts(:,1), key));
|
|
switch numel(idx)
|
|
case 1
|
|
value = opts{idx,2}; % just the value
|
|
otherwise
|
|
value = opts(idx,2); % as cell array
|
|
end
|
|
end
|
|
function opts = opts_append(opts, key, value)
|
|
% append a key-value pair to an options array (duplicate keys allowed)
|
|
if ~exist('value','var')
|
|
value = '';
|
|
end
|
|
value = char(value);
|
|
if ~(opts_has(opts, key) && isequal(opts_get(opts, key), value))
|
|
opts = cat(1, opts, {key, value});
|
|
end
|
|
end
|
|
function opts = opts_append_userdefined(opts, userDefined)
|
|
% appends user-defined options to an options array
|
|
% the userDefined options can come either as a single string or a cellstr that
|
|
% is already TikZ-formatted. The internal 2D cell format is NOT supported.
|
|
if ~isempty(userDefined)
|
|
if ischar(userDefined)
|
|
userDefined = {userDefined};
|
|
end
|
|
for k = 1:length(userDefined)
|
|
opts = opts_append(opts, userDefined{k});
|
|
end
|
|
end
|
|
end
|
|
function opts = opts_copy(opts_from, name_from, opts, name_to)
|
|
% copies an option (if it exists) from one option array to another one
|
|
if ~exist('name_to', 'var') || isempty(name_to)
|
|
name_to = name_from;
|
|
end
|
|
if opts_has(opts_from, name_from)
|
|
value = opts_get(opts_from, name_from);
|
|
opts = opts_append(opts, name_to, value);
|
|
end
|
|
end
|
|
function opts = opts_remove(opts, varargin)
|
|
% remove some key-value pairs from an options array
|
|
keysToDelete = varargin;
|
|
idxToDelete = ismember(opts(:,1), keysToDelete);
|
|
opts(idxToDelete, :) = [];
|
|
end
|
|
function opts = opts_merge(opts, varargin)
|
|
% merge multiple options arrays
|
|
for jArg = 1:numel(varargin)
|
|
opts2 = varargin{jArg};
|
|
for k = 1:size(opts2, 1)
|
|
opts = opts_append(opts, opts2{k,1}, opts2{k,2});
|
|
end
|
|
end
|
|
end
|
|
function str = opts_print(opts, sep)
|
|
% pretty print an options array
|
|
if ~exist('sep','var') || ~ischar(sep)
|
|
sep = ', ';
|
|
end
|
|
nOpts = size(opts,1);
|
|
c = cell(1,nOpts);
|
|
for k = 1:nOpts
|
|
if isempty(opts{k,2})
|
|
c{k} = sprintf('%s', opts{k,1});
|
|
else
|
|
c{k} = sprintf('%s=%s', opts{k,1}, opts{k,2});
|
|
end
|
|
end
|
|
str = m2tstrjoin(c, sep);
|
|
end
|
|
% ==============================================================================
|
|
function m2t = m2t_addAxisOption(m2t, key, value)
|
|
% Adds an option to the last axesContainer
|
|
if ~exist('value','var')
|
|
value = '';
|
|
end
|
|
m2t.axes{end}.options = opts_add(m2t.axes{end}.options, key, value);
|
|
end
|
|
% ==============================================================================
|
|
function bool = isHG2()
|
|
% Checks if graphics system is HG2 (true) or HG1 (false).
|
|
% HG1 : MATLAB up to R2014a and currently all OCTAVE versions
|
|
% HG2 : MATLAB starting from R2014b (version 8.4)
|
|
[env, envVersion] = getEnvironment();
|
|
bool = strcmpi(env,'MATLAB') && ~isVersionBelow(envVersion, [8,4]);
|
|
end
|
|
% ==============================================================================
|
|
function str = formatAspectRatio(m2t, values)
|
|
% format the aspect ratio. Behind the scenes, formatDim is used
|
|
strs = arrayfun(@formatDim, values, 'UniformOutput', false);
|
|
str = join(m2t, strs, ' ');
|
|
end
|
|
% ==============================================================================
|
|
function str = formatDim(value, unit)
|
|
% format the value for use as a TeX dimension
|
|
if ~exist('unit','var') || isempty(unit)
|
|
unit = '';
|
|
end
|
|
tolerance = 1e-7;
|
|
value = round(value/tolerance)*tolerance;
|
|
if value == 1 && ~isempty(unit) && unit(1) == '\'
|
|
str = unit; % just use the unit
|
|
else
|
|
% LaTeX has support for single precision (about 6.5 decimal places),
|
|
% but such accuracy is overkill for positioning. We clip to three
|
|
% decimals to overcome numerical rounding issues that tend to be very
|
|
% platform and version dependent. See also #604.
|
|
str = sprintf('%.3f', value);
|
|
str = regexprep(str, '(\d*\.\d*?)0+$', '$1'); % remove trailing zeros
|
|
str = regexprep(str, '\.$', ''); % remove trailing period
|
|
str = [str unit];
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [retval] = switchMatOct(matlabValue, octaveValue)
|
|
% Returns a different value for MATLAB and Octave
|
|
switch getEnvironment
|
|
case 'MATLAB'
|
|
retval = matlabValue;
|
|
case 'Octave'
|
|
retval = octaveValue;
|
|
otherwise
|
|
errorUnknownEnvironment();
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function checkDeprecatedEnvironment(minimalVersions)
|
|
[env, envVersion] = getEnvironment();
|
|
if isfield(minimalVersions, env)
|
|
minVersion = minimalVersions.(env);
|
|
envWithVersion = sprintf('%s %s', env, minVersion.name);
|
|
|
|
if isVersionBelow(envVersion, minVersion.num)
|
|
ID = 'matlab2tikz:deprecatedEnvironment';
|
|
|
|
warningMessage = ['\n', repmat('=',1,80), '\n\n', ...
|
|
' matlab2tikz is tested and developed on %s and newer.\n', ...
|
|
' This script may still be able to handle your plots, but if you\n', ...
|
|
' hit a bug, please consider upgrading your environment first.\n', ...
|
|
' Type "warning off %s" to suppress this warning.\n', ...
|
|
'\n', repmat('=',1,80), ];
|
|
warning(ID, warningMessage, envWithVersion, ID);
|
|
|
|
end
|
|
else
|
|
errorUnknownEnvironment();
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function m2t = needsPgfplotsVersion(m2t, minVersion)
|
|
if isVersionBelow(m2t.pgfplotsVersion, minVersion)
|
|
m2t.pgfplotsVersion = minVersion;
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function str = formatPgfplotsVersion(version)
|
|
version = versionArray(version);
|
|
if all(isfinite(version))
|
|
str = sprintf('%d.',version);
|
|
str = str(1:end-1); % remove the last period
|
|
else
|
|
str = 'newest';
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function [formatted,treeish] = VersionControlIdentifier()
|
|
% This function gives the (git) commit ID of matlab2tikz
|
|
%
|
|
% This assumes the standard directory structure as used by Nico's master branch:
|
|
% SOMEPATH/src/matlab2tikz.m with a .git directory in SOMEPATH.
|
|
%
|
|
% The HEAD of that repository is determined from file system information only
|
|
% by following dynamic references (e.g. ref:refs/heds/master) in branch files
|
|
% until an absolute commit hash (e.g. 1a3c9d1...) is found.
|
|
% NOTE: Packed branch references are NOT supported by this approach
|
|
MAXITER = 10; % stop following dynamic references after a while
|
|
formatted = '';
|
|
REFPREFIX = 'ref:';
|
|
isReference = @(treeish)(any(strfind(treeish, REFPREFIX)));
|
|
treeish = [REFPREFIX 'HEAD'];
|
|
try
|
|
% get the matlab2tikz directory
|
|
m2tDir = fileparts(mfilename('fullpath'));
|
|
gitDir = fullfile(m2tDir,'..','.git');
|
|
|
|
nIter = 1;
|
|
while isReference(treeish)
|
|
refName = treeish(numel(REFPREFIX)+1:end);
|
|
branchFile = fullfile(gitDir, refName);
|
|
|
|
if exist(branchFile, 'file') && nIter < MAXITER
|
|
% The FID is reused in every iteration, so `onCleanup` cannot
|
|
% be used to `fclose(fid)`. But since there is very little that
|
|
% can go wrong in a single `fscanf`, it's probably best to leave
|
|
% this part as it is for the time being.
|
|
fid = fopen(branchFile,'r');
|
|
treeish = fscanf(fid,'%s');
|
|
fclose(fid);
|
|
nIter = nIter + 1;
|
|
else % no branch file or iteration limit reached
|
|
treeish = '';
|
|
return;
|
|
end
|
|
end
|
|
catch
|
|
treeish = '';
|
|
end
|
|
if ~isempty(treeish)
|
|
formatted = sprintf('(commit %s)',treeish);
|
|
end
|
|
end
|
|
% ==============================================================================
|
|
function bool = isCategoricalType(data)
|
|
% This is a wrapper function for iscategorical(), which (as February 2018)
|
|
% is not available on GNU Octave.
|
|
switch getEnvironment()
|
|
case 'MATLAB'
|
|
bool = iscategorical(data);
|
|
case 'Octave'
|
|
bool = false;
|
|
otherwise
|
|
errorUnknownEnvironment();
|
|
end
|
|
end
|
|
% ==============================================================================
|