MATLAB中的xkcd样式图

xkcd样式图

所以才华横溢的人已经想出了如何在Mathematica , LaTeX , Python和R中制作xkcd风格的图表。

如何使用MATLAB来产生一个看起来像上面的情节?

我曾经尝试过

我创造了摆动的线条,但我无法摆动摆动的轴线。 我想到的唯一的解决办法是用扭曲线覆盖它们,但我想能够改变实际的轴。 我也无法使幽默字体工作,使用的代码是:

annotation('textbox',[left+left/8 top+0.65*top 0.05525 0.065],... 'String',{'EMBARRASSMENT'},... 'FontSize',24,... 'FontName','Humor',... 'FitBoxToText','off',... 'LineStyle','none'); 

对于摆动线,我尝试添加一个小的随机噪音和平滑:

  smooth(0.05*randn(size(x)),10) 

但是当他们相交时,我无法使他们周围出现白色的背景。

我看到了两种方法来解决这个问题:第一种方法是在plot特征的x​​ / y坐标上添加一些抖动。 这有一个好处,你可以很容易地修改一个情节,但是你必须自己画一个坐标轴,如果你想让它们xkcdyfied(参见@Rody Oldenhuis的解决scheme )。 第二种方法是创build一个非紧张的情节,并使用imtransform应用随机失真的形象。 这有一个好处,你可以使用它的任何情节,但你最终将形成一个图像,而不是一个可编辑的阴谋。

我会先显示#2,然后我会在下面的#1(如果你更喜欢#1,看看Rody的解决scheme !)。

在这里输入图像说明

这个解决scheme依赖于两个关键function: EXPORT_FIG从文件交换中获得一个反锯齿截图, IMTRANSFORM得到一个转换。

 %# define plot data x = 1:0.1:10; y1 = sin(x).*exp(-x/3) + 3; y2 = 3*exp(-(x-7).^2/2) + 1; %# plot fh = figure('color','w'); hold on plot(x,y1,'b','lineWidth',3); plot(x,y2,'w','lineWidth',7); plot(x,y2,'r','lineWidth',3); xlim([0.95 10]) ylim([0 5]) set(gca,'fontName','Comic Sans MS','fontSize',18,'lineWidth',3,'box','off') %# add an annotation annotation(fh,'textarrow',[0.4 0.55],[0.8 0.65],... 'string',sprintf('text%shere',char(10)),'headStyle','none','lineWidth',1.5,... 'fontName','Comic Sans MS','fontSize',14,'verticalAlignment','middle','horizontalAlignment','left') %# capture with export_fig im = export_fig('-nocrop',fh); %# add a bit of border to avoid black edges im = padarray(im,[15 15 0],255); %# make distortion grid sfc = size(im); [yy,xx]=ndgrid(1:7:sfc(1),1:7:sfc(2)); pts = [xx(:),yy(:)]; tf = cp2tform(pts+randn(size(pts)),pts,'lwm',12); w = warning; warning off images:inv_lwm:cannotEvaluateTransfAtSomeOutputLocations imt = imtransform(im,tf); warning(w) %# remove padding imt = imt(16:end-15,16:end-15,:); figure('color','w') imshow(imt) 

这是我最初的抖动尝试

在这里输入图像说明

 %# define plot data x = 1:0.1:10; y1 = sin(x).*exp(-x/3) + 3; y2 = 3*exp(-(x-7).^2/2) + 1; %# jitter x = x+randn(size(x))*0.01; y1 = y1+randn(size(x))*0.01; y2 = y2+randn(size(x))*0.01; %# plot figure('color','w') hold on plot(x,y1,'b','lineWidth',3); plot(x,y2,'w','lineWidth',7); plot(x,y2,'r','lineWidth',3); xlim([0.95 10]) ylim([0 5]) set(gca,'fontName','Comic Sans MS','fontSize',18,'lineWidth',3,'box','off') 

