This release closes the structural gap in the design and brings the package up to date with ggplot2 4.x. A taichi grid is a superposition comparison: it shows two sources in one position, which is what makes spatial patterns directly comparable, and which is also why it can say “which is bigger here?” but not “by how much?”. 0.3.0 answers the second question three ways (as a third channel of the glyph, as a companion heatmap, and as a table) and adds the two things a colour-encoded chart needs to be trustworthy: an interactive route to the exact values, and a way to check that its two colour ramps are a fair pair.
Nothing about the default appearance changes.
explicit = computes a third channel
from the two sources ("difference", "ratio",
"log_ratio" or "z"), and
explicit_channel = decides where it goes:
"eye_size" (the default) puts the gap in the eyes,
which already exist and are visually subordinate to the fills. Cells
where the two sources agree exactly get no eye, so a plain glyph
means agreement."angle" puts it in the glyph’s tilt. Direction is read
far more accurately than shading, so this is the most precise of the
four: upright means the sources agree, and the lean shows which way and
how far."border" puts it in the outline width, and
"radius" in the glyph’s size, scaled by area rather than by
diameter (with the perceptual correction described under
radius_exponent below). explicit_range = sets
the output range. The statistic is rescaled across the whole layer, so
facets stay comparable, and driving the same channel by hand as well is
an error rather than a silent override.geom_taichi_diff() draws the same
statistic as a diverging heatmap, with limits symmetric about “the two
sources agree”. Sometimes the right chart for “how much bigger?” is not
a glyph, and the package would rather say so than insist.taichi_summary() returns the numbers
per cell: both values, the difference, the ratio, the log ratio, the
standardised difference, which source dominates, and the cell’s rank by
the size of the gap.NA, never
Inf, in all three, and the two geoms warn when that
happens.interactive = TRUE makes the fish and
their eyes ggiraph
grobs, so ggiraph::girafe() turns the plot into a widget.
This matters more than convenience: fill is the least accurate channel
there is, and hovering is how a colour-encoded chart supplies exact
values without abandoning its encoding. The default tooltip carries both
values, their difference and the cell’s coordinates.data_id_by = decides what a hover
highlights: "cell" (both fish of one glyph, the default),
"fish", or "source", which lights up every
fish of one source at once, temporarily turning the superposition
display into a single-source one. That is the one thing a static
superposition cannot do.tooltip, data_id and onclick
take a data column to override any of it.interactive = FALSE
the package does not load ggiraph at all, and the interactive geometry
is identical to the static geometry. ggiraph is a Suggests-only
dependency.ggplotly() cannot translate the custom grobs this package
draws. The help page now says so.taichi_check_palette() measures a pair
of ramps: per-step luminance and chroma, the largest luminance mismatch,
whether each ramp is monotone, and (with colorspace
installed) how far apart the two stay under deuteranopia, protanopia and
tritanopia, against a normal-vision baseline. Run on the package’s own
defaults it returns FAIL: the grey yin ramp spans the
full luminance range while the red yang ramp stops around L* 41, a
mismatch of about 41 units, so equal values have never read as equal
ink. That is documented rather than quietly fixed; see below.taichi_palette_pair() builds a pair
that differs only in hue, sharing one luminance and one chroma
trajectory, so step for step the two fish carry the same visual
weight.palette = on
geom_taichi() selects a ready-made pair:
"balanced" (the recommended one), "diverging",
"viridis_pair", "brewer_pair",
"greyscale_safe" (a grey ramp and a hued ramp on the same
luminance trajectory, so the figure survives greyscale printing), or
"default". taichi_palette()
returns any of them for inspection.scale_taichi_yin_c() /
scale_taichi_yang_c(), scale_taichi_yin_d() /
scale_taichi_yang_d(),
scale_taichi_yin_binned() /
scale_taichi_yang_binned(), and
scale_taichi_yin_viridis_c() / _d() with their
yang counterparts. Pass them to yin_scale /
yang_scale, or use them directly with the fish geoms.shared_limits now composes with a supplied
scale. Previously the limits ggtaichi computed were dropped as
soon as anyone brought their own scale, so
shared_limits = TRUE silently did nothing next to a binned
or viridis scale. They are now pushed into it (a scale that sets its own
limits still wins), which is what makes binning both fish against one
set of breaks work. shared_legend likewise drops the
duplicate yang guide from a supplied scale.theme(geom = element_geom(ink, paper, accent)); ggtaichi’s
fallbacks were hard-coded, so on a dark theme the fallback fish was
nearly invisible and the two eyes were the wrong way round. Fill,
outline colour, linewidth, linetype and both eye colours now read from
the theme, resolving to exactly the previous values on any light theme.
The eye-colour arguments default to NULL, meaning “ask the
theme”. The ggplot2 floor stays at 3.4.0: the theme-aware defaults are
installed at load time when the installed ggplot2 supports them, and the
literal fallbacks are used otherwise. Raising the floor to 4.0.0 is a
1.0.0 decision.draw_key_taichi(): legend keys are now
small taichi symbols with the layer’s own fish filled, rather than plain
rectangles, and they grow the layer’s own eyes when it has them.
key_glyph = "rect" restores the old keys, and
key_glyph is a new argument of geom_taichi(),
geom_yin_fish() and geom_yang_fish(). Keys
only appear for discrete fills; a continuous fill still gets a
colourbar.inst/CITATION, so
citation("ggtaichi") gives a proper entry.ggplot_add() dispatch, the + chain, and the
theme-aware defaults resolving to the historical appearance. The suite
moves to testthat edition 3, adds expect_snapshot()
coverage of every print method and error message, and adds a vdiffr case
per new channel.geom_yin_fish() and geom_yang_fish()
additionally understand radius (a proportion of the cell’s
own radius, so 0.5 draws a half-size glyph in the same
cell), border (a per-cell outline width in mm, overriding
linewidth), and tooltip / data_id
/ onclick. Both gain interactive and
key_glyph arguments.
group column, as a "<id>"
suffix; the geom’s setup_data() reset group to
seq_len(nrow(data)) whenever it held duplicates, which
every transition produces, and threw that away, so
transition_states(), transition_manual() and
the rest all rendered one frame. The rewrite was dead code from the
package’s first commit (nothing in the draw path reads
group, since each panel is batched into one polygon that
numbers its own vertices), and removing it changes no static output
(every vdiffr snapshot is unchanged). It went unnoticed because
vignette("animations") builds the gganim
object but leaves every animate() call commented out for
CI, so the frames were never rendered. There is now a test that renders
frames with gganimate::file_renderer(), which needs no
gifski and no system libraries, and asserts the count.theme_void(), and any theme built with
rect = element_blank(), leaves the theme’s
paper fully transparent, so the new theme-aware default
painted the yin eye #00000000. A fully transparent
paper now falls back to white (and a transparent
ink to black), which also stops the fallback fill being
mixed towards transparency instead of towards the page.coord_cartesian(reverse = "y") and
coord_transform(y = "reverse") hand the cells over with
their maximum below their minimum, and the negative size rotated every
glyph by 180 degrees (yin eye at the bottom) and let the longer cell
side set the radius, so glyphs could overflow their cells.
(scale_y_reverse() was never affected.)theme_taichi(base_size =) left the main text at
its default size. The axis titles, tick labels and plot title
were fixed point sizes; they now scale with base_size. At
the default of 11 nothing changes.yin_name / yang_name were ignored
for a scale object passed as yin_scale /
yang_scale (a constructor always got them). They now title
it unless it names itself, and shared_legend gives it the
joint title.geom_yin_fish() / geom_yang_fish()
ignored a mistyped flag: eyes = "yes" quietly
meant no eyes. eyes and interactive must now
be TRUE or FALSE.remove_padding() turned a date axis into day
counts. A Date column counts as continuous, so the
axis was rebuilt with scale_x_continuous(), which threw the
date labels away and printed the raw numbers (18420) in
their place; every bundled data set carries such a column. A date,
date-time or hms axis now keeps
scale_x_date(), scale_x_datetime() or
scale_x_time(), with the padding removed. As a result
remove_padding(x = "c", y = "d") is resolved when it is
added to the plot, like the auto-detecting form, instead of returning
two scales straight away.geom_taichi() took over a fill scale already on
the plot. Added after another fill layer
(geom_tile(aes(fill = z)) + scale_fill_viridis_c()), its
yin scale replaced that layer’s scale, so the tiles were drawn in the
grey yin ramp and both were trained on one range, washing the fish out;
a plot-level aes(fill = ) did the same to the yin fish on
its own, and a second geom_taichi() repainted the first
one’s yang fish. It now starts a fresh fill scale first whenever the
plot already uses fill, and so does geom_taichi_diff(),
whose diverging scale laid over a taichi grid repainted the yang
fish.drop = FALSE crashed a factor fill with an
unused level (“Insufficient values in manual scale”): the
palette was sized from the levels present, not from the levels the scale
was asked to keep. With shared_limits, the unused levels
are now kept in the shared set as well.coord_polar() drew one oversized glyph in a
corner. It moves the cell centres but not the cell boxes the
glyph is drawn in, so the boxes stayed in data units.
coord_polar(), coord_radial() and
coord_map() are now an error naming the coords that work,
rather than a wrong picture.yin_name = bquote(mu * g) (or any call or symbol) was
evaluated by the do.call() that builds the scales instead
of being passed on, and print() of a
geom_taichi() or geom_taichi_diff() object
failed on an expression() title.yin_scale failed to build
a fill scale”.Found by auditing 0.3.0 against its own documentation before release. None of these has appeared in a CRAN release, so all are corrections rather than breaking changes.
data_id: hovering it showed nothing and highlighted
nothing (the eyes were unaffected). The attributes now go in one per
vertex, and a test reads them back out of girafe()’s SVG
rather than off the grob.order, and the tie was broken by something that is
not stable across R sessions: the same plot, the same package and the
same ggplot2 could put yin first in one render and yang first in the
next. One of the committed vdiffr references had in fact recorded the
wrong order, which is how it was found. Yin is now pinned before yang,
matching the argument order and every example in the documentation. An
explicit guide passed through ... still wins
(and is pinned the same way unless it asks for an order itself), and
shared_legend still drops the yang guide. A supplied
yin_scale / yang_scale is pinned the same way
unless it asks for an order itself: left alone, the two legends were
sorted by a hash of their contents, and the committed binned snapshot
had recorded yang first.vignette("animations") now really renders its
animations. Every animate() call in it was
commented out, because gifski is not installed on every check machine,
and since building a gganim object succeeds whether or not
the transition works, nothing ever noticed that the geom was collapsing
every animation to a single frame. The calls now execute through
gganimate::file_renderer(), which needs no gifski and no
system libraries, and emit a GIF on top of that wherever gifski exists.
A demonstration that is never run is not a demonstration.palette = "print_safe" is renamed
"greyscale_safe". The preset guarantees that the
two ramps collapse to the same ink in greyscale; it says
nothing about the CMYK gamut, which is what most readers understand by
“print safe”. The name over-promised. Renamed now because the preset is
new in this cycle and has never been released.explicit_channel = "radius" gains
radius_exponent, defaulting to 0.57. The radius
was scaled by sqrt(), strict area scaling, which was a
silent choice. Cartography’s answer for proportional symbols is the
apparent-magnitude (Flannery) exponent of about 0.57, because readers
systematically underestimate the area ratio between large and small
circles. radius_exponent = 0.5 restores the previous
behaviour.taichi_check_palette() now names the colour
space it measured in. Every number it prints is space-dependent
and none of them said so, which made them impossible to check against
another tool.taichi_summary() documents a caveat on
rank. The widest gap in a 96-cell grid is
frequently the largest noise; rank lists places to look,
not findings.geom_taichi() documents two things the mark
cannot do. A sequential discrete palette asserts an ordering,
which suits an ordered factor and overstates an unordered one; and
putting time on x encodes the series in fill rather than
position, so slope is not encoded at all.yin_scale /
yang_scale was modified in place. Scales are
shared by reference, so the shared limits and the dropped yang guide
followed the object into every other plot it was used in. They now go on
a copy.interactive = TRUE object in a
second plot broke the first: filling in the default tooltip
wrote into layers the two plots shared.shared_legend the yin
value was labelled with the joint legend title
(matcha / espresso: 35); each source is now named by its
own column. A category or cell label containing < or
& is now escaped like a column name, instead of
breaking the tooltip’s markup.scale_taichi_yin_d() /
scale_taichi_yang_d() treated an explicit
colors vector as a ramp, so c("red", "blue")
came out purple and blue; it is now used as given, as in
geom_taichi(). They also failed outright on ggplot2 3.4,
the supported floor, which still requires
discrete_scale()’s scale_name.explicit warned twice about a
non-positive ratio (once per fish), and now warns once. A
border missing from some cells keeps the layer’s
linewidth there instead of dropping to 0.1, a non-numeric
mapped border is an error, and the deprecated
size counts as linewidth when checking for a
clash with explicit_channel = "border". When every cell
agrees, the angle channel now sits at the middle of a
custom explicit_range, where the signed mapping puts
agreement.taichi_summary() failed (“factor level
is duplicated”) when a column was compared with itself or was called
tie, and gave a missing value in a constant column a
z of 0 rather than NA.geom_taichi_diff() took its symmetric
limits from the plot’s data even when the tiles had a data
of their own, and its "z" legend read
z( a ) - z( b ).palette = preset
was reported as a bad name, an argument the caller never
passed; geom_taichi_diff()’s palette error now names its
three-colour form; taichi_check_palette() checks
tolerance.explicit_channel = "radius" was still described as
square-root scaling; the claim that a non-positive ratio warns in all
three places is corrected (taichi_summary() does not); and
the animations vignette counted three releases where there were
two.explicit = "ratio" measured the gap from
0. A ratio’s agreement point is 1, and every ratio is positive,
so a cell where the two sources were equal got a mid-sized eye (or a
tilt, a thicker border, a larger glyph) where the documentation promises
none. Every channel now measures the distance from agreement, 1 for
"ratio" and 0 for the other statistics.explicit_range was likewise ignored there for the eyes and
the border. Every cell now gets the channel’s agreement end, as it would
next to a disagreeing one.shared_limits put a transformed scale object on
the wrong limits. A continuous scale keeps its limits in
transformed units, and the shared limits were written into a supplied
object in raw units, so on a transform = "log10" scale most
cells fell below the lower limit and were painted na.value.
A constructor was never affected.1.2e+04 and
123456 lost two digits as 1.235e+05. They now stay in fixed
notation (four significant digits) except at magnitudes where that would
be a run of zeros, and a plotmath legend title reaches the tooltip as
text instead of being evaluated.Error in pull(),
resolve_values(), check_colours(),
as_palette_pair()), and geom_taichi_diff()
blamed an explicit argument it does not have. A bad
yin_colors / yang_colors is named when
geom_taichi() is called instead of surfacing at print time
as an anonymous “Unknown colour name”. Infinite values are now refused
wherever a finite number is needed (n, hues,
chroma, luminance, which must also lie between
0 and 100, explicit_range, radius_exponent,
midpoint and a constant eye size):
chroma = Inf used to return a ramp of NA
colours without a word.palette = gave a discrete fill the palest steps
of its ramp. A preset was taken verbatim, like an explicit
colour vector, so a three-level factor got the first three of the
preset’s five colours: the light half of the ramp, the first level close
to invisible on a white panel. A palette is now sampled as the ramp it
is, palest end skipped, as the built-in colours are and as
scale_taichi_yin_d(palette = ) already did. Explicit
yin_colors / yang_colors are still used as
given.shared_legend keeps now fills
both halves of its keys. It governs both fish, and a key with
only the yin half filled read as a legend for the top fish alone. An
explicit key_glyph still wins.vignette("animations") recommended
enter_grow() for a grow-in reveal, which has no
visible effect on the glyphs. It now says what the enter and exit
effects do here: cells pop in and out, enter_drift() moves
them, and enter_fade() / enter_recolour() stop
with an error on any plot with a ggnewscale break (a gganimate
limitation that plain geom_tile() layers share).?geom_taichi says what the default hover ids do
under facets: they name a cell by its x and
y, so the same cell lights up in every panel.expect_doppelganger() announces nothing. Each visual test
now announces its reference before any skip, and the visual tests also
skip themselves on a ggplot2 older than the one the references were
drawn with, where every comparison would fail for reasons that have
nothing to do with ggtaichi.size argument of geom_taichi(),
soft-deprecated in favour of linewidth since 0.2.0, will be
removed in 1.0.0. It still works, and still warns.geom_taichi() now takes around thirty arguments, which
is a design smell the roadmap has flagged. Grouping them into option
objects (taichi_eyes(), taichi_scales(), …) is
intended to land before the next wave of glyph channels, not
after.stable.
The API has been through three releases without a breaking change: 0.3.0
added arguments but removed and altered nothing, and every plot written
against 0.1.0 or 0.2.0 still draws the same picture. stable
is a promise about breakage, not a promise to stop adding features, and
that promise the package can keep.eyes = TRUE): draw
the classic taichi dots, each centred in its own fish’s head (yin in the
top bulb, yang in the bottom bulb). yin_eye_size /
yang_eye_size and yin_eye_colour /
yang_eye_colour accept either a constant or an unquoted
data column, so a single glyph can now encode up to six
dimensions (x, y, two fills, two eyes) (#3b).angle): rotate each glyph by
a constant number of degrees or by a data column, encoding a directional
or temporal variable as orientation, and unlocking spin animations
(#3a).geom_taichi() inspects the plot data at + time
and auto-selects scale_fill_manual() for discrete (factor /
character / logical) yin / yang values,
including computed expressions such as factor(week), and
scale_fill_gradientn() for continuous ones. With the
default color vectors, discrete categories sample the ramp evenly while
skipping its palest end, so no category is invisible on a white panel.
Custom scales (objects or constructors) can be supplied via
yin_scale / yang_scale (#4a, BUG-4).shared_limits = TRUE gives both auto-built fill scales
common limits (the union range of the two sources, or the union of
levels when both are discrete), so equal values read as equal ink.
Explicit limits passed through ... still win,
and mixing a discrete with a continuous source warns and ignores the
flag.shared_legend = TRUE treats the sources as one measure:
it implies shared limits, paints both fish with yin_colors,
drops the duplicate yang guide, and titles the single legend
“yin / yang” unless yin_name is
supplied.geom_yin_fish() and geom_yang_fish() are now
documented exports (with the GeomYinFish /
GeomYangFish ggproto objects available for extension
packages), for users who want a single fish or full manual control over
scale stacking.remove_padding() auto mode: called
with no arguments it now detects each axis’s scale type from the plot it
is added to; the explicit "c" / "d" arguments
remain as overrides.cafes_tg: a small, clearly
synthetic (seeded) espresso vs. matcha dataset whose two columns share
units: an evergreen demo for the shared-scale features and a break from
the COVID-era examples. The generating script ships in
data-raw/.yin and yang also accept strings naming a
column (yin = "Twitter"), which previously produced a
meaningless constant fill.geom_taichi() now returns an object with a friendly
print() method instead of dumping raw list internals at the
console.vignette("animations") documents how
geom_taichi() composes with gganimate
(transition_states(), spin animations via
angle, export recipes), said to be verified frame-by-frame
against gganimate 1.0.11 (it was not; see 0.3.0). gganimate is a
Suggests-only dependency.makeContent(). Glyphs stay perfectly round under resize,
and large grids render an order of magnitude faster than the per-cell
grob building used in 0.1.0: a 1200-cell grid with eyes takes 0.24 s to
build and draw versus ~3.5 s with the per-cell approach (~15x, same
machine), with pixel-identical output.eye_size = 0 drew an eye (#1):
a mapped eye-size of 0 was rescaled to a positive radius,
so an eye was drawn despite the documented “0 → no eye” rule. Zeros are
now preserved and drawn without an eye.0 is excluded from the documented
(0, 0.5] pass-through range, a single “no eye here” zero
made every other value in an otherwise-proportional column go through
the [0.05, 0.3] rescale instead:
c(0, 0.2, 0.4) drew eyes of 0, 0.175, 0.3.
Zeros are markers rather than measurements, so they no longer take part
in that decision; the column now draws 0, 0.2, 0.4, and the
two documented rules compose as intended.shared_legend palette mismatch (#2):
with shared_legend = TRUE and discrete fills, the yang fish
was painted with a differently-interpolated palette than the yin fish
when only one of yin_colors / yang_colors was
supplied. Both fish now use yin_colors, so identical
categories read as identical ink.states_tg documentation (#3): the
dataset spans 31 weeks (the bundled data has 31); the documentation said
30. Corrected to 31 (the data is unchanged).... routing (BUG-1): geom parameters
(alpha, colour, linewidth,
linetype, width, height,
na.rm, show.legend) are now real, documented
arguments of geom_taichi() and are forwarded to the
underlying fish layers. ... is reserved for options applied
to both fill scales (e.g. shared limits); per-fish scale
control goes through yin_scale /
yang_scale.linewidth aesthetic (BUG-2): the
outline width now uses the modern linewidth aesthetic.
Passing size to geom_taichi() still works but
warns and is routed to linewidth, and an inherited
aes(size = ...) mapping is renamed through ggplot2’s
built-in deprecation path. ggtaichi now requires ggplot2 >=
3.4.0.yin or yang errors immediately with a clear
message instead of silently producing a degenerate grey plot, and a
yin / yang column that does not exist in the
plot data errors at + time with the offending name.angle and eye_size
values: the guards tested is.na(), which is
TRUE for NA and NaN but not for
Inf / -Inf. An infinite angle therefore
reached cos() / sin(), turned every vertex of
that glyph into NaN and drew nothing while warning “NaNs
produced”; an infinite mapped eye size asked grid for a circle of
infinite radius. Both now test is.finite(), so a non-finite
angle falls back to no rotation and a non-finite eye size means no eye,
exactly as NA already did.angle columns: mapping
angle to a character column failed at draw time with the
base error “non-numeric argument to binary operator”, and mapping it to
a factor silently drew unrotated glyphs alongside
'*' not meaningful for factors warnings. Both now error at
build time with a clear message, matching how a non-numeric
eye_size column is already handled.limits: passing
limits through ... for a factor / character
fish aborted with “Insufficient values in manual scale” whenever the
limits held more entries than the data had levels. The auto-built
palette is now sized against the limits.yin_scale = scale_colour_viridis_c attached the scale
to colour, which the fish never map, so the fish fell back
to ggplot2’s default blue fill gradient with no error at all.
yin_scale / yang_scale are now checked to
govern a fill aesthetic, and a value that is neither a scale object nor
a constructor function reports that instead of the base error “‘what’
must be a function or character string”.width /
height failed inside setup_data()
with “non-numeric argument to binary operator”; now reported
directly.... colliding with the scale options
geom_taichi() sets itself: guide
together with shared_legend = TRUE aborted with the base
error “formal argument guide matched by multiple actual
arguments”. The internally computed options now take precedence, so the
yang guide is still dropped while a user-supplied guide
styles the shared legend. Passing name,
values, colors, or colours
through ... now reports which per-fish argument to use
instead of raising the same base error.theme_taichi() no longer clips text at the plot
edges: the title is now aligned with the whole plot area
(plot.title.position = "plot") and slightly smaller (15
instead of 18), so realistic titles fit at typical figure sizes, and the
right plot margin is a touch wider so an axis label sitting on the panel
boundary (common with remove_padding()) is not cut
off.theme_taichi() element inheritance:
because the theme is composed with %+replace%, three
properties theme_bw() had set were silently dropped and
fell back to the generic text / rect parents.
The rice-paper canvas picked up a near-black 1px border around the whole
plot, the y-axis tick labels lost their right alignment and their gap
from the panel, and the legend title lost its left alignment. All three
are restored.yin_scale /
yang_scale), and NA visibility.?geom_taichi:
alpha, colour, linewidth and
linetype are layer-wide constants there (each has a
concrete default, so it is always forwarded as a layer parameter and
outranks an inherited mapping), so map those through
geom_yin_fish() / geom_yang_fish() instead.
width and height default to NULL
and are forwarded only when supplied, so a plot-level
aes(width = ...) does size the cells per row.?theme_taichi now spells out its two surprising choices
(the blanked y axis title, so labs(y = ) has no effect, and
the 90-degree legend text), together with the theme() calls
that put either back.?remove_padding now states that ...
reaches both position scales, so with axes of different types
only arguments common to continuous and discrete scales work there, and
that auto-detection reads the plot’s mapping (name the type explicitly
when x / y are mapped in a layer
instead).?pitts_emojis now documents the actual format (HTML
<img> tags aligned row-for-row with
pitts_tg) and notes that the remote images it points at are
no longer served.taichi_fish() geometry down to a Monte-Carlo tiling check,
parameter routing, rotation, eyes, discrete-scale selection, grob-level
rendering checks) plus vdiffr visual-regression
snapshots.width on x and
height on y, and the glyph radius coming from the shorter
cell side), which every coord_fixed() snapshot is blind to.
It also pins the direction of all three places rotation
is applied (taichi_fish(), the vectorised body rotation in
makeContent(), and the eye placement), so a sign error in
any one of them fails a test.tests/testthat/setup.R holds a null device open for the
run, so the suite no longer leaves a stray Rplots.pdf in
tests/testthat/.geom_taichi() now returns a ggtaichi_plot
object added to the plot via a ggplot_add() method, which
is what makes data-aware scale selection and shared limits
possible.geom_taichi() turns each cell of a grid into a taichi
(yin-yang) diagram, filling the two fish with values from two data
sources.theme_taichi() and remove_padding()
helpers.pitts_tg, states_tg, and
pitts_emojis data sets.