我正在尝试定义一组可用于绘制黎曼曲面的形状。为此,我非常感谢 Andrew Stacey 的 tqft 包。
我在定义一个形状来表示我们用来表示表面上的属的标准简写时遇到了一些麻烦。我希望该形状能够绘制弧线,但当我填充它时,只填充“洞”。我目前有一个 hack 解决方案(下面的代码片段),但它的工作原理是用白色填充洞并忽略任何填充。
从我的具体情况中抽象出来:
如何分别定义形状的描边和填充部分?
编辑:我在我的例子中包含了更多细节。
首先,举一个玩具示例:我有一个名为“太阳”的形状,它由一个带有射线的圆盘和外面较小的圆圈组成。当我填充它时,我希望它给我一个绿色的主圆盘,但外圆未填充。(即下面两张图片的混合:外圆应与左侧匹配,内圆应与右侧匹配)。
这是太阳的代码:
\documentclass[convert]{standalone}
\usepackage{tikz, pgf}
\usetikzlibrary{shapes,calc}
\makeatletter
\pgfdeclareshape{sun}{
\savedanchor{\centre}{ \pgfpointorigin}
\anchor{center}{\centre}
\savedanchor{\top}{
\pgf@x = 0cm
\pgf@y = 0.15cm
}
\anchor{top}{\top}
\savedanchor{\bottom}{
\pgf@x = 0cm
\pgf@y = -0.375cm
}
\anchor{bottom}{\bottom}
\backgroundpath{
\foreach \x in {0,45,...,360} {
\pgfpathmoveto{\pgfpointpolarxy{ \x }{ 1.2 }}
\pgfpathlineto{ \pgfpointpolarxy{\x }{2 } }
\pgfpathcircle{\pgfpointpolarxy{\x}{2.2}}{2pt}
}
\pgfpathcircle{\pgfpointorigin}{1cm}
}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\node [sun, draw, scale=.5, fill=green] at (2,0) {};
\node [sun, draw, scale=.5, fill=none] at (-2,0) {};
\end{tikzpicture}
\end{document}
现在,来看真实的例子:带有 genus 的图片。我有四种形状:genuspic、hackgenuspic、strokegenuspic 和 fillhackgenuspic。图片从上到下依次为:带有 fill=green 的 genuspic、带有 fill=green 的 hackgenuspic、带有 fill=green 的 fillhackgenuspic、带有 fill=none 的 fillhackgenuspic、strokegenuspic。Genuspic 会按照我的要求绘制弧线。Hackgenuspic 会按照我的要求绘制弧线,还会绘制一条限制洞的路径。然后它会填充该路径(白色,忽略 fill=green)。
我怎样才能得到一个可以执行 hackgenuspic 所做操作的形状,但其填充颜色注意 fill=green?
以下是黎曼曲面示例的代码:
\documentclass[convert]{standalone}
\usepackage{tikz, pgf}
\usetikzlibrary{shapes}
\makeatletter
\pgfdeclareshape{genuspic}{
\anchor{center}{\pgfpointorigin}
\backgroundpath{
\pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
\pgfpathcurveto %
{\pgfpoint{-0.5cm}{-.5cm}}
{\pgfpoint{0.5cm}{-.5cm}}
{\pgfpoint{1cm}{0cm}}
\pgfpathmoveto{\pgfqpoint{-0.75cm}{-0.15cm}}
\pgfpathcurveto %
{\pgfpoint{-0.25cm}{.25cm}}
{\pgfpoint{.25cm}{.25cm}}
{\pgfpoint{0.75cm}{-0.15cm}}
}
}
\pgfdeclareshape{strokegenuspic}{
\anchor{center}{\pgfpointorigin}
\backgroundpath{
\pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
\pgfpathcurveto %
{\pgfpoint{-0.5cm}{-.5cm}}
{\pgfpoint{0.5cm}{-.5cm}}
{\pgfpoint{1cm}{0cm}}
\pgfpathmoveto{\pgfqpoint{-0.75cm}{-0.15cm}}
\pgfpathcurveto %
{\pgfpoint{-0.25cm}{.25cm}}
{\pgfpoint{.25cm}{.25cm}}
{\pgfpoint{0.75cm}{-0.15cm}}
\pgfusepath{stroke}
}
}
\pgfdeclareshape{hackgenuspic}{
\anchor{center}{\pgfpointorigin}
\backgroundpath{
\pgfpathmoveto{\pgfqpoint{-0.78cm}{-.17cm}}
\pgfpathcurveto %
{\pgfpoint{-0.35cm}{-.44cm}}
{\pgfpoint{0.35cm}{-.44cm}}
{\pgfpoint{.78cm}{-0.17cm}}
\pgfpathmoveto{\pgfqpoint{-0.78cm}{-0.17cm}}
\pgfpathcurveto %
{\pgfpoint{-0.25cm}{.25cm}}
{\pgfpoint{.25cm}{.25cm}}
{\pgfpoint{0.78cm}{-0.17cm}}
\pgfsetfillcolor{white}
\pgfusepath{fill}
\pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
\pgfpathcurveto %
{\pgfpoint{-0.5cm}{-.5cm}}
{\pgfpoint{0.5cm}{-.5cm}}
{\pgfpoint{1cm}{0cm}}
\pgfpathmoveto{\pgfqpoint{-0.75cm}{-0.15cm}}
\pgfpathcurveto %
{\pgfpoint{-0.25cm}{.25cm}}
{\pgfpoint{.25cm}{.25cm}}
{\pgfpoint{0.75cm}{-0.15cm}}
\pgfusepath{stroke}
}
}
\pgfdeclareshape{fillhackgenuspic}{
\anchor{center}{\pgfpointorigin}
\backgroundpath{
\pgfpathmoveto{\pgfqpoint{-0.78cm}{-.17cm}}
\pgfpathcurveto %
{\pgfpoint{-0.35cm}{-.44cm}}
{\pgfpoint{0.35cm}{-.44cm}}
{\pgfpoint{.78cm}{-0.17cm}}
\pgfpathmoveto{\pgfqpoint{-0.78cm}{-0.17cm}}
\pgfpathcurveto %
{\pgfpoint{-0.25cm}{.25cm}}
{\pgfpoint{.25cm}{.25cm}}
{\pgfpoint{0.78cm}{-0.17cm}}
\pgfusepath{fill}
\pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
\pgfpathcurveto %
{\pgfpoint{-0.5cm}{-.5cm}}
{\pgfpoint{0.5cm}{-.5cm}}
{\pgfpoint{1cm}{0cm}}
\pgfpathmoveto{\pgfqpoint{-0.75cm}{-0.15cm}}
\pgfpathcurveto %
{\pgfpoint{-0.25cm}{.25cm}}
{\pgfpoint{.25cm}{.25cm}}
{\pgfpoint{0.75cm}{-0.15cm}}
\pgfusepath{stroke}
}
}
\makeatother
\begin{document}
\begin{tikzpicture}[scale=.5]
\fill[color=black!10] (-6,3) arc (-180:0:2 cm and 1 cm)
(-2,3) .. controls +(-60:1) and +(-120:1) .. (2,3)
(2,3) arc (-180:0:2 cm and 1 cm)
(6,3) .. controls +(-90:2) and +(0:4) .. (0,-10) .. controls +(180:4) and +(-90:2) .. (-6,3);
\node [genuspic, draw, scale=1, fill=green] at (0,0) {};
\node [hackgenuspic, draw, scale=1, fill=green] at (0,-2) {};
\node [fillhackgenuspic, draw, scale=1, fill=green] at (0, -4) {};
\node [fillhackgenuspic, draw, scale=1, fill=none] at (0, -6) {};
\node [strokegenuspic, draw, scale=1, fill=green] at (0, -8) {};
\draw (-4,3) ellipse (2 cm and 1 cm)
(-2,3) .. controls +(-60:1) and +(-120:1) .. (2,3)
(4,3) ellipse (2cm and 1cm)
(6,3) .. controls +(-90:2) and +(0:4) .. (0,-10) .. controls +(180:4) and +(-90:2) .. (-6,3);
\end{tikzpicture}
\end{document}
答案1
TikZ 不允许你将fill
和draw
命令应用于路径的单独部分。相关部分在宏中\tikz@finish
。在正常路径上,绘制和填充由以下部分完成:
\edef\tikz@temp{\noexpand\pgfusepath{%
\iftikz@mode@fill fill,\fi%
\iftikz@mode@draw draw,\fi%
\iftikz@mode@clip clip,\fi%
}}%
\tikz@temp%
在节点形状中,\backgroundpath
是此时使用的路径。现在你不想要这个:你希望能够指定 的一部分\backgroundpath
作为draw
, 的一部分为fill
。所以你必须指定两个路径。实际上,它们都不能位于 TikZ 通常绘制或填充的位置,因为它们都不应该有两个都应用的操作。通常,我建议使用节点形状可以有多个与之关联的路径(\beforebackgroundpath
等等)这一事实,但这样一来,您将在将样式传递给不同部分时遇到麻烦(在 TQFT 包中,我使用它来设置共边的各个部分的样式,但在那里这样做是合理的,因为有不同的组件。在这里,您只有一件事,而将其拆分则不太方便用户使用。)。
因此,我们只需要颠覆正常的 TikZ 机制并将其替换为我们自己的机制。这意味着\backgroundpath
在定义时使用,而不是留到以后。您已经在hackgenuspic
(和其他)形状中执行了此操作。您唯一不需要做的就是使用它有条件的。
在处理时\backgroundpath
,TikZ 的所有路径选项都已处理。因此,TikZ 知道填充颜色、线宽、绘制颜色等等。唯一需要注意的是没有唯一需要知道的是是否要填充或绘制路径。这只能在结束时确定。(这是合理的:路径的定义可以取决于选项,但并不取决于通常取决于要采取的行动。)幸运的是,找出所需的行动并不难:我们只需执行\tikz@mode
。这设置了一些条件,即\iftikz@mode@fill
和\iftikz@mode@draw
(还有\iftikz@mode@clip
和一些其他条件 - 对于真正强大的解决方案,我们也应该考虑它们)。
从那以后,剩下的就相当简单了。我们执行\tikz@mode
,如果需要fill
路径,我们定义并填充洞,如果需要绘制它,我们定义并绘制外部部分。考虑到我们使用的时间\tikz@mode
比平时早一点,我把它放在一个组中。不过,我怀疑我在这方面过于谨慎,因为 TikZ 非常谨慎,不会做出假设。
这是我的代码。形状基于hackgenuspic
(但我将其重命名为genus
)。我很可能遗漏了某些内容 - 我没有对其进行压力测试!如果出现问题,请告诉我。(如果没有,它似乎是 TQFT 包的一个合理补充 - 您觉得如何?)
\documentclass[convert]{standalone}
%\documentclass{article}
\usepackage{tikz, pgf}
\usetikzlibrary{shapes}
\makeatletter
\pgfdeclareshape{genus}{
\anchor{center}{\pgfpointorigin}
\backgroundpath{
\begingroup
\tikz@mode
\iftikz@mode@fill
\pgfpathmoveto{\pgfqpoint{-0.78cm}{-.17cm}}
\pgfpathcurveto %
{\pgfpoint{-0.35cm}{-.44cm}}
{\pgfpoint{0.35cm}{-.44cm}}
{\pgfpoint{.78cm}{-0.17cm}}
\pgfpathmoveto{\pgfqpoint{-0.78cm}{-0.17cm}}
\pgfpathcurveto %
{\pgfpoint{-0.25cm}{.25cm}}
{\pgfpoint{.25cm}{.25cm}}
{\pgfpoint{0.78cm}{-0.17cm}}
\pgfusepath{fill}
\fi
\iftikz@mode@draw
\pgfpathmoveto{\pgfqpoint{-1cm}{0cm}}
\pgfpathcurveto %
{\pgfpoint{-0.5cm}{-.5cm}}
{\pgfpoint{0.5cm}{-.5cm}}
{\pgfpoint{1cm}{0cm}}
\pgfpathmoveto{\pgfqpoint{-0.75cm}{-0.15cm}}
\pgfpathcurveto %
{\pgfpoint{-0.25cm}{.25cm}}
{\pgfpoint{.25cm}{.25cm}}
{\pgfpoint{0.75cm}{-0.15cm}}
\pgfusepath{stroke}
\fi
\endgroup
}
}
\makeatother
\begin{document}
\begin{tikzpicture}[scale=.5]
\fill[color=black!10] (-6,3) arc (-180:0:2 cm and 1 cm)
(-2,3) .. controls +(-60:1) and +(-120:1) .. (2,3)
(2,3) arc (-180:0:2 cm and 1 cm)
(6,3) .. controls +(-90:2) and +(0:4) .. (0,-10) .. controls +(180:4) and +(-90:2) .. (-6,3);
\node [genus, scale=1, fill=green] at (0,0) {};
\node [genus, draw, scale=1, fill=orange] at (0,-2) {};
\node [genus, draw, ultra thick, scale=1, fill=green] at (0, -4) {};
\node [genus, draw, scale=.7] at (0, -6) {};
\node [genus, draw, dashed, scale=.5, fill=green] at (0, -8) {};
\draw (-4,3) ellipse (2 cm and 1 cm)
(-2,3) .. controls +(-60:1) and +(-120:1) .. (2,3)
(4,3) ellipse (2cm and 1cm)
(6,3) .. controls +(-90:2) and +(0:4) .. (0,-10) .. controls +(180:4) and +(-90:2) .. (-6,3);
\end{tikzpicture}
\end{document}
结果:
答案2
正如评论中提到的,填充fill
仅适用于由路径封闭的区域。因此,如果您想将填充应用于非封闭路径的区域,则需要提供定义封闭区域的附加路径。
这是一个简单的例子,其中路径定义为\MyPath
不是关闭,并填充它你定义一个新的路径是关闭。
\documentclass[border=2pt]{standalone}
\usepackage{tikz}
\newcommand{\MyPath}{(0,0) -- (1,0) -- (2,2)}%
\begin{document}
\begin{tikzpicture}
\draw [draw=blue, ultra thick] \MyPath;
\end{tikzpicture}
%
\begin{tikzpicture}
\fill [draw=none, fill=yellow] \MyPath -- cycle;
\draw [draw=blue, ultra thick] \MyPath;
\end{tikzpicture}
\end{document}
答案3
只为阳光下的乐趣,一个可以填写在里面或外面的代码。这里有用的代码是
\ifsunfill \pgfusepath{fill,stroke} \else \pgfusepath{stroke} \fi
\documentclass{article}
\usepackage{tikz, pgf}
\usetikzlibrary{shapes,calc}
\newif\ifsunfill \sunfillfalse
\makeatletter
\pgfdeclareshape{sun}{%
\savedanchor{\centre}{ \pgfpointorigin}
\anchor{center}{\centre}
\savedanchor{\top}{
\pgf@x = 0cm
\pgf@y = 0.15cm
}
\anchor{top}{\top}
\savedanchor{\bottom}{
\pgf@x = 0cm
\pgf@y = -0.375cm
}
\anchor{bottom}{\bottom}
\backgroundpath{
\foreach \x in {0,45,...,360} {%
\pgfpathmoveto{\pgfpointpolarxy{ \x }{ 1.2 }}
\pgfpathlineto{ \pgfpointpolarxy{\x }{2 } }
\pgfpathcircle{\pgfpointpolarxy{\x}{2.2}}{2pt}
}
\ifsunfill \pgfusepath{fill,stroke} \else \pgfusepath{stroke} \fi
\pgfpathcircle{\pgfpointorigin}{1cm}
\ifsunfill \pgfusepath{stroke} \else \pgfusepath{fill,stroke} \fi
}}
\makeatother
\begin{document}
\tikzset{fill out/.is if=sunfill}
\begin{tikzpicture}
\node [sun, draw, scale=.5, fill=green] at (2,0) {};
\node [sun, draw, scale=.5, fill=green,fill out] at (-2,0) {};
\end{tikzpicture}
\end{document}
(由 Andrew Stacey 在编辑中添加:使用.is if
处理程序意味着可以编写fill out=true
和fill out=false
来回切换,从而更容易在样式中使用。现在您可以编写every node/.style={fill out}
来设置全局默认值并fill out=false
在特定实例上覆盖它。fill out
不带参数的默认值为fill out=true
。)