[Tex/LaTex] Finding centroid of the content in whole tikzpicture, scope or node

tikz-pgf

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"

Best Answer

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.