我不想重新实现所有的绘图function,我想创build一个通用的工具,可以将任何现有的情节转换为xkcd风格的情节。

这种方法意味着你可以使用标准的MATLAB函数来创build绘图和样式,然后当你完成后,你可以重新绘制一个xkcd风格的绘图,同时保留绘图的整体风格。

例子

情节 在这里输入图像说明

酒吧和剧情

在这里输入图像说明

箱形图 在这里输入图像说明

怎么运行的

该函数通过迭代轴的子元素来工作。 如果孩子是linepatch则会使他们稍微变形。 如果孩子是hggrouptypes的,那么它会迭代hggroup的子孩子。 我有计划支持其他情节types,如image ,但不清楚是什么是扭曲图像有一个xkcd风格的最佳方式。

最后,为了确保扭曲看起来是均匀的(即,短线没有超出长线的距离),我以像素为单位来测量线长,然后向上取样与其长度成比例。 然后给每个第N个样本添加噪声,从而产生或多或less具有相同失真量的行。

代码

而不是粘贴几百行代码,我只是链接到源代码的要点 。 此外,源代码和生成上述示例的代码可以免费获得GitHub 。

从示例中可以看出,尽pipe我计划尽快实施最佳方式,但仍未扭曲轴线本身。

第一步…find你喜欢的系统字体(使用函数listfonts来查看可用的内容),或者安装一个与xkcd中的手写风格相匹配的字体。 我在这个博客文章中提到了用户ch00f的“Humor Sans” TrueType字体,并将其用于我的后续示例。

正如我所看到的,通常需要三个不同的修改graphics对象来制作这些graphics:一个轴对象 ,一个线对象和一个文本对象 。 您可能还需要一个注释对象来使事情变得更容易,但是现在我已经接受了这一点,因为它可能比上述三个对象更难以实现。

我创build了包装函数,创build了三个对象,覆盖了某些属性设置,使它们更像xkcd。 一个限制是它们产生的新graphics在某些情况下不会被更新(比如调整轴的大小时文本对象上的边界框),但是这可以用一个更完整的面向对象的实现来解决,该实现涉及从句柄类 ,使用事件和监听器等。现在,这里是我更简单的实现:

