9

Does TikZ allow moderately easy implementation of finding centroid of the content in tikzpicture, scope or node? Could you show it?

Let's say the only assumption is intuitive one, that each 1pt dot has some unit weight. For simple drawings, like triangle, filled triangle, etc. it is easy to find it manually, but some help of TikZ in more complex cases (many circles with unequal radii [results are obviously different if these circles are filled], etc) would be appreciated.

Side note: There is no point (usually) in considering mixed drawings (filled and not filled in one picture), so solutions can (and, if at all, possibly will) be different for filled and path-only drawings. Universal solution would be the best, though.

Example using Ryan's solution

I couldn't resist and do not give my own example. Sorry!

\documentclass{minimal}
\usepackage{tikz}
\usepackage[a5paper,margin=16mm,top=4cm]{geometry}
\usetikzlibrary{decorations.markings,scopes}
\newcommand\globallist[2]{\global\edef#1{#1#2}}
\tikzset{bary markings/.style = {
    decoration = {
        markings,
        mark = between positions 0 and 1 step .1 with {
            \edef\number{\pgfkeysvalueof{/pgf/decoration/mark info/sequence number}}
            \coordinate (r\number);
            \globallist\refpoints{r\number=1,}
        }
    },
    postaction = {decorate}
}}
\def\refpoints{}
\def\docentroid{
    \coordinate (fake) at (5,0);
    \globallist\refpoints{fake=0}
    \draw[fill] (barycentric cs:\refpoints) circle (2mm);
    \global\def\refpoints{}
}
\begin{document}
\pagestyle{empty}
\begin{tikzpicture}[y=3mm, x=3mm, yscale=-1, inner sep=0pt, outer sep=0pt]
    \draw[bary markings]
        (161.8225,329.7121) .. controls (156.5960,329.7121) and
        (152.9749,330.3097) .. (150.9593,331.5050) .. controls (148.9436,332.7004) and
        (147.9358,334.7394) .. (147.9358,337.6222) .. controls (147.9358,339.9191) and
        (148.6858,341.7472) .. (150.1858,343.1066) .. controls (151.7092,344.4425) and
        (153.7717,345.1105) .. (156.3733,345.1105) .. controls (159.9592,345.1105) and
        (162.8303,343.8449) .. (164.9866,341.3136) .. controls (167.1663,338.7589) and
        (168.2561,335.3722) .. (168.2561,331.1535) -- (168.2561,329.7121) --
        (161.8225,329.7121)(174.7249,327.0402) -- (174.7249,349.5050) --
        (168.2561,349.5050) -- (168.2561,343.5285) .. controls (166.7795,345.9191) and
        (164.9397,347.6886) .. (162.7366,348.8371) .. controls (160.5335,349.9621) and
        (157.8381,350.5246) .. (154.6507,350.5246) .. controls (150.6194,350.5246) and
        (147.4085,349.3996) .. (145.0179,347.1496) .. controls (142.6507,344.8761) and
        (141.4671,341.8410) .. (141.4671,338.0441) .. controls (141.4671,333.6144) and
        (142.9436,330.2746) .. (145.8968,328.0246) .. controls (148.8733,325.7746) and
        (153.3030,324.6496) .. (159.1858,324.6496) -- (168.2561,324.6496) --
        (168.2561,324.0168) .. controls (168.2561,321.0402) and (167.2717,318.7434) ..
        (165.3030,317.1261) .. controls (163.3577,315.4855) and (160.6155,314.6652) ..
        (157.0764,314.6652) .. controls (154.8264,314.6652) and (152.6350,314.9348) ..
        (150.5022,315.4738) .. controls (148.3694,316.0129) and (146.3186,316.8215) ..
        (144.3499,317.8996) -- (144.3499,311.9230) .. controls (146.7171,311.0090) and
        (149.0139,310.3293) .. (151.2405,309.8839) .. controls (153.4671,309.4152) and
        (155.6350,309.1809) .. (157.7444,309.1808) .. controls (163.4397,309.1809) and
        (167.6936,310.6574) .. (170.5061,313.6105) .. controls (173.3186,316.5637) and
        (174.7248,321.0402) .. (174.7249,327.0402);
    \docentroid
\end{tikzpicture}
\end{document}

centroid of letter "a"

przemoc
  • 2,142
  • 1
    I don't know of any easy way to use TikZ to compute areas (surface integrals) but it does know how to compute lengths (line integrals): the decorations.markings library has the key /pgf/decoration/mark info/distance from start; see the manual, section 30.5. If you were really crazy you could probably find a way to trick it into computing line integrals of nonconstant functions and then use Green's theorem to get areas. – Ryan Reich Jun 25 '11 at 01:08
  • Note: that would take approximately one million years of computer time to finish, however. Decorations are slow. – Ryan Reich Jun 25 '11 at 01:08
  • @Ryan: Thank you for your insightful comment about distance from start. I can only add that it's not available in ver. 2.0 (provided by TeXLive 2009, available in Debian Squeeze). – przemoc Jun 25 '11 at 08:35
  • That's quite likely; v2.10 has quite a few improvements over v2.0 and is also years newer. As others have advised me, you should install v2.10 manually in your ~/texmf tree; it is not hard and well worth it. You should also install TeXLive yourself rather than use a repo, but I have never been able to take that advice. – Ryan Reich Jun 25 '11 at 13:20
  • My example for some reasons (unknown to me) is very fragile, i.e. changing x and y size, increasing mark resolutions, etc. easily make it blank (sometimes with circle on second page). – przemoc Jun 25 '11 at 18:13

2 Answers2

8

Here is a strange suggestion based on Gonzalo's idea of using the barycentric coordinate system. Using the decorations.markings library, you can mark a curve periodically. Using those computed reference points, you can do a giant barycentric computation to find the approximate centroid. To wit:

\documentclass{article}
\usepackage{tikz,nopageno}
\usetikzlibrary{decorations.markings,scopes}
\newcommand{\globallist}[2]{%
 \global\edef#1{#1#2}%
}
\tikzset{bary markings/.style = {
  decoration = {
   markings,
   mark = between positions 0 and 1 step .1 with
    {
     \edef\number{\pgfkeysvalueof{/pgf/decoration/mark info/sequence number}}
     \coordinate (r\number);
     \globallist\refpoints{r\number=1,}
    }
  },
  postaction = {decorate}
 }
}
\def\refpoints{}
\def\docentroid{
 \coordinate (fake) at (5,0);
 \globallist\refpoints{fake=0}
 \node [circle = 3pt, fill = black] at (barycentric cs:\refpoints) {};
 \global\def\refpoints{}
}
\begin{document}
 \begin{tikzpicture}
  { [shift = {(0,0)}]
   \draw [bary markings] (90:2cm) -- (210:2cm) -- (-30:2cm) -- cycle;
   \docentroid
  }
  { [shift = {(2.5cm,0)}]
   \draw [bary markings] (0,0) .. controls (1,1) and (2,-1) .. (3,0) -- (3,2) -- (0,2) -- cycle;
   \docentroid;
  }
  { [shift = {(6.5cm,0)}]
   \draw [bary markings]
    (0,0) parabola bend (2,2) (3,1) .. controls (2.5, -1) and (1,-1/3) .. (1,-1) -- cycle;
   \docentroid
  }
  { [shift = {(10cm, 3cm)}]
   \draw [bary markings] (0,0) -- (1,0) -- (1,-4) -- (4,-4) -- (4,-5) -- (0,-5) -- cycle;
   \docentroid
  }
 \end{tikzpicture}
\end{document}

enter image description here

The last example demonstrates (at xport's request) finding the centroid of a very nonconvex region.

An explanation for the unfamiliar. The important computation here is not so much the barycentric coordinates as the marked points, which are constructed with:

decoration = {
 markings,
 mark = between positions 0 and 1 step .1 with
  {
   \edef\number{\pgfkeysvalueof{/pgf/decoration/mark info/sequence number}}
   \coordinate (r\number);
   \globallist\refpoints{r\number=1,}
  }
 },
 postaction = {decorate}
}

As the manual (v2.10, section 30.5) explains, the markings decoration destroys the original path, but since I am only using it to produce coordinates, I have to apply it as a postaction (thus, it just destroys a copy of the path). I chose to put only 10 (well, 11) markings on the curve to avoid unnecessary slowdown, since decorations proceed at a stately pace. Also, as przemoc noted, the numerical computations can go wrong when very large or very small numbers are involved.

I've also gathered the future argument of the barycentric coordinate in a list \refpoints, which I'm assembling with a quick-and-dirty macro \globallist:

\newcommand{\globallist}[2]{%
 \global\edef#1{#1#2}%
}
\def\refpoints{}

I wanted to use \pgfkeys (namely, the .append handler) to assemble the list, but it turns out that the markings are executed in a TeX group and so the list needs to be set globally, and I couldn't figure out how to coerce \pgfkeys into doing that.

Once the path is prepared, the centroid can be computed:

\def\docentroid{
 \coordinate (fake) at (5,0);
 \globallist\refpoints{fake=0}
 \node [circle = 3pt, fill = black] at (barycentric cs:\refpoints) {};
 \global\def\refpoints{}
}

The point of the coordinate (fake) is to eat up the comma at the end of the list \refpoints. Alas, although barycentric cs: takes a comma-separated list of node=weight assignments, that list does not have the conveniences of a list of PGF keys, and neither spaces nor trailing commas are allowed. One hopes that this will be fixed in a future version, but in any case, giving (fake) a weight of zero makes TikZ ignore it anyway.

Ryan Reich
  • 37,958
  • Awesome example, thanks! Makes me even more encouraged to update TikZ&PGF, just to compile myself what you provided. :) 0.1 resolution won't be good for complex (and long) paths, but it's only a technicality. – przemoc Jun 25 '11 at 15:48
  • Now I am happy 2.10 user (I overlooked that in debian squeeze it's as simple as grabbing this deb made for wheezy/sid and installing it). Changing 0.1 to 0.01 substantially increases compile time and difference is negligible in your example. Unfortunately 0.001 is too slow and doesn't even work correctly, but it's possibly TeX's limitation. – przemoc Jun 25 '11 at 16:21
  • @przemoc: I chose .1 because I was afraid of arithmetic errors; TeX is not very precise, unfortunately. I guess you should stick with O(.1) or, if you have a very complex curve, O(.01). – Ryan Reich Jun 25 '11 at 16:24
  • @Ryan: Can it still work for an L-shape object (example)? – Display Name Jun 25 '11 at 16:44
  • 2
    @xport: I can't post a picture in comments, but \draw [bary markings] (0,0) -- (1,0) -- (1,-4) -- (4,-4) -- (4,-5) -- (0,-5) -- cycle; \docentroid does put the centroid just above the bend of the L, outside the figure, as you would want. – Ryan Reich Jun 25 '11 at 16:53
  • @xport: You may check also my own example just added to question. – przemoc Jun 25 '11 at 18:09
  • @Ryan: In my example I had to replace \node [circle = 3pt, fill = black] at (barycentric cs:\refpoints) {}; because circle option wasn't working as expected (at all?). – przemoc Jun 25 '11 at 18:11
  • @przemoc: Actually, I don't know why it worked for me. – Ryan Reich Jun 25 '11 at 18:58
  • "Doesn't even work correctly" example once again, this time using stack.imgur: http://i.stack.imgur.com/DHxmX.png (so it shouldn't ever disappear) – przemoc Jul 05 '11 at 23:59
7

Please refer to Section 13.2.2 Barycentric Systems of the pgfmanual. A little example:

\documentclass{article}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}
\coordinate (A) at (90:3cm);
\coordinate (B) at (210:3cm);
\coordinate (C) at (-30:3cm);
\draw [thick,gray] (A) -- (B) -- (C) -- cycle;
\draw[fill=blue]  (A) circle [radius=4pt];
\draw[fill=red]  (B) circle [radius=6pt];
\draw[fill=green]  (C) circle [radius=8pt];
\node at (barycentric cs:A=4,B=6,C=8) {TikZ};
\end{tikzpicture}

