Overview
Concentric puzzles feature rings of pieces that radiate outward from a central shape. They combine the elegance of hexagonal geometry with a distinctive radial pattern, creating puzzles that are both beautiful and satisfying to assemble.
Ring Variations
make_conc_rings <- function (r) {
ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (ring)),
rings = r,
seed = 42
) +
scale_fill_viridis_c (option = "plasma" , guide = "none" ) +
coord_fixed () +
theme_puzzle () +
labs (title = paste0 (r, " rings" ))
}
print (make_conc_rings (2 ) + make_conc_rings (3 ) + make_conc_rings (4 ))
# Generate ring previews in a grid
rings <- c (2 , 3 , 4 )
items <- lapply (rings, function (r) {
generate_puzzle (type = "concentric" , grid = c (r), size = c (200 ), seed = 42 , fill_palette = "plasma" )
})
render_puzzle_grid (items, ncol = 3 , labels = paste (rings, "rings" ))
Center Shape Options
The center piece can be either a hexagon or a circle:
# Hexagon center (default)
print (ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (ring)),
rings = 3 ,
center_shape = "hexagon" ,
seed = 42
) +
scale_fill_viridis_c (option = "magma" , guide = "none" ) +
coord_fixed () +
theme_puzzle () +
labs (title = "Hexagon center" ))
# Circle center
print (ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (ring)),
rings = 3 ,
center_shape = "circle" ,
seed = 42
) +
scale_fill_viridis_c (option = "magma" , guide = "none" ) +
coord_fixed () +
theme_puzzle () +
labs (title = "Circle center" ))
# Hexagon center (default)
result_hex <- generate_puzzle (
type = "concentric" ,
grid = c (3 ),
size = c (200 ),
seed = 42 ,
center_shape = "hexagon" ,
fill_palette = "magma"
)
render_puzzle_preview (result_hex)
# Circle center
result_circle <- generate_puzzle (
type = "concentric" ,
grid = c (3 ),
size = c (200 ),
seed = 42 ,
center_shape = "circle" ,
fill_palette = "magma"
)
render_puzzle_preview (result_circle)
Fill by Ring vs Piece ID
# By ring
print (ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (ring)),
rings = 3 , seed = 42
) +
scale_fill_viridis_c (option = "viridis" , name = "Ring" ) +
coord_fixed () +
theme_puzzle () +
labs (title = "By ring" ) +
theme (legend.position = "bottom" ))
# By piece_id
print (ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (piece_id)),
rings = 3 , seed = 42
) +
scale_fill_viridis_c (option = "viridis" , name = "Piece" ) +
coord_fixed () +
theme_puzzle () +
labs (title = "By piece_id" ) +
theme (legend.position = "bottom" ))
Color Palettes
make_conc_palette <- function (pal) {
ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (ring)),
rings = 3 , seed = 42
) +
scale_fill_viridis_c (option = pal, guide = "none" ) +
coord_fixed () +
theme_puzzle () +
labs (title = pal)
}
print ((make_conc_palette ("viridis" ) + make_conc_palette ("magma" ) + make_conc_palette ("plasma" )) /
(make_conc_palette ("inferno" ) + make_conc_palette ("cividis" ) + make_conc_palette ("turbo" )))
# Generate all palette previews and combine into a grid
palettes <- c ("viridis" , "magma" , "plasma" , "inferno" , "cividis" , "turbo" )
items <- lapply (palettes, function (pal) {
generate_puzzle (type = "concentric" , grid = c (3 ), size = c (200 ), seed = 42 , fill_palette = pal)
})
render_puzzle_grid (items, ncol = 3 , labels = palettes)
Fill Direction
Control the spatial order of color assignment. For concentric puzzles, "reverse" reverses colors within each ring while keeping the center piece unchanged. This is distinct from palette_invert which reverses the dark↔︎light ends of the palette.
make_direction_plot <- function (dir) {
ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (fill_order)),
rings = 3 ,
fill_direction = dir,
seed = 42
) +
scale_fill_viridis_c (option = "viridis" , guide = "none" ) +
coord_fixed () +
theme_puzzle () +
labs (title = paste0 ("fill_direction = \" " , dir, " \" " ))
}
print (make_direction_plot ("forward" ) + make_direction_plot ("reverse" ))
directions <- c ("forward" , "reverse" )
items <- lapply (directions, function (dir) {
generate_puzzle (type = "concentric" , grid = c (3 ), size = c (200 ), seed = 42 , fill_palette = "viridis" , fill_direction = dir)
})
render_puzzle_grid (items, ncol = 2 , labels = paste0 ("fill_direction = \" " , directions, " \" " ))
Offset (Separation)
make_conc_offset <- function (off) {
ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (ring)),
rings = 3 ,
offset = off, seed = 42
) +
scale_fill_viridis_c (option = "cividis" , guide = "none" ) +
coord_fixed () +
theme_puzzle () +
labs (title = paste0 ("offset = " , off))
}
print (make_conc_offset (0 ) + make_conc_offset (5 ) + make_conc_offset (10 ) + make_conc_offset (15 ))
# Generate offset previews in a grid
offsets <- c (0 , 5 , 10 , 15 )
items <- lapply (offsets, function (off) {
generate_puzzle (type = "concentric" , grid = c (3 ), size = c (200 ), seed = 42 , offset = off, fill_palette = "cividis" )
})
render_puzzle_grid (items, ncol = 4 , labels = paste ("offset =" , offsets))
Fusion Groups
Merge pieces together using PILES notation:
# Center merged with ring 1
print (ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (ring)),
rings = 3 ,
fusion_groups = "center-ring1" ,
seed = 42
) +
scale_fill_viridis_c (option = "plasma" , guide = "none" ) +
coord_fixed () +
theme_puzzle () +
labs (title = "center + ring1" ))
# Ring 2 pieces merged
print (ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (ring)),
rings = 3 ,
fusion_groups = "ring2" ,
seed = 42
) +
scale_fill_viridis_c (option = "plasma" , guide = "none" ) +
coord_fixed () +
theme_puzzle () +
labs (title = "ring2" ))
# Center merged with ring 1
result_center_ring1 <- generate_puzzle (
type = "concentric" ,
grid = c (3 ),
size = c (200 ),
seed = 42 ,
fusion_groups = "center-ring1" ,
fill_palette = "plasma"
)
render_puzzle_preview (result_center_ring1)
# Ring 2 pieces merged
result_ring2 <- generate_puzzle (
type = "concentric" ,
grid = c (3 ),
size = c (200 ),
seed = 42 ,
fusion_groups = "ring2" ,
fill_palette = "plasma"
)
render_puzzle_preview (result_ring2)
Large Concentric Puzzle
ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (ring)),
rings = 5 ,
center_shape = "hexagon" ,
seed = 456
) +
scale_fill_viridis_c (
option = "turbo" ,
name = "Ring"
) +
coord_fixed () +
theme_puzzle () +
theme (
legend.position = "bottom" ,
plot.title = element_text (hjust = 0.5 , size = 16 )
) +
labs (title = "5-Ring Concentric Puzzle" )
result <- generate_puzzle (
type = "concentric" ,
grid = c (5 ),
size = c (300 ),
seed = 456 ,
center_shape = "hexagon" ,
fill_palette = "turbo"
)
render_puzzle_preview (result, max_width = "500px" )
Parameters Reference
rings
integer
3
Number of concentric rings
center_shape
string
“hexagon”
Center shape: “hexagon” or “circle”
seed
integer
random
Random seed for reproducibility
offset
numeric
0 (mm)
Piece separation distance
tabsize
numeric
6 (%)
Tab size as percentage of edge length
jitter
numeric
2 (%)
Randomness in tab shape as percentage
min_tab_size
numeric
0 (mm)
Minimum tab size in millimeters
max_tab_size
numeric
Inf (mm)
Maximum tab size in millimeters
fusion_groups
string
NULL
PILES notation for fusing pieces
Fill Variables
When using ggplot2 with concentric puzzles, you can map these variables:
piece_id
Unique piece identifier
ring
Which ring the piece belongs to (0 = center)
is_center
TRUE if the piece is the center piece
Code Example
# Visualize with ggplot2
ggplot () +
geom_puzzle_conc (
aes (fill = after_stat (ring)),
rings = 4 ,
diameter = 280 ,
center_shape = "hexagon" ,
seed = 42 ,
offset = 5
) +
scale_fill_viridis_c (option = "plasma" , name = "Ring" ) +
coord_fixed () +
theme_puzzle () +
labs (title = "4-Ring Concentric Puzzle" )
# Generate puzzle
result <- generate_puzzle (
type = "concentric" ,
grid = c (4 ), # 4 rings
size = c (280 ), # 280mm diameter
seed = 42 ,
center_shape = "hexagon" ,
offset = 5
)
# writeLines(result$svg_content, "concentric_puzzle.svg")
# Preview the puzzle
render_puzzle_preview (result)