xkcd_axes.m:

 function hAxes = xkcd_axes(xkcdOptions, varargin) hAxes = axes(varargin{:}, 'NextPlot', 'add', 'Visible', 'off', ... 'XLimMode', 'manual', 'YLimMode', 'manual'); axesUnits = get(hAxes, 'Units'); set(hAxes, 'Units', 'pixels'); axesPos = get(hAxes, 'Position'); set(hAxes, 'Units', axesUnits); xPoints = round(axesPos(3)/10); yPoints = round(axesPos(4)/10); limits = [xlim(hAxes) ylim(hAxes)]; ranges = [abs(limits(2) - limits(1)) abs(limits(4) - limits(3))]; backColor = get(get(hAxes, 'Parent'), 'Color'); xColor = get(hAxes, 'XColor'); yColor = get(hAxes, 'YColor'); line('Parent', hAxes, 'Color', xColor, 'LineWidth', 3, ... 'Clipping', 'off', ... 'XData', linspace(limits(1), limits(2), xPoints), ... 'YData', limits(3) + rand(1, xPoints).*0.005.*ranges(2)); line('Parent', hAxes, 'Color', yColor, 'LineWidth', 3, ... 'Clipping', 'off', ... 'YData', linspace(limits(3), limits(4), yPoints), ... 'XData', limits(1) + rand(1, yPoints).*0.005.*ranges(1)); xTicks = get(hAxes, 'XTick'); if ~isempty(xTicks) yOffset = limits(3) - 0.05.*ranges(2); tickIndex = true(size(xTicks)); if ismember('left', xkcdOptions) tickIndex(1) = false; xkcd_arrow('left', [limits(1) + 0.02.*ranges(1) xTicks(1)], ... yOffset, xColor); end if ismember('right', xkcdOptions) tickIndex(end) = false; xkcd_arrow('right', [xTicks(end) limits(2) - 0.02.*ranges(1)], ... yOffset, xColor); end plot([1; 1]*xTicks(tickIndex), ... 0.5.*[-yOffset; yOffset]*ones(1, sum(tickIndex)), ... 'Parent', hAxes, 'Color', xColor, 'LineWidth', 3, ... 'Clipping', 'off'); xLabels = cellstr(get(hAxes, 'XTickLabel')); for iLabel = 1:numel(xLabels) xkcd_text(xTicks(iLabel), yOffset, xLabels{iLabel}, ... 'HorizontalAlignment', 'center', ... 'VerticalAlignment', 'middle', ... 'BackgroundColor', backColor); end end yTicks = get(hAxes, 'YTick'); if ~isempty(yTicks) xOffset = limits(1) - 0.05.*ranges(1); tickIndex = true(size(yTicks)); if ismember('down', xkcdOptions) tickIndex(1) = false; xkcd_arrow('down', xOffset, ... [limits(3) + 0.02.*ranges(2) yTicks(1)], yColor); end if ismember('up', xkcdOptions) tickIndex(end) = false; xkcd_arrow('up', xOffset, ... [yTicks(end) limits(4) - 0.02.*ranges(2)], yColor); end plot(0.5.*[-xOffset; xOffset]*ones(1, sum(tickIndex)), ... [1; 1]*yTicks(tickIndex), ... 'Parent', hAxes, 'Color', yColor, 'LineWidth', 3, ... 'Clipping', 'off'); yLabels = cellstr(get(hAxes, 'YTickLabel')); for iLabel = 1:numel(yLabels) xkcd_text(xOffset, yTicks(iLabel), yLabels{iLabel}, ... 'HorizontalAlignment', 'right', ... 'VerticalAlignment', 'middle', ... 'BackgroundColor', backColor); end end function xkcd_arrow(arrowType, xArrow, yArrow, arrowColor) if ismember(arrowType, {'left', 'right'}) xLine = linspace(xArrow(1), xArrow(2), 10); yLine = yArrow + rand(1, 10).*0.003.*ranges(2); arrowScale = 0.05.*ranges(1); if strcmp(arrowType, 'left') xArrow = xLine(1) + arrowScale.*[0 0.5 1 1 1 0.5]; yArrow = yLine(1) + arrowScale.*[0 0.125 0.25 0 -0.25 -0.125]; else xArrow = xLine(end) - arrowScale.*[0 0.5 1 1 1 0.5]; yArrow = yLine(end) + arrowScale.*[0 -0.125 -0.25 0 0.25 0.125]; end else xLine = xArrow + rand(1, 10).*0.003.*ranges(1); yLine = linspace(yArrow(1), yArrow(2), 10); arrowScale = 0.05.*ranges(2); if strcmp(arrowType, 'down') xArrow = xLine(1) + arrowScale.*[0 0.125 0.25 0 -0.25 -0.125]; yArrow = yLine(1) + arrowScale.*[0 0.5 1 1 1 0.5]; else xArrow = xLine(end) + arrowScale.*[0 -0.125 -0.25 0 0.25 0.125]; yArrow = yLine(end) - arrowScale.*[0 0.5 1 1 1 0.5]; end end line('Parent', hAxes, 'Color', arrowColor, 'LineWidth', 3, ... 'Clipping', 'off', 'XData', xLine, 'YData', yLine); patch('Parent', hAxes, 'FaceColor', arrowColor, ... 'EdgeColor', arrowColor, 'LineWidth', 2, 'Clipping', 'off', ... 'XData', xArrow + [0 rand(1, 5).*0.002.*ranges(1)], ... 'YData', yArrow + [0 rand(1, 5).*0.002.*ranges(2)]); end end 