\end{document}

enter image description here

Gonzalo Medina
  • 505,128
  • Nice! However, using anchors like south etc. has no real affect on coordinates, because they don't have a size. All anchors are therefore at the same place. – Martin Scharrer Jun 24 '11 at 22:20
  • @Martin: you're right. I'll update the example. – Gonzalo Medina Jun 24 '11 at 22:26
  • @Gonzalo: Thanks. Barycentric coordinate system is not as generic and DRY-ful solution as one would want, but it surely can be helpful. After all I doubted whether TikZ can have integration-like features, it would be too good to be true. – przemoc Jun 24 '11 at 22:40
  • @przemoc: provided you are willing to do some computations yourself, the barycentric system can get you far. This is the case if your diagram is the disjoint union of regions each of whose areas and centroids you can compute independently; then you can use the barycentric system with reference points at each of the centroids having weights each of the areas to find the centroid of the whole picture. You can even use the barycentric system to find the centroids of the pieces, if they are geometrically simple; otherwise, at least you may have an easier integration problem. – Ryan Reich Jun 25 '11 at 13:57
  • @Ryan: Yes, I'm aware of all that. It's what I meant saying "[barycentric system] surely can be helpful". – przemoc Jun 25 '11 at 14:49
  • @premoc: Sorry, I didn't mean to condescend. Since Gonzalo gave a very simple example, I thought it would be helpful (to you, to someone else) to set out what else might be possible. – Ryan Reich Jun 25 '11 at 15:03
  • @Ryan: No, you weren't condescend, maybe I just shouldn't respond if I didn't have anything useful to add. I always appreciate clearing things up, explaining even possibly known or understood ideas and exemplifying them, because it clearly shows that one wants to share the knowledge and is really concerned about helping, which is great! Moreover, there are always others that may come up here later and be thankful for comments, that are maybe not essential for your interlocutor, but still might be necessary for them. Do not ever hesitate to add such comments! – przemoc Jun 25 '11 at 15:29
  • @Gonzalo: Because I cannot accept two answers, so I'll go with Ryan's answer, which tries to solve more complex cases out-of-the-box. But you've opened this "box", so thank you very much! – przemoc Jun 27 '11 at 12:41