Paintball plots in LaTeX

The LaTeX TikzPaintballPlot draws vote share directly on the x-axis and seat share directly on the y-axis as native TikZ, so a report can include it without rasterizing. The first dataset arrives at construction; seats can be counts with total_seats= or already-normalized shares.

from gerrytools.latex import TikzPaintballPlot

seats = [13, 15, 17, 21, 21, 14, 14, 15, 13, 16, 24, 24, 24, 24]
seat_shares = [s / 29 for s in seats]
vote_shares = [
    0.5231,
    0.4782,
    0.5057,
    0.5674,
    0.5865,
    0.5007,
    0.5020,
    0.5174,
    0.4996,
    0.5169,
    0.5977,
    0.5948,
    0.5930,
    0.5650,
]


plot = TikzPaintballPlot(vote_shares, seat_shares)
plot.add_efficiency_gap_line()
plot.add_proportionality_line()
plot.set_marker_options(color="cadmiumgreen")
print(plot)
\documentclass[border=2pt]{standalone}
\usepackage{latexcolors, tikz}
\begin{document}
\begin{tikzpicture}
\begin{scope}[xscale=10.0, yscale=10.0]

\clip [draw] (0.0, 0.0) rectangle (1.0, 1.0);

{\color{gray!50}\draw [line width=5.00pt] (0.5, 0.0) -- (0.5, 1.0);}
{\color{gray!50}\draw [line width=5.00pt] (0.0, 0.5) -- (1.0, 0.5);}

{\color{gray}\draw [line width=1.00pt, solid] (0.25, 0.0) -- (0.75, 1.0);}
{\color{gray}\draw [line width=1.00pt, dashed] (0.0, 0.0) -- (1.0, 1.0);}

\foreach \votes/\seats in {
    0.5231/0.4483,
    0.4782/0.5172,
    0.5057/0.5862,
    0.5674/0.7241,
    0.5865/0.7241,
    0.5007/0.4828,
    0.5020/0.4828,
    0.5174/0.5172,
    0.4996/0.4483,
    0.5169/0.5517,
    0.5977/0.8276,
    0.5948/0.8276,
    0.5930/0.8276,
    0.5650/0.8276
} {
    \node [transform shape=false, circle, inner sep=0pt, minimum size=8.00pt, fill=cadmiumgreen, draw=cadmiumgreen, fill opacity=0.8000, line width=0.50pt, draw opacity=1.0000] at (\votes, \seats) {};
}

\end{scope}
\end{tikzpicture}

\end{document}

TikZ paintball plot with actual election data

set_xlim / set_ylim / set_scale control the window and scaling; set_crosshair_options() styles the 50-50 crosshairs; add_efficiency_gap_line() and add_proportionality_line() add the standard guides, and add_lines_with_slope() adds reference lines through the center with any slope.

The hull view

hull_document (and preview(hull=True)) renders the horizontal envelope of the cloud instead of the points. set_hull_options() styles the fill and edge.

plot.set_hull_options(
    color="denim",
    alpha=0.25,
    edgecolor="denim",
    edgewidth=1.5,
)
print(plot)
\documentclass[border=2pt]{standalone}
\usepackage{latexcolors, tikz}
\begin{document}
\begin{tikzpicture}
\begin{scope}[xscale=10.0, yscale=10.0]

\clip [draw] (0.0, 0.0) rectangle (1.0, 1.0);

{\color{gray!50}\draw [line width=5.00pt] (0.5, 0.0) -- (0.5, 1.0);}
{\color{gray!50}\draw [line width=5.00pt] (0.0, 0.5) -- (1.0, 0.5);}

{\color{gray}\draw [line width=1.00pt, solid] (0.25, 0.0) -- (0.75, 1.0);}
{\color{gray}\draw [line width=1.00pt, dashed] (0.0, 0.0) -- (1.0, 1.0);}

\foreach \votes/\seats in {
    0.5231/0.4483,
    0.4782/0.5172,
    0.5057/0.5862,
    0.5674/0.7241,
    0.5865/0.7241,
    0.5007/0.4828,
    0.5020/0.4828,
    0.5174/0.5172,
    0.4996/0.4483,
    0.5169/0.5517,
    0.5977/0.8276,
    0.5948/0.8276,
    0.5930/0.8276,
    0.5650/0.8276
} {
    \node [transform shape=false, circle, inner sep=0pt, minimum size=8.00pt, fill=cadmiumgreen, draw=cadmiumgreen, fill opacity=0.8000, line width=0.50pt, draw opacity=1.0000] at (\votes, \seats) {};
}

\end{scope}
\end{tikzpicture}

\end{document}

The hull view of the same data

Copy it into a document

Printing the plot shows the complete standalone source (hull_document.to_tex() is the hull variant as a string), and plot.print() writes the TikZ body alone for pasting into an existing report.