Я пытаюсь воссоздать эту точную диаграмму с помощью tikz. До сих пор я пытался использовать вложенные декорации в tikz, поскольку я использовал их для других фрактальных конструкций. Однако все эти конструкции были либо декорациями, предопределенными tikz, как кривая Коха, либо теми, для которых я нашел решения на стеке обмена, как треугольник Серпинского.
Я много изучал определение собственных украшений, но это кажется довольно сложным процессом для новичка в tikz, и не нашел примеров, слишком похожих на то, что я пытаюсь сделать. Я знаю, что это также возможно с использованием систем Линдемайера, но понимаю только, как использовать их для построения линий.
Если это как-то поможет, то, на мой взгляд, самый простой способ сделать это — установить квадрат в качестве исходной фигуры с началом координат в левом нижнем углу, затем выполнить масштабирование на 1/4 для нижнего левого квадрата, масштабирование на 1/4, затем перемещение вверх для верхнего левого квадрата и т. д., а затем установить новую фигуру вместо исходной, готовую к следующей итерации.
Любая помощь приветствуется!
решение1
Вот способ с системой Lindenmayer. Для заказов выше 5 компилируйте с LuaLaTeX.
% \RequirePackage{luatex85} % Only for LuaLaTeX and standalone class
\documentclass[varwidth,border=5]{standalone}
\usepackage{tikz}
\usetikzlibrary{lindenmayersystems}
\pgfdeclarelindenmayersystem{square fractal}{%
\symbol{S}{\pgflsystemstep=0.5\pgflsystemstep}
\symbol{A}{\pgftransformshift%
{\pgfqpoint{0.75\pgflsystemstep}{0.75\pgflsystemstep}}}
\symbol{R}{\pgftransformrotate{90}}
\symbol{Q}{%
\pgfpathrectangle{\pgfqpoint{-0.5\pgflsystemstep}{-0.5\pgflsystemstep}}%
{\pgfqpoint{\pgflsystemstep}{\pgflsystemstep}}%
}
\rule{Q -> [SQ[ASQ][RASQ][RRASQ][RRRASQ]]}
}
\begin{document}
\foreach\i in {0,...,5}{%
\tikz\fill [l-system={square fractal, step=5cm, axiom=Q, order=\i}]
lindenmayer system;
\ifodd\i\par\bigskip\leavevmode\fi
}
\end{document}
А вот способ с украшениями:
\documentclass[varwidth,border=5]{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations}
\pgfdeclaredecoration{square fractal}{start}{
\state{start}[width=0pt,next state=draw]{
\pgfpathmoveto{\pgfpointdecoratedinputsegmentfirst}
}
\state{draw}[width=\pgfdecoratedinputsegmentlength]{
\pgfpointdiff{\pgfpointdecoratedinputsegmentfirst}%
{\pgfpointdecoratedinputsegmentlast}
\pgfgetlastxy\tmpx\tmpy
\pgfmathveclen\tmpx\tmpy
\pgfmathparse{\pgfmathresult/4}%
\let\tmp=\pgfmathresult
\pgfpathlineto{\pgfpoint{\tmp}{+0pt}}
\pgfpathlineto{\pgfpoint{\tmp}{-\tmp}}
\pgfpathlineto{\pgfpoint{3*\tmp}{-\tmp}}
\pgfpathlineto{\pgfpoint{3*\tmp}{+0pt}}
\pgfpathlineto{\pgfpointdecoratedinputsegmentlast}
}
\state{final}{
\pgfpathclose
}
}
\begin{document}
\tikz[decoration=square fractal]
\fill (0,0) rectangle (4,4);
\tikz[decoration=square fractal]
\fill decorate { (0,0) rectangle (4,4) };
\\
\tikz[decoration=square fractal]
\fill decorate { decorate { (0,0) rectangle (4,4) } };
\tikz[decoration=square fractal]
\fill decorate { decorate { decorate { (0,0) rectangle (4,4) } } };
\end{document}
решение2
Решение TikZ
Черные квадраты фрактала генерируются с помощьюрасширяемыйрекурсия.
\documentclass[tikz]{standalone}
\usepackage{etoolbox}
\makeatletter
\patchcmd{\tikz@@command@path}{=100}{=10000}{}{\errmessage{Patching failed.}}
\makeatother
\makeatletter
\newcommand*{\@SquareFractal}[4]{%
% #1: order
% #2: edge length
% #3: x position of lower left corner
% #4: y position of lower left corner
\ifnum#1=0
(#3,#4)rectangle(\the\dimexpr(#3)+(#2)\relax,\the\dimexpr(#4)+(#2)\relax)%
\expandafter\@gobble
\else
\expandafter\@firstofone
\fi
{
% Middle
\expandafter\@SquareFractal
\expandafter{\the\numexpr(#1)-1\expandafter}%
\expandafter{\the\dimexpr(#2)/2\expandafter}%
\expandafter{\the\dimexpr(#3)+(#2)/4\expandafter}%
\expandafter{\the\dimexpr(#4)+(#2)/4}%
% Bottom left
\expandafter\@SquareFractal
\expandafter{\the\numexpr(#1)-1\expandafter}%
\expandafter{\the\dimexpr(#2)/4}%
{#3}%
{#4}%
% Bottom right
\expandafter\@SquareFractal
\expandafter{\the\numexpr(#1)-1\expandafter}%
\expandafter{\the\dimexpr(#2)/4\expandafter}%
\expandafter{\the\dimexpr(#3)+(#2)*3/4}%
{#4}%
% Top left
\expandafter\@SquareFractal
\expandafter{\the\numexpr(#1)-1\expandafter}%
\expandafter{\the\dimexpr(#2)/4\expandafter}%
\expandafter{\the\dimexpr(#3)\expandafter}%
\expandafter{\the\dimexpr(#4)+(#2)*3/4}%
% Top right
\expandafter\@SquareFractal
\expandafter{\the\numexpr(#1)-1\expandafter}%
\expandafter{\the\dimexpr(#2)/4\expandafter}%
\expandafter{\the\dimexpr(#3)+(#2)*3/4\expandafter}%
\expandafter{\the\dimexpr(#4)+(#2)*3/4}%
}%
}
\newcommand*{\SquareFractal}[2]{%
% #1: order
% #2: edge length
\begingroup
\edef\x{\@SquareFractal{#1}{#2}{0pt}{0pt}}%
\expandafter\tikz\expandafter\fill\x;%
\endgroup
}
\makeatother
\begin{document}
\foreach\i in {0, ..., 5} {\SquareFractal{\i}{\linewidth}}
\end{document}
Поскольку все команды рисования хранятся в памяти, память является ограничивающим фактором.
Результат для заказа 5:
Решение IniTeX
В следующем примере используются простые правила в iniTeX для рисования квадратов с целью получения более высоких порядков без исчерпания памяти.
Максимальное измерение в TeX составляет 16383,99998 pt ( \maxdimen
). Это (2 30 - 1) sp (1 pt = 2 16 sp = 65536 sp). Наименьшие квадраты следующего уровня используют длину стороны квадрата в четверть. Тогда, из этого следует, что при наименьшей длине стороны квадрата в 1 sp наибольший порядок равен 14, длина стороны результата тогда равна 2 28 sp.
В примере используется либо pdfTeX, либо luaTeX в режиме iniTeX ( pdftex -ini -etex
или luatex -ini
). LuaTeX быстрее и имеет меньше ограничений по памяти. Для сравнения, порядок 8 занимает около 45 с с pdfTeX, но 8 с с LuaTeX. Более высокие порядки с LuaTeX:
Заказ 10:продолжительность 3 3/4 мин, размер файла 47 МБ.
Заказ 11:время 33 мин, размер файла 173 МБ.
На 12-м заказе компьютер отказал, и мне пришлось перезагрузиться.
Пример:
\catcode`\{=1
\catcode`\}=2
\catcode`\#=6
\ifx\directlua\undefined
\pdfoutput=1
\pdfminorversion=4
\pdfhorigin=0pt
\pdfvorigin=0pt
\pdfcompresslevel=9
\else
\directlua{%
tex.enableprimitives('', {'outputmode', 'dimexpr', 'numexpr'})
tex.enableprimitives('pdf', {'pagewidth', 'pageheight'})
}
\outputmode=1
\directlua{
pdf.setorigin()
pdf.setminorversion(4)
pdf.setcompresslevel(9)
}
\fi
\dimendef\pagewidth=0
\dimendef\xpos=2
\def\SquareFractal#1#2{%
% #1: order
% #2: minimum edge length
\pagewidth=\dimexpr#2\MulFour#1!\relax
\immediate\write16{* Calculating square fractal of order #1 ...}%
\pdfpagewidth=\pagewidth %
\pdfpageheight=\pagewidth %
\shipout\hbox{%
\xpos=0pt\relax
\SquareFractalRecursiv#1!\pagewidth!0pt!0pt!%
\kern\dimexpr\pagewidth-\xpos\relax
}%
\advance\count0 by 1\relax
}
\def\MulFour#1!{%
\ifnum#1=0
\else
*4%
\expandafter\MulFour
\the\numexpr#1-1\expandafter!%
\fi
}
\def\SquareFractalRecursiv#1!#2!#3!#4!{%
% #1: order
% #2: edge length
% #3: x position of lower left corner
% #4: y position of lower left corner
\ifnum#1=0 %
\iffalse
\raise#4\hbox to 0pt{%
\kern#3\relax
\vrule width#2height#2\relax
\hss
}%
\else
\ifdim#3=\xpos
\else
\kern\dimexpr#3-\xpos\relax
\fi
\vrule width#2 depth-#4 height\dimexpr#4+#2\relax
\xpos=\dimexpr#3+#2\relax
\fi
\else
% Lower left square
\expandafter\SquareFractalRecursiv
\the\numexpr#1-1\expandafter!%
\the\dimexpr#2/4\expandafter!%
#3!%
#4!%
% Middle square
\expandafter\SquareFractalRecursiv
\the\numexpr#1-1\expandafter!%
\the\dimexpr#2/2\expandafter!%
\the\dimexpr#3+#2/4\expandafter!%
\the\dimexpr#4+#2/4!%
% Lower right square
\expandafter\SquareFractalRecursiv
\the\numexpr#1-1\expandafter!%
\the\dimexpr#2/4\expandafter!%
\the\dimexpr#3+#2*3/4!%
#4!%
% Upper left square
\expandafter\SquareFractalRecursiv
\the\numexpr#1-1\expandafter!%
\the\dimexpr#2/4\expandafter!%
\the\dimexpr#3\expandafter!%
\the\dimexpr#4+#2*3/4!%
% Upper right square
\expandafter\SquareFractalRecursiv
\the\numexpr#1-1\expandafter!%
\the\dimexpr#2/4\expandafter!%
\the\dimexpr#3+#2*3/4\expandafter!%
\the\dimexpr#4+#2*3/4\expandafter!%
\fi
}
% BTW, unit bp instead of pt decreases the output file size
% a bit because of less fractional digits.
% \SquareFractal{<order>}{<length of smallest square>}
% The values of the follwing calls are used in such a way
% that the generated fractals with different orders have
% the same widths and heights.
\SquareFractal{0}{4096pt}
\SquareFractal{1}{1024pt}
\SquareFractal{2}{256pt}
\SquareFractal{3}{64pt}
\SquareFractal{4}{16pt}
\SquareFractal{5}{4pt}
\SquareFractal{6}{1pt}% 65536 sp
\SquareFractal{7}{16384sp}
\SquareFractal{8}{4096sp}
\SquareFractal{9}{1024sp}
\SquareFractal{10}{256sp}
\SquareFractal{11}{64sp}
% \SquareFractal{12}{16sp}
% \SquareFractal{13}{4sp}
% \SquareFractal{14}{1sp}
\end
Результат для заказа 11 (лучшие разрешения отклонены imgur):
Из-за большого количества квадратов просмотр PDF-файла с более высокими порядками замедляет работу просмотрщика PDF-файлов.
Поэтому более эффективно генерировать монохромное растровое изображение, например, с наименьшими квадратами в виде квадратов 1 x 1 пиксель. Ширина и высота изображения для порядка 11 тогда составляет 2 22 пикселя = 4194304 пикселя.
решение3
Вот попытка с MetaPost, может кому интересно. Рекурсивный макрос (square_fractal
) в основе этой программы во многом вдохновленэтот ответктесно связанный предмет.
vardef square_fractal(expr A, B, n) =
save P; pair P[]; P0 = A; P1 = B;
for i = 1 upto 2:
P[i+1] = P[i-1] rotatedaround (P[i], -90);
endfor;
if n = 0: fill P0 for i = 1 upto 3: -- P[i] endfor -- cycle;
else:
save Q; pair Q[];
for i = 0, 2:
Q[i] = 1/4[P[i],P[i+1]]; Q[i+1] = 3/4[P[i],P[i+1]];
square_fractal(P[i], Q[i], n-1);
square_fractal(Q[i+1], P[i+1], n-1);
endfor;
square_fractal(P0 rotatedaround (Q0, -90), P1 rotatedaround (Q1, 90), n-1); fi
enddef;
beginfig(1);
for n = 0 upto 4:
draw image(square_fractal(origin, (4cm, 0), n)) shifted (n*4.5cm, 0);
endfor;
endfig;
end.
Начиная с порядка 0 (полный квадрат), MetaPost управляет выводом до порядка 6 на моей машине. Интересно, что порядок 7 достигается, если предыдущий код включен в программу LuaLaTeX. Я не знаю, почему.
РедактироватьВсе еще в LuaLaTeX, и после использования чисел с плавающей точкой ( \mplibnumbersystem{double}
добавлено сразу после \usepackage{luamplib}
) вместо чисел с фиксированной точкой по умолчанию, MetaPost удается выдать число в порядке 9 через 20 минут. Но он почти замораживает мой очень старый ноутбук (MacBook Pro 2008 года), поэтому я не решаюсь продолжать. Может быть, я попробую снова на более новом и более мощном компьютере.
\RequirePackage{luatex85}
\documentclass[border=5mm]{standalone}
\usepackage{luamplib}
\mplibnumbersystem{double}
\begin{document}
\begin{mplibcode}
vardef square_fractal(expr A, B, n) =
save P; pair P[]; P0 = A; P1 = B;
for i = 1 upto 2:
P[i+1] = P[i-1] rotatedaround (P[i], -90);
endfor;
if n = 0: fill P0 for i = 1 upto 3: -- P[i] endfor -- cycle;
else:
save Q; pair Q[];
for i = 0, 2:
Q[i] = 1/4[P[i],P[i+1]]; Q[i+1] = 3/4[P[i],P[i+1]];
square_fractal(P[i], Q[i], n-1);
square_fractal(Q[i+1], P[i+1], n-1);
endfor;
square_fractal(P0 rotatedaround (Q0, -90), P1 rotatedaround (Q1, 90), n-1); fi
enddef;
beginfig(1);
square_fractal(origin, (12cm, 0), 9);
endfig;
\end{mplibcode}
\end{document}
Рисунок ниже относится к порядку 8. Мне не удалось создать версию заказа 9 в формате PNG из-за того, что мой ноутбук почти завис.
решение4
Еще одна альтернатива с Tikz и рекурсией.
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\newcommand\DrawFracSquare[4]{{% {Current number}{Side Length}{X}{Y}
\ifnum#1=0
\fill[black] ($(#3,#4)-(#2/2,#2/2)$) rectangle +(#2,#2);
\else
\pgfmathsetmacro\NewNumber{int(#1-1)}
\pgfmathsetmacro\NewSideLength{#2/2}
\edef\NewRec{\noexpand\DrawFracSquare{\NewNumber}{\NewSideLength}{#3}{#4}}
\NewRec
\pgfmathsetmacro\NewSideLength{#2/4}
\pgfmathsetmacro\NewX{#3+3*#2/8}
\pgfmathsetmacro\NewY{#4+3*#2/8}
\edef\NewRec{\noexpand\DrawFracSquare{\NewNumber}{\NewSideLength}{\NewX}{\NewY}}
\NewRec
\pgfmathsetmacro\NewX{#3-3*#2/8}
\pgfmathsetmacro\NewY{#4+3*#2/8}
\edef\NewRec{\noexpand\DrawFracSquare{\NewNumber}{\NewSideLength}{\NewX}{\NewY}}
\NewRec
\pgfmathsetmacro\NewX{#3-3*#2/8}
\pgfmathsetmacro\NewY{#4-3*#2/8}
\edef\NewRec{\noexpand\DrawFracSquare{\NewNumber}{\NewSideLength}{\NewX}{\NewY}}
\NewRec
\pgfmathsetmacro\NewX{#3+3*#2/8}
\pgfmathsetmacro\NewY{#4-3*#2/8}
\edef\NewRec{\noexpand\DrawFracSquare{\NewNumber}{\NewSideLength}{\NewX}{\NewY}}
\NewRec
\fi
}}
\begin{document}
\begin{tikzpicture}
\DrawFracSquare{0}{3}{0}{4}
\DrawFracSquare{1}{3}{4}{4}
\DrawFracSquare{2}{3}{8}{4}
\DrawFracSquare{3}{3}{0}{0}
\DrawFracSquare{4}{3}{4}{0}
\DrawFracSquare{5}{3}{8}{0}
\end{tikzpicture}
\end{document}