xkcd_text.m:

 function hText = xkcd_text(varargin) hText = text(varargin{:}); set(hText, 'FontName', 'Humor Sans', 'FontSize', 13, ... 'FontWeight', 'normal'); backColor = get(hText, 'BackgroundColor'); edgeColor = get(hText, 'EdgeColor'); if ~strcmp(backColor, 'none') || ~strcmp(edgeColor, 'none') hParent = get(hText, 'Parent'); extent = get(hText, 'Extent'); nLines = size(get(hText, 'String'), 1); extent = extent + [-0.5 -0.5 1 1].*0.25.*extent(4)./nLines; yPoints = 5*nLines; xPoints = round(yPoints*extent(3)/extent(4)); noiseScale = 0.05*extent(4)/nLines; set(hText, 'BackgroundColor', 'none', 'EdgeColor', 'none'); xBox = [linspace(extent(1), extent(1) + extent(3), xPoints) ... extent(1) + extent(3) + noiseScale.*rand(1, yPoints) ... linspace(extent(1) + extent(3), extent(1), xPoints) ... extent(1) + noiseScale.*rand(1, yPoints)]; yBox = [extent(2) + noiseScale.*rand(1, xPoints) ... linspace(extent(2), extent(2) + extent(4), yPoints) ... extent(2) + extent(4) + noiseScale.*rand(1, xPoints) ... linspace(extent(2) + extent(4), extent(2), yPoints)]; patch('Parent', hParent, 'FaceColor', backColor, ... 'EdgeColor', edgeColor, 'LineWidth', 2, 'Clipping', 'off', ... 'XData', xBox, 'YData', yBox); hKids = get(hParent, 'Children'); set(hParent, 'Children', [hText; hKids(hKids ~= hText)]); end end 

xkcd_line.m:

 function hLine = xkcd_line(xData, yData, varargin) yData = yData + 0.01.*max(range(xData), range(yData)).*rand(size(yData)); line(xData, yData, varargin{:}, 'Color', 'w', 'LineWidth', 8); hLine = line(xData, yData, varargin{:}, 'LineWidth', 3); end 

这里有一个示例脚本,它使用这些来重新创build上面的漫画。 我通过使用ginput来重新ginput线条,用ginput标记图中的点,捕捉它们,然后绘制它们的方式:

 xS = [0.0359 0.0709 0.1004 0.1225 0.1501 0.1759 0.2219 0.2477 0.2974 0.3269 0.3582 0.3895 0.4061 0.4337 0.4558 0.4797 0.5074 0.5276 0.5589 0.5810 0.6013 0.6179 0.6271 0.6344 0.6381 0.6418 0.6529 0.6713 0.6842 0.6934 0.7026 0.7118 0.7265 0.7376 0.7560 0.7726 0.7836 0.7965 0.8149 0.8370 0.8573 0.8867 0.9033 0.9346 0.9659 0.9843 0.9936]; yS = [0.2493 0.2520 0.2548 0.2548 0.2602 0.2629 0.2629 0.2657 0.2793 0.2657 0.2575 0.2575 0.2602 0.2629 0.2657 0.2766 0.2793 0.2875 0.3202 0.3856 0.4619 0.5490 0.6771 0.7670 0.7970 0.8270 0.8433 0.8433 0.8243 0.7180 0.6199 0.5272 0.4510 0.4128 0.3392 0.2711 0.2275 0.1757 0.1485 0.1131 0.1022 0.0858 0.0858 0.1022 0.1267 0.1567 0.1594]; xF = [0.0304 0.0488 0.0727 0.0967 0.1335 0.1630 0.2090 0.2348 0.2698 0.3011 0.3269 0.3545 0.3803 0.4153 0.4466 0.4724 0.4945 0.5110 0.5350 0.5516 0.5608 0.5700 0.5755 0.5810 0.5884 0.6013 0.6179 0.6363 0.6492 0.6584 0.6676 0.6731 0.6842 0.6860 0.6934 0.7007 0.7136 0.7265 0.7394 0.7560 0.7726 0.7818 0.8057 0.8444 0.8794 0.9107 0.9475 0.9751 0.9917]; yF = [0.0804 0.0940 0.0967 0.1049 0.1185 0.1458 0.1512 0.1540 0.1649 0.1812 0.1812 0.1703 0.1621 0.1594 0.1703 0.1975 0.2411 0.3065 0.3801 0.4782 0.5708 0.6526 0.7452 0.8106 0.8324 0.8488 0.8433 0.8270 0.7888 0.7343 0.6826 0.5981 0.5300 0.4782 0.3910 0.3420 0.2847 0.2248 0.1621 0.0995 0.0668 0.0395 0.0232 0.0177 0.0204 0.0232 0.0259 0.0204 0.0232]; xE = [0.0267 0.0488 0.0856 0.1409 0.1759 0.2164 0.2514 0.3011 0.3269 0.3637 0.3969 0.4245 0.4503 0.4890 0.5313 0.5608 0.5939 0.6344 0.6694 0.6934 0.7192 0.7394 0.7523 0.7689 0.7891 0.8131 0.8481 0.8757 0.9070 0.9346 0.9604 0.9807 0.9936]; yE = [0.0232 0.0232 0.0232 0.0259 0.0259 0.0259 0.0313 0.0259 0.0259 0.0259 0.0368 0.0395 0.0477 0.0586 0.0777 0.0886 0.1213 0.1730 0.2466 0.2902 0.3638 0.5082 0.6499 0.7916 0.8924 0.9414 0.9550 0.9387 0.9060 0.8760 0.8542 0.8379 0.8188]; hFigure = figure('Position', [300 300 700 450], 'Color', 'w'); hAxes = xkcd_axes({'left', 'right'}, 'XTick', [0.45 0.60 0.7 0.8], ... 'XTickLabel', {'YARD', 'STEPS', 'DOOR', 'INSIDE'}, ... 'YTick', []); hSpeed = xkcd_line(xS, yS, 'Parent', hAxes, 'Color', [0.5 0.5 0.5]); hFear = xkcd_line(xF, yF, 'Parent', hAxes, 'Color', [0 0.5 1]); hEmb = xkcd_line(xE, yE, 'Parent', hAxes, 'Color', 'r'); hText = xkcd_text(0.27, 0.9, ... {'WALKING BACK TO MY'; 'FRONT DOOR AT NIGHT:'}, ... 'Parent', hAxes, 'EdgeColor', 'k', ... 'HorizontalAlignment', 'center'); hSpeedNote = xkcd_text(0.36, 0.35, {'FORWARD'; 'SPEED'}, ... 'Parent', hAxes, 'Color', 'k', ... 'HorizontalAlignment', 'center'); hSpeedLine = xkcd_line([0.4116 0.4282 0.4355 0.4411], ... [0.3392 0.3256 0.3038 0.2820], ... 'Parent', hAxes, 'Color', 'k'); hFearNote = xkcd_text(0.15, 0.45, {'FEAR'; 'THAT THERE''S'; ... 'SOMETHING'; 'BEIND ME'}, ... 'Parent', hAxes, 'Color', 'k', ... 'HorizontalAlignment', 'center'); hFearLine = xkcd_line([0.1906 0.1998 0.2127 0.2127 0.2201 0.2256], ... [0.3501 0.3093 0.2629 0.2221 0.1975 0.1676], ... 'Parent', hAxes, 'Color', 'k'); hEmbNote = xkcd_text(0.88, 0.45, {'EMBARRASSMENT'}, ... 'Parent', hAxes, 'Color', 'k', ... 'HorizontalAlignment', 'center'); hEmbLine = xkcd_line([0.8168 0.8094 0.7983 0.7781 0.7578], ... [0.4864 0.5436 0.5872 0.6063 0.6226], ... 'Parent', hAxes, 'Color', 'k'); 

(号angular)这是由此产生的阴谋!

在这里输入图像说明

那么好吧,这里是我不那么粗鲁,但仍然不够完美的尝试:

 %# init %# ------------------------ noise = @(x,A) A*randn(size(x)); ns = @(x,A) A*ones(size(x)); h = figure(2); clf, hold on pos = get(h, 'position'); set(h, 'position', [pos(1:2) 800 450]); blackline = { 'k', ... 'linewidth', 2}; axisline = { 'k', ... 'linewidth', 3}; textprops = { 'fontName','Comic Sans MS',... 'fontSize', 14,... 'lineWidth',3}; %# Plot data %# ------------------------ x = 1:0.1:10; y0 = sin(x).*exp(-x/30) + 3; y1 = sin(x).*exp(-x/3) + 3; y2 = 3*exp(-(x-7).^6/.05) + 1; y0 = y0 + noise(x, 0.01); y1 = y1 + noise(x, 0.01); y2 = y2 + noise(x, 0.01); %# plot plot(x,y0, 'color', [0.7 0.7 0.7], 'lineWidth',3); plot(x,y1, 'w','lineWidth',7); plot(x,y1, 'b','lineWidth',3); plot(x,y2, 'w','lineWidth',7); plot(x,y2, 'r','lineWidth',3); %# text %# ------------------------ ll(1) = text(1.3, 4.2,... {'Walking back to my' 'front door at night:'}); ll(2) = text(5, 0.7, 'yard'); ll(3) = text(6.2, 0.7, 'steps'); ll(4) = text(7, 0.7, 'door'); ll(5) = text(8, 0.7, 'inside'); set(ll, textprops{:}); %# arrows & lines %# ------------------------ %# box around "walking back..." xx = 1.2:0.1:3.74; yy = ns(xx, 4.6) + noise(xx, 0.007); plot(xx, yy, blackline{:}) xx = 1.2:0.1:3.74; yy = ns(xx, 3.8) + noise(xx, 0.007); plot(xx, yy, blackline{:}) yy = 3.8:0.1:4.6; xx = ns(yy, 1.2) + noise(yy, 0.007); plot(xx, yy, blackline{:}) xx = ns(yy, 3.74) + noise(yy, 0.007); plot(xx, yy, blackline{:}) %# left arrow x_arr = 1.2:0.1:4.8; y_arr = 0.65 * ones(size(x_arr)) + noise(x_arr, 0.005); plot(x_arr, y_arr, blackline{:}) x_head = [1.1 1.6 1.62]; y_head = [0.65 0.72 0.57]; patch(x_head, y_head, 'k') %# right arrow x_arr = 8.7:0.1:9.8; y_arr = 0.65 * ones(size(x_arr)) + noise(x_arr, 0.005); plot(x_arr, y_arr, blackline{:}) x_head = [9.8 9.3 9.3]; y_head = [0.65 0.72 0.57]; patch(x_head, y_head, 'k') %# left line on axis y_line = 0.8:0.1:1.1; x_line = ns(y_line, 6.5) + noise(y_line, 0.005); plot(x_line, y_line, blackline{:}) %# right line on axis y_line = 0.8:0.1:1.1; x_line = ns(y_line, 7.2) + noise(y_line, 0.005); plot(x_line, y_line, blackline{:}) %# axes x_xax = x; y_xax = 0.95 + noise(x_xax, 0.01); y_yax = 0.95:0.1:5; x_yax = x(1) + noise(y_yax, 0.01); plot(x_xax, y_xax, axisline{:}) plot(x_yax, y_yax, axisline{:}) % finalize %# ------------------------ xlim([0.95 10]) ylim([0 5]) axis off 

结果:

在Matlab中XKCD模仿

要做的事情:

  1. find更好的function(更好地定义它们)
  2. 添加“注释”和波浪线到他们描述的曲线
  3. find比Comic Sans更好的字体!
  4. 将所有东西都plot2xkcd为一个函数plot2xkcd以便我们可以将任何绘图/graphics转换为xkcd样式。