plotcli-py 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- CLAUDE.md +51 -0
- LICENSE +21 -0
- PKG-INFO +358 -0
- README.md +340 -0
- main.py +6 -0
- plotcli-original/.Rbuildignore +18 -0
- plotcli-original/.github/workflows/deploy_docs.yml +43 -0
- plotcli-original/.gitignore +46 -0
- plotcli-original/DESCRIPTION +25 -0
- plotcli-original/NAMESPACE +60 -0
- plotcli-original/NEWS.md +112 -0
- plotcli-original/R/ascii_escape.r +13 -0
- plotcli-original/R/canvas.r +586 -0
- plotcli-original/R/class_functions.r +114 -0
- plotcli-original/R/geom_registry.r +1376 -0
- plotcli-original/R/ggplotcli.r +234 -0
- plotcli-original/R/ggplotcli_helpers.r +1099 -0
- plotcli-original/R/helper_functions.r +351 -0
- plotcli-original/R/plotcli.r +963 -0
- plotcli-original/R/plotcli_grid.r +1 -0
- plotcli-original/R/plotcli_wrappers.r +416 -0
- plotcli-original/R/zzz.r +15 -0
- plotcli-original/README.md +192 -0
- plotcli-original/docs/ascii.png +0 -0
- plotcli-original/docs/bar.png +0 -0
- plotcli-original/docs/block.png +0 -0
- plotcli-original/docs/boxplot.png +0 -0
- plotcli-original/docs/density.png +0 -0
- plotcli-original/docs/facet.png +0 -0
- plotcli-original/docs/facet_grid.png +0 -0
- plotcli-original/docs/generate_png.sh +137 -0
- plotcli-original/docs/heatmap.png +0 -0
- plotcli-original/docs/histogram.png +0 -0
- plotcli-original/docs/line.png +0 -0
- plotcli-original/docs/noborder.png +0 -0
- plotcli-original/docs/scatter.png +0 -0
- plotcli-original/docs/showcase.R +182 -0
- plotcli-original/inst/doc/ggplotcli.R +231 -0
- plotcli-original/inst/doc/ggplotcli.Rmd +329 -0
- plotcli-original/inst/doc/ggplotcli.html +1078 -0
- plotcli-original/inst/doc/plotcli_class.R +98 -0
- plotcli-original/inst/doc/plotcli_class.Rmd +121 -0
- plotcli-original/inst/doc/plotcli_class.html +564 -0
- plotcli-original/inst/doc/plotcli_wrappers.R +35 -0
- plotcli-original/inst/doc/plotcli_wrappers.Rmd +62 -0
- plotcli-original/inst/doc/plotcli_wrappers.html +546 -0
- plotcli-original/man/AsciiCanvas.Rd +116 -0
- plotcli-original/man/BlockCanvas.Rd +132 -0
- plotcli-original/man/BrailleCanvas.Rd +146 -0
- plotcli-original/man/Canvas.Rd +492 -0
- plotcli-original/man/GeomRegistry.Rd +9 -0
- plotcli-original/man/add_legend_to_output.Rd +12 -0
- plotcli-original/man/braille_dot_bit.Rd +29 -0
- plotcli-original/man/braille_set_dot.Rd +21 -0
- plotcli-original/man/bresenham.Rd +27 -0
- plotcli-original/man/build_plot_output.Rd +28 -0
- plotcli-original/man/build_plot_output_v2.Rd +41 -0
- plotcli-original/man/cat_plot_matrix.Rd +17 -0
- plotcli-original/man/cbind.plotcli.Rd +19 -0
- plotcli-original/man/cbind_plots.Rd +17 -0
- plotcli-original/man/color_to_term.Rd +18 -0
- plotcli-original/man/create_canvas.Rd +21 -0
- plotcli-original/man/create_panel_scales.Rd +29 -0
- plotcli-original/man/create_scales.Rd +27 -0
- plotcli-original/man/dot-geom_registry.Rd +16 -0
- plotcli-original/man/draw_border.Rd +12 -0
- plotcli-original/man/draw_grid.Rd +12 -0
- plotcli-original/man/extract_legend_info.Rd +12 -0
- plotcli-original/man/extract_plot_labels.Rd +12 -0
- plotcli-original/man/extract_plot_style.Rd +12 -0
- plotcli-original/man/format_axis_label.Rd +18 -0
- plotcli-original/man/format_four_chars.Rd +21 -0
- plotcli-original/man/geom_area_handler.Rd +12 -0
- plotcli-original/man/geom_bar_handler.Rd +12 -0
- plotcli-original/man/geom_boxplot_handler.Rd +13 -0
- plotcli-original/man/geom_density_handler.Rd +12 -0
- plotcli-original/man/geom_histogram_handler.Rd +12 -0
- plotcli-original/man/geom_hline_handler.Rd +12 -0
- plotcli-original/man/geom_line_handler.Rd +12 -0
- plotcli-original/man/geom_path_handler.Rd +12 -0
- plotcli-original/man/geom_point_handler.Rd +12 -0
- plotcli-original/man/geom_rect_handler.Rd +12 -0
- plotcli-original/man/geom_segment_handler.Rd +12 -0
- plotcli-original/man/geom_smooth_handler.Rd +12 -0
- plotcli-original/man/geom_text_handler.Rd +12 -0
- plotcli-original/man/geom_vline_handler.Rd +12 -0
- plotcli-original/man/get_color_hue.Rd +18 -0
- plotcli-original/man/get_data_subset.Rd +23 -0
- plotcli-original/man/get_facet_info.Rd +18 -0
- plotcli-original/man/get_geom_handler.Rd +17 -0
- plotcli-original/man/get_term_colors.Rd +21 -0
- plotcli-original/man/ggplotcli.Rd +83 -0
- plotcli-original/man/init_color_mapping.Rd +15 -0
- plotcli-original/man/is_braille.Rd +20 -0
- plotcli-original/man/is_geom_registered.Rd +17 -0
- plotcli-original/man/list_registered_geoms.Rd +14 -0
- plotcli-original/man/make_colored.Rd +23 -0
- plotcli-original/man/make_unique_names.Rd +20 -0
- plotcli-original/man/normalize_data.Rd +27 -0
- plotcli-original/man/pclib.Rd +48 -0
- plotcli-original/man/pclibx.Rd +46 -0
- plotcli-original/man/pclid.Rd +44 -0
- plotcli-original/man/pclih.Rd +50 -0
- plotcli-original/man/pclil.Rd +48 -0
- plotcli-original/man/pclis.Rd +48 -0
- plotcli-original/man/pixel_to_braille.Rd +23 -0
- plotcli-original/man/plotcli.Rd +598 -0
- plotcli-original/man/plotcli_bar.Rd +48 -0
- plotcli-original/man/plotcli_box.Rd +46 -0
- plotcli-original/man/plotcli_density.Rd +44 -0
- plotcli-original/man/plotcli_histogram.Rd +50 -0
- plotcli-original/man/plotcli_line.Rd +48 -0
- plotcli-original/man/plotcli_options.Rd +18 -0
- plotcli-original/man/plotcli_scatter.Rd +48 -0
- plotcli-original/man/plus-.plotcli.Rd +19 -0
- plotcli-original/man/rbind.plotcli.Rd +19 -0
- plotcli-original/man/rbind_plots.Rd +17 -0
- plotcli-original/man/register_geom.Rd +21 -0
- plotcli-original/man/remove_color_codes.Rd +21 -0
- plotcli-original/man/render_faceted_plot.Rd +25 -0
- plotcli-original/man/render_single_panel.Rd +12 -0
- plotcli-original/man/safe_aes_name.Rd +18 -0
- plotcli-original/tests/testthat/test-new-geoms.R +136 -0
- plotcli-original/tests/testthat/test-plotcli.R +69 -0
- plotcli-original/tests/testthat.R +4 -0
- plotcli-original/vignettes/ggplotcli.Rmd +329 -0
- plotcli-original/vignettes/plotcli_class.R +98 -0
- plotcli-original/vignettes/plotcli_class.Rmd +121 -0
- plotcli-original/vignettes/plotcli_wrappers.R +35 -0
- plotcli-original/vignettes/plotcli_wrappers.Rmd +62 -0
- plotcli.egg-info/PKG-INFO +11 -0
- plotcli.egg-info/SOURCES.txt +7 -0
- plotcli.egg-info/dependency_links.txt +1 -0
- plotcli.egg-info/entry_points.txt +3 -0
- plotcli.egg-info/top_level.txt +1 -0
- plotcli.py +978 -0
- plotcli_py-0.1.0.dist-info/METADATA +358 -0
- plotcli_py-0.1.0.dist-info/RECORD +143 -0
- plotcli_py-0.1.0.dist-info/WHEEL +4 -0
- plotcli_py-0.1.0.dist-info/entry_points.txt +2 -0
- plotcli_py-0.1.0.dist-info/licenses/LICENSE +21 -0
- pyproject.toml +31 -0
- uv.lock +8 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
#' Canvas Classes for Terminal Plotting
|
|
2
|
+
#'
|
|
3
|
+
#' Abstract canvas system that provides different rendering backends:
|
|
4
|
+
#' - AsciiCanvas: Basic ASCII characters (*, -, |, +)
|
|
5
|
+
#' - BrailleCanvas: Unicode Braille patterns (2x4 dots per cell = 8x resolution)
|
|
6
|
+
#' - BlockCanvas: Unicode block elements (half-blocks for 2x vertical resolution)
|
|
7
|
+
#'
|
|
8
|
+
#' @name Canvas
|
|
9
|
+
NULL
|
|
10
|
+
|
|
11
|
+
#' Base Canvas Class
|
|
12
|
+
#'
|
|
13
|
+
#' Abstract base class for all canvas types. Provides the interface
|
|
14
|
+
#' that all canvas implementations must follow.
|
|
15
|
+
#'
|
|
16
|
+
#' @export
|
|
17
|
+
Canvas <- R6Class("Canvas",
|
|
18
|
+
public = list(
|
|
19
|
+
#' @field width Character width of the canvas
|
|
20
|
+
width = NULL,
|
|
21
|
+
#' @field height Character height of the canvas
|
|
22
|
+
height = NULL,
|
|
23
|
+
#' @field pixel_width Pixel width (may be higher than char width for Braille/Block)
|
|
24
|
+
pixel_width = NULL,
|
|
25
|
+
#' @field pixel_height Pixel height (may be higher than char height for Braille/Block)
|
|
26
|
+
pixel_height = NULL,
|
|
27
|
+
#' @field matrix Character matrix for rendering
|
|
28
|
+
matrix = NULL,
|
|
29
|
+
#' @field color_matrix Parallel matrix tracking colors per cell
|
|
30
|
+
color_matrix = NULL,
|
|
31
|
+
#' @field x_mult Horizontal multiplier (pixels per character)
|
|
32
|
+
x_mult = 1,
|
|
33
|
+
#' @field y_mult Vertical multiplier (pixels per character)
|
|
34
|
+
y_mult = 1,
|
|
35
|
+
|
|
36
|
+
#' @description Initialize the canvas
|
|
37
|
+
#' @param width Character width
|
|
38
|
+
#' @param height Character height
|
|
39
|
+
initialize = function(width, height) {
|
|
40
|
+
self$width <- width
|
|
41
|
+
self$height <- height
|
|
42
|
+
self$pixel_width <- width * self$x_mult
|
|
43
|
+
self$pixel_height <- height * self$y_mult
|
|
44
|
+
self$matrix <- matrix(" ", nrow = height, ncol = width)
|
|
45
|
+
self$color_matrix <- matrix(NA_character_, nrow = height, ncol = width)
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
#' @description Set a pixel at the given coordinates
|
|
49
|
+
|
|
50
|
+
#' @param x X coordinate (1-based, in pixel space)
|
|
51
|
+
#' @param y Y coordinate (1-based, in pixel space, 1 = top)
|
|
52
|
+
#' @param color Optional color name
|
|
53
|
+
set_pixel = function(x, y, color = NULL) {
|
|
54
|
+
stop("set_pixel must be implemented by subclass")
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
#' @description Draw a line between two points
|
|
58
|
+
#' @param x0 Start X coordinate
|
|
59
|
+
#' @param y0 Start Y coordinate
|
|
60
|
+
#' @param x1 End X coordinate
|
|
61
|
+
#' @param y1 End Y coordinate
|
|
62
|
+
#' @param color Optional color name
|
|
63
|
+
draw_line = function(x0, y0, x1, y1, color = NULL) {
|
|
64
|
+
# Use Bresenham's algorithm at pixel resolution
|
|
65
|
+
points <- bresenham(
|
|
66
|
+
round(x0), round(y0),
|
|
67
|
+
round(x1), round(y1)
|
|
68
|
+
)
|
|
69
|
+
for (pt in points) {
|
|
70
|
+
self$set_pixel(pt[1], pt[2], color)
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
#' @description Draw multiple connected line segments
|
|
75
|
+
#' @param xs Vector of X coordinates
|
|
76
|
+
#' @param ys Vector of Y coordinates
|
|
77
|
+
#' @param color Optional color name
|
|
78
|
+
draw_polyline = function(xs, ys, color = NULL) {
|
|
79
|
+
if (length(xs) != length(ys)) stop("xs and ys must have same length")
|
|
80
|
+
if (length(xs) < 2) return()
|
|
81
|
+
|
|
82
|
+
for (i in 1:(length(xs) - 1)) {
|
|
83
|
+
self$draw_line(xs[i], ys[i], xs[i + 1], ys[i + 1], color)
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
#' @description Draw points (scatter)
|
|
88
|
+
#' @param xs Vector of X coordinates
|
|
89
|
+
#' @param ys Vector of Y coordinates
|
|
90
|
+
#' @param color Optional color name
|
|
91
|
+
draw_points = function(xs, ys, color = NULL) {
|
|
92
|
+
if (length(xs) != length(ys)) stop("xs and ys must have same length")
|
|
93
|
+
|
|
94
|
+
for (i in seq_along(xs)) {
|
|
95
|
+
self$set_pixel(round(xs[i]), round(ys[i]), color)
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
#' @description Fill a rectangle
|
|
100
|
+
#' @param x0 Left X coordinate
|
|
101
|
+
#' @param y0 Top Y coordinate
|
|
102
|
+
#' @param x1 Right X coordinate
|
|
103
|
+
#' @param y1 Bottom Y coordinate
|
|
104
|
+
#' @param color Optional color name
|
|
105
|
+
fill_rect = function(x0, y0, x1, y1, color = NULL) {
|
|
106
|
+
x0 <- round(x0); x1 <- round(x1)
|
|
107
|
+
y0 <- round(y0); y1 <- round(y1)
|
|
108
|
+
|
|
109
|
+
for (x in x0:x1) {
|
|
110
|
+
for (y in y0:y1) {
|
|
111
|
+
self$set_pixel(x, y, color)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
#' @description Fill a vertical bar from bottom up to a height
|
|
117
|
+
#' @param x X coordinate (center of bar in pixel space)
|
|
118
|
+
#' @param height Height in pixels from bottom
|
|
119
|
+
#' @param bar_width Width of bar in pixels (default 2)
|
|
120
|
+
#' @param color Optional color name
|
|
121
|
+
fill_bar = function(x, height, bar_width = 2, color = NULL) {
|
|
122
|
+
x <- round(x)
|
|
123
|
+
height <- round(height)
|
|
124
|
+
half_width <- floor(bar_width / 2)
|
|
125
|
+
|
|
126
|
+
for (dx in (-half_width):(half_width - 1 + bar_width %% 2)) {
|
|
127
|
+
for (y in (self$pixel_height - height + 1):self$pixel_height) {
|
|
128
|
+
self$set_pixel(x + dx, y, color)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
#' @description Place text at a position
|
|
134
|
+
#' @param x X coordinate (character position)
|
|
135
|
+
#' @param y Y coordinate (character position)
|
|
136
|
+
#' @param text Text string to place
|
|
137
|
+
#' @param color Optional color name
|
|
138
|
+
draw_text = function(x, y, text, color = NULL) {
|
|
139
|
+
chars <- strsplit(text, "")[[1]]
|
|
140
|
+
for (i in seq_along(chars)) {
|
|
141
|
+
col <- x + i - 1
|
|
142
|
+
if (col >= 1 && col <= self$width && y >= 1 && y <= self$height) {
|
|
143
|
+
self$matrix[y, col] <- chars[i]
|
|
144
|
+
if (!is.null(color)) {
|
|
145
|
+
self$color_matrix[y, col] <- color
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
#' @description Apply colors to the matrix
|
|
152
|
+
#' @return Matrix with ANSI color codes applied
|
|
153
|
+
apply_colors = function() {
|
|
154
|
+
result <- self$matrix
|
|
155
|
+
for (i in 1:nrow(result)) {
|
|
156
|
+
for (j in 1:ncol(result)) {
|
|
157
|
+
if (!is.na(self$color_matrix[i, j])) {
|
|
158
|
+
result[i, j] <- make_colored(result[i, j], self$color_matrix[i, j])
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return(result)
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
#' @description Get the rendered matrix (with colors)
|
|
166
|
+
#' @return Character matrix
|
|
167
|
+
render = function() {
|
|
168
|
+
self$apply_colors()
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
#' @description Print the canvas to console
|
|
172
|
+
print = function() {
|
|
173
|
+
rendered <- self$render()
|
|
174
|
+
cat("\n")
|
|
175
|
+
for (i in 1:nrow(rendered)) {
|
|
176
|
+
cat(paste(rendered[i, ], collapse = ""), "\n")
|
|
177
|
+
}
|
|
178
|
+
invisible(self)
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
#' @description Clear the canvas
|
|
182
|
+
clear = function() {
|
|
183
|
+
self$matrix <- matrix(" ", nrow = self$height, ncol = self$width)
|
|
184
|
+
self$color_matrix <- matrix(NA_character_, nrow = self$height, ncol = self$width)
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
#' @description Draw a rectangle outline
|
|
188
|
+
#' @param x0 Left X coordinate (pixel space)
|
|
189
|
+
#' @param y0 Top Y coordinate (pixel space)
|
|
190
|
+
#' @param x1 Right X coordinate (pixel space)
|
|
191
|
+
#' @param y1 Bottom Y coordinate (pixel space)
|
|
192
|
+
#' @param color Optional color name
|
|
193
|
+
draw_rect = function(x0, y0, x1, y1, color = NULL) {
|
|
194
|
+
x0 <- round(x0); x1 <- round(x1)
|
|
195
|
+
y0 <- round(y0); y1 <- round(y1)
|
|
196
|
+
|
|
197
|
+
# Draw four sides
|
|
198
|
+
self$draw_line(x0, y0, x1, y0, color) # Top
|
|
199
|
+
self$draw_line(x0, y1, x1, y1, color) # Bottom
|
|
200
|
+
self$draw_line(x0, y0, x0, y1, color) # Left
|
|
201
|
+
self$draw_line(x1, y0, x1, y1, color) # Right
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
#' @description Fill an area between a polyline and the bottom
|
|
205
|
+
#' @param xs Vector of X coordinates
|
|
206
|
+
#' @param ys Vector of Y coordinates
|
|
207
|
+
#' @param color Optional color name
|
|
208
|
+
fill_area = function(xs, ys, color = NULL) {
|
|
209
|
+
if (length(xs) != length(ys)) stop("xs and ys must have same length")
|
|
210
|
+
if (length(xs) < 2) return()
|
|
211
|
+
|
|
212
|
+
# For each x, find the y value and fill from there to bottom
|
|
213
|
+
for (i in seq_along(xs)) {
|
|
214
|
+
x <- round(xs[i])
|
|
215
|
+
y <- round(ys[i])
|
|
216
|
+
|
|
217
|
+
if (x >= 1 && x <= self$pixel_width) {
|
|
218
|
+
# Fill from y to bottom (pixel_height)
|
|
219
|
+
for (py in y:self$pixel_height) {
|
|
220
|
+
self$set_pixel(x, py, color)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
#' @description Draw a segment (line with optional arrowhead)
|
|
227
|
+
#' @param x0 Start X coordinate
|
|
228
|
+
#' @param y0 Start Y coordinate
|
|
229
|
+
#' @param x1 End X coordinate
|
|
230
|
+
#' @param y1 End Y coordinate
|
|
231
|
+
#' @param arrow_end Add arrowhead at end (default FALSE)
|
|
232
|
+
#' @param color Optional color name
|
|
233
|
+
draw_segment = function(x0, y0, x1, y1, arrow_end = FALSE, color = NULL) {
|
|
234
|
+
# Draw the main line
|
|
235
|
+
self$draw_line(x0, y0, x1, y1, color)
|
|
236
|
+
|
|
237
|
+
# Add arrowhead if requested
|
|
238
|
+
if (arrow_end) {
|
|
239
|
+
# Calculate arrow direction
|
|
240
|
+
dx <- x1 - x0
|
|
241
|
+
dy <- y1 - y0
|
|
242
|
+
len <- sqrt(dx^2 + dy^2)
|
|
243
|
+
|
|
244
|
+
if (len > 0) {
|
|
245
|
+
# Normalize and scale for arrow size
|
|
246
|
+
arrow_len <- min(len * 0.2, 4) # Arrow is 20% of line or max 4 pixels
|
|
247
|
+
dx <- dx / len * arrow_len
|
|
248
|
+
dy <- dy / len * arrow_len
|
|
249
|
+
|
|
250
|
+
# Arrow wings at 30 degrees
|
|
251
|
+
wing_x1 <- x1 - dx + dy * 0.5
|
|
252
|
+
wing_y1 <- y1 - dy - dx * 0.5
|
|
253
|
+
wing_x2 <- x1 - dx - dy * 0.5
|
|
254
|
+
wing_y2 <- y1 - dy + dx * 0.5
|
|
255
|
+
|
|
256
|
+
self$draw_line(x1, y1, wing_x1, wing_y1, color)
|
|
257
|
+
self$draw_line(x1, y1, wing_x2, wing_y2, color)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
#' @description Draw a horizontal line
|
|
263
|
+
#' @param y Y coordinate
|
|
264
|
+
#' @param x0 Start X (default 1)
|
|
265
|
+
#' @param x1 End X (default pixel_width)
|
|
266
|
+
#' @param color Optional color name
|
|
267
|
+
draw_hline = function(y, x0 = 1, x1 = NULL, color = NULL) {
|
|
268
|
+
if (is.null(x1)) x1 <- self$pixel_width
|
|
269
|
+
self$draw_line(x0, y, x1, y, color)
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
#' @description Draw a vertical line
|
|
273
|
+
#' @param x X coordinate
|
|
274
|
+
#' @param y0 Start Y (default 1)
|
|
275
|
+
#' @param y1 End Y (default pixel_height)
|
|
276
|
+
#' @param color Optional color name
|
|
277
|
+
draw_vline = function(x, y0 = 1, y1 = NULL, color = NULL) {
|
|
278
|
+
if (is.null(y1)) y1 <- self$pixel_height
|
|
279
|
+
self$draw_line(x, y0, x, y1, color)
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
#' @description Draw a circle outline
|
|
283
|
+
#' @param cx Center X coordinate
|
|
284
|
+
#' @param cy Center Y coordinate
|
|
285
|
+
#' @param r Radius in pixels
|
|
286
|
+
#' @param color Optional color name
|
|
287
|
+
draw_circle = function(cx, cy, r, color = NULL) {
|
|
288
|
+
# Midpoint circle algorithm
|
|
289
|
+
x <- r
|
|
290
|
+
y <- 0
|
|
291
|
+
err <- 0
|
|
292
|
+
|
|
293
|
+
while (x >= y) {
|
|
294
|
+
self$set_pixel(cx + x, cy + y, color)
|
|
295
|
+
self$set_pixel(cx + y, cy + x, color)
|
|
296
|
+
self$set_pixel(cx - y, cy + x, color)
|
|
297
|
+
self$set_pixel(cx - x, cy + y, color)
|
|
298
|
+
self$set_pixel(cx - x, cy - y, color)
|
|
299
|
+
self$set_pixel(cx - y, cy - x, color)
|
|
300
|
+
self$set_pixel(cx + y, cy - x, color)
|
|
301
|
+
self$set_pixel(cx + x, cy - y, color)
|
|
302
|
+
|
|
303
|
+
y <- y + 1
|
|
304
|
+
err <- err + 1 + 2 * y
|
|
305
|
+
|
|
306
|
+
if (2 * (err - x) + 1 > 0) {
|
|
307
|
+
x <- x - 1
|
|
308
|
+
err <- err + 1 - 2 * x
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
#' @description Fill a circle
|
|
314
|
+
#' @param cx Center X coordinate
|
|
315
|
+
#' @param cy Center Y coordinate
|
|
316
|
+
#' @param r Radius in pixels
|
|
317
|
+
#' @param color Optional color name
|
|
318
|
+
fill_circle = function(cx, cy, r, color = NULL) {
|
|
319
|
+
for (y in (cy - r):(cy + r)) {
|
|
320
|
+
for (x in (cx - r):(cx + r)) {
|
|
321
|
+
if ((x - cx)^2 + (y - cy)^2 <= r^2) {
|
|
322
|
+
self$set_pixel(x, y, color)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
#' @description Draw a polygon outline
|
|
329
|
+
#' @param xs Vector of X coordinates
|
|
330
|
+
#' @param ys Vector of Y coordinates
|
|
331
|
+
#' @param closed Whether to close the polygon (default TRUE)
|
|
332
|
+
#' @param color Optional color name
|
|
333
|
+
draw_polygon = function(xs, ys, closed = TRUE, color = NULL) {
|
|
334
|
+
if (length(xs) != length(ys)) stop("xs and ys must have same length")
|
|
335
|
+
if (length(xs) < 2) return()
|
|
336
|
+
|
|
337
|
+
# Draw all edges
|
|
338
|
+
for (i in 1:(length(xs) - 1)) {
|
|
339
|
+
self$draw_line(xs[i], ys[i], xs[i + 1], ys[i + 1], color)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# Close the polygon if requested
|
|
343
|
+
if (closed && length(xs) >= 3) {
|
|
344
|
+
self$draw_line(xs[length(xs)], ys[length(ys)], xs[1], ys[1], color)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
#' ASCII Canvas Class
|
|
352
|
+
#'
|
|
353
|
+
#' Simple canvas using basic ASCII characters.
|
|
354
|
+
#' Resolution: 1x1 (one pixel per character)
|
|
355
|
+
#'
|
|
356
|
+
#' @export
|
|
357
|
+
AsciiCanvas <- R6Class("AsciiCanvas",
|
|
358
|
+
inherit = Canvas,
|
|
359
|
+
public = list(
|
|
360
|
+
#' @field point_char Character used for points
|
|
361
|
+
point_char = "*",
|
|
362
|
+
|
|
363
|
+
#' @description Initialize ASCII canvas
|
|
364
|
+
#' @param width Character width
|
|
365
|
+
#' @param height Character height
|
|
366
|
+
#' @param point_char Character to use for points (default "*")
|
|
367
|
+
initialize = function(width, height, point_char = "*") {
|
|
368
|
+
self$x_mult <- 1
|
|
369
|
+
self$y_mult <- 1
|
|
370
|
+
self$point_char <- point_char
|
|
371
|
+
super$initialize(width, height)
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
#' @description Set a pixel
|
|
375
|
+
#' @param x X coordinate (1-based)
|
|
376
|
+
#' @param y Y coordinate (1-based, 1 = top)
|
|
377
|
+
#' @param color Optional color name
|
|
378
|
+
set_pixel = function(x, y, color = NULL) {
|
|
379
|
+
x <- round(x)
|
|
380
|
+
y <- round(y)
|
|
381
|
+
|
|
382
|
+
if (x < 1 || x > self$width || y < 1 || y > self$height) return()
|
|
383
|
+
|
|
384
|
+
self$matrix[y, x] <- self$point_char
|
|
385
|
+
if (!is.null(color)) {
|
|
386
|
+
self$color_matrix[y, x] <- color
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
#' Braille Canvas Class
|
|
394
|
+
#'
|
|
395
|
+
#' High-resolution canvas using Unicode Braille patterns.
|
|
396
|
+
#' Resolution: 2x4 (2 horizontal, 4 vertical dots per character = 8x resolution)
|
|
397
|
+
#'
|
|
398
|
+
#' Braille dot layout (dot numbers and bit values):
|
|
399
|
+
#' \preformatted{
|
|
400
|
+
#' Col 0 Col 1 Bit values
|
|
401
|
+
#' Row 0: 1 4 0x01 0x08
|
|
402
|
+
#' Row 1: 2 5 0x02 0x10
|
|
403
|
+
#' Row 2: 3 6 0x04 0x20
|
|
404
|
+
#' Row 3: 7 8 0x40 0x80
|
|
405
|
+
#' }
|
|
406
|
+
#'
|
|
407
|
+
#' @export
|
|
408
|
+
BrailleCanvas <- R6Class("BrailleCanvas",
|
|
409
|
+
inherit = Canvas,
|
|
410
|
+
public = list(
|
|
411
|
+
#' @field braille_base Unicode code point for empty Braille character
|
|
412
|
+
braille_base = 0x2800L,
|
|
413
|
+
|
|
414
|
+
#' @description Initialize Braille canvas
|
|
415
|
+
#' @param width Character width
|
|
416
|
+
#' @param height Character height
|
|
417
|
+
initialize = function(width, height) {
|
|
418
|
+
self$x_mult <- 2
|
|
419
|
+
self$y_mult <- 4
|
|
420
|
+
super$initialize(width, height)
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
#' @description Get the bit value for a dot position
|
|
424
|
+
#' @param dot_row Row within Braille cell (0-3)
|
|
425
|
+
#' @param dot_col Column within Braille cell (0-1)
|
|
426
|
+
#' @return Bit value
|
|
427
|
+
get_dot_bit = function(dot_row, dot_col) {
|
|
428
|
+
if (dot_col == 0) {
|
|
429
|
+
if (dot_row < 3) {
|
|
430
|
+
return(bitwShiftL(1L, dot_row))
|
|
431
|
+
} else {
|
|
432
|
+
return(0x40L)
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
if (dot_row < 3) {
|
|
436
|
+
return(bitwShiftL(1L, dot_row + 3))
|
|
437
|
+
} else {
|
|
438
|
+
return(0x80L)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
#' @description Set a pixel in Braille space
|
|
444
|
+
#' @param x X coordinate (1-based, in pixel space: 1 to width*2)
|
|
445
|
+
#' @param y Y coordinate (1-based, in pixel space: 1 to height*4, 1 = top)
|
|
446
|
+
#' @param color Optional color name
|
|
447
|
+
set_pixel = function(x, y, color = NULL) {
|
|
448
|
+
x <- round(x)
|
|
449
|
+
y <- round(y)
|
|
450
|
+
|
|
451
|
+
# Clamp to valid range
|
|
452
|
+
if (x < 1) x <- 1
|
|
453
|
+
if (x > self$pixel_width) x <- self$pixel_width
|
|
454
|
+
if (y < 1) y <- 1
|
|
455
|
+
if (y > self$pixel_height) y <- self$pixel_height
|
|
456
|
+
|
|
457
|
+
# Convert pixel coords to cell coords
|
|
458
|
+
cell_col <- ((x - 1) %/% 2) + 1
|
|
459
|
+
cell_row <- ((y - 1) %/% 4) + 1
|
|
460
|
+
|
|
461
|
+
# Get dot position within cell
|
|
462
|
+
dot_col <- (x - 1) %% 2
|
|
463
|
+
dot_row <- (y - 1) %% 4
|
|
464
|
+
|
|
465
|
+
# Get current character
|
|
466
|
+
current_char <- self$matrix[cell_row, cell_col]
|
|
467
|
+
if (current_char == " " || !is_braille(current_char)) {
|
|
468
|
+
current_code <- self$braille_base
|
|
469
|
+
} else {
|
|
470
|
+
current_code <- utf8ToInt(current_char)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
# Set the dot bit
|
|
474
|
+
dot_bit <- self$get_dot_bit(dot_row, dot_col)
|
|
475
|
+
new_code <- bitwOr(current_code, dot_bit)
|
|
476
|
+
|
|
477
|
+
self$matrix[cell_row, cell_col] <- intToUtf8(new_code)
|
|
478
|
+
|
|
479
|
+
if (!is.null(color)) {
|
|
480
|
+
self$color_matrix[cell_row, cell_col] <- color
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
)
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
#' Block Canvas Class
|
|
488
|
+
#'
|
|
489
|
+
#' Canvas using Unicode block elements for 2x vertical resolution.
|
|
490
|
+
#' Uses half-block characters: upper half (U+2580), lower half (U+2584), full block (U+2588).
|
|
491
|
+
#'
|
|
492
|
+
#' Resolution: 1x2 (1 horizontal, 2 vertical pixels per character)
|
|
493
|
+
#'
|
|
494
|
+
#' @export
|
|
495
|
+
BlockCanvas <- R6Class("BlockCanvas",
|
|
496
|
+
inherit = Canvas,
|
|
497
|
+
public = list(
|
|
498
|
+
#' @field upper_block Upper half block character
|
|
499
|
+
upper_block = "\u2580", # ▀
|
|
500
|
+
#' @field lower_block Lower half block character
|
|
501
|
+
lower_block = "\u2584", # ▄
|
|
502
|
+
#' @field full_block Full block character
|
|
503
|
+
full_block = "\u2588", # █
|
|
504
|
+
#' @field pixel_state Matrix tracking which half-pixels are set (0=none, 1=upper, 2=lower, 3=both)
|
|
505
|
+
pixel_state = NULL,
|
|
506
|
+
|
|
507
|
+
#' @description Initialize Block canvas
|
|
508
|
+
#' @param width Character width
|
|
509
|
+
#' @param height Character height
|
|
510
|
+
initialize = function(width, height) {
|
|
511
|
+
self$x_mult <- 1
|
|
512
|
+
self$y_mult <- 2
|
|
513
|
+
super$initialize(width, height)
|
|
514
|
+
self$pixel_state <- matrix(0L, nrow = height, ncol = width)
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
#' @description Set a pixel in Block space
|
|
518
|
+
#' @param x X coordinate (1-based, in pixel space: 1 to width)
|
|
519
|
+
#' @param y Y coordinate (1-based, in pixel space: 1 to height*2, 1 = top)
|
|
520
|
+
#' @param color Optional color name
|
|
521
|
+
set_pixel = function(x, y, color = NULL) {
|
|
522
|
+
x <- round(x)
|
|
523
|
+
y <- round(y)
|
|
524
|
+
|
|
525
|
+
# Clamp to valid range
|
|
526
|
+
if (x < 1) x <- 1
|
|
527
|
+
if (x > self$pixel_width) x <- self$pixel_width
|
|
528
|
+
if (y < 1) y <- 1
|
|
529
|
+
if (y > self$pixel_height) y <- self$pixel_height
|
|
530
|
+
|
|
531
|
+
# Convert pixel coords to cell coords
|
|
532
|
+
cell_col <- x
|
|
533
|
+
cell_row <- ((y - 1) %/% 2) + 1
|
|
534
|
+
|
|
535
|
+
# Determine if upper or lower half
|
|
536
|
+
is_lower <- ((y - 1) %% 2) == 1
|
|
537
|
+
|
|
538
|
+
# Update pixel state
|
|
539
|
+
current_state <- self$pixel_state[cell_row, cell_col]
|
|
540
|
+
if (is_lower) {
|
|
541
|
+
new_state <- bitwOr(current_state, 2L) # Set lower bit
|
|
542
|
+
} else {
|
|
543
|
+
new_state <- bitwOr(current_state, 1L) # Set upper bit
|
|
544
|
+
}
|
|
545
|
+
self$pixel_state[cell_row, cell_col] <- new_state
|
|
546
|
+
|
|
547
|
+
# Update character based on state
|
|
548
|
+
char <- switch(as.character(new_state),
|
|
549
|
+
"0" = " ",
|
|
550
|
+
"1" = self$upper_block,
|
|
551
|
+
"2" = self$lower_block,
|
|
552
|
+
"3" = self$full_block,
|
|
553
|
+
" "
|
|
554
|
+
)
|
|
555
|
+
self$matrix[cell_row, cell_col] <- char
|
|
556
|
+
|
|
557
|
+
if (!is.null(color)) {
|
|
558
|
+
self$color_matrix[cell_row, cell_col] <- color
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
#' @description Clear the canvas
|
|
563
|
+
clear = function() {
|
|
564
|
+
super$clear()
|
|
565
|
+
self$pixel_state <- matrix(0L, nrow = self$height, ncol = self$width)
|
|
566
|
+
}
|
|
567
|
+
)
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
#' Create a canvas of the specified type
|
|
572
|
+
#'
|
|
573
|
+
#' @param width Character width
|
|
574
|
+
#' @param height Character height
|
|
575
|
+
#' @param type Canvas type: "ascii", "braille", or "block"
|
|
576
|
+
#' @return A Canvas object
|
|
577
|
+
#' @export
|
|
578
|
+
create_canvas <- function(width, height, type = "braille") {
|
|
579
|
+
switch(type,
|
|
580
|
+
"ascii" = AsciiCanvas$new(width, height),
|
|
581
|
+
"braille" = BrailleCanvas$new(width, height),
|
|
582
|
+
"block" = BlockCanvas$new(width, height),
|
|
583
|
+
stop("Unknown canvas type: ", type)
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#' Combine plot matrices horizontally
|
|
2
|
+
#'
|
|
3
|
+
#' This function combines multiple plot matrices horizontally, centering them vertically.
|
|
4
|
+
#'
|
|
5
|
+
#' @param ... A list of plot matrices to be combined.
|
|
6
|
+
#' @return A combined plot matrix.
|
|
7
|
+
#' @export
|
|
8
|
+
#'
|
|
9
|
+
cbind_plots = function(...) {
|
|
10
|
+
plots <- list(...)
|
|
11
|
+
if (any(sapply(plots, function(plot) !inherits(plot, "plotcli")))) {
|
|
12
|
+
stop("All input objects must be of class 'plotcli'")
|
|
13
|
+
}
|
|
14
|
+
max_rows <- max(sapply(plots, function(plot) nrow(plot$plot_matrix)))
|
|
15
|
+
|
|
16
|
+
# Initialize the combined plot matrix
|
|
17
|
+
combined_matrix <- matrix(" ", nrow = max_rows, ncol = 0)
|
|
18
|
+
|
|
19
|
+
for (i in seq_along(plots)) {
|
|
20
|
+
plot <- plots[[i]]
|
|
21
|
+
# Center the plot matrix vertically
|
|
22
|
+
row_offset <- floor((max_rows - nrow(plot$plot_matrix)) / 2)
|
|
23
|
+
centered_matrix <- matrix(" ", nrow = max_rows, ncol = ncol(plot$plot_matrix))
|
|
24
|
+
centered_matrix[(1 + row_offset):(nrow(plot$plot_matrix) + row_offset), ] <- plot$plot_matrix
|
|
25
|
+
|
|
26
|
+
# Remove the last endline in the first plot_matrix
|
|
27
|
+
if (i == 1) {
|
|
28
|
+
centered_matrix <- centered_matrix[, -ncol(centered_matrix)]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Combine the centered plot matrix with the combined matrix
|
|
32
|
+
combined_matrix <- cbind(combined_matrix, centered_matrix)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return(combined_matrix)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#' Combine plot matrices vertically
|
|
39
|
+
#'
|
|
40
|
+
#' This function combines multiple plot matrices vertically, centering them horizontally.
|
|
41
|
+
#'
|
|
42
|
+
#' @param ... A list of plot matrices to be combined.
|
|
43
|
+
#' @return A combined plot matrix.
|
|
44
|
+
#' @export
|
|
45
|
+
#'
|
|
46
|
+
rbind_plots = function(...) {
|
|
47
|
+
plots <- list(...)
|
|
48
|
+
if (any(sapply(plots, function(plot) !inherits(plot, "plotcli")))) {
|
|
49
|
+
stop("All input objects must be of class 'plotcli'")
|
|
50
|
+
}
|
|
51
|
+
max_cols <- max(sapply(plots, function(plot) ncol(plot$plot_matrix)))
|
|
52
|
+
|
|
53
|
+
# Initialize the combined plot matrix
|
|
54
|
+
combined_matrix <- matrix(" ", nrow = 0, ncol = max_cols)
|
|
55
|
+
|
|
56
|
+
for (plot in plots) {
|
|
57
|
+
# Center the plot matrix horizontally
|
|
58
|
+
col_offset <- floor((max_cols - ncol(plot$plot_matrix)) / 2)
|
|
59
|
+
centered_matrix <- matrix(" ", nrow = nrow(plot$plot_matrix), ncol = max_cols)
|
|
60
|
+
centered_matrix[, (1 + col_offset):(ncol(plot$plot_matrix) + col_offset)] <- plot$plot_matrix
|
|
61
|
+
|
|
62
|
+
# Combine the centered plot matrix with the combined matrix
|
|
63
|
+
combined_matrix <- rbind(combined_matrix, centered_matrix)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return(combined_matrix)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
#' Overload the "+" operator for plotcli objects
|
|
71
|
+
#'
|
|
72
|
+
#' This function overloads the "+" operator to merge two plotcli objects.
|
|
73
|
+
#'
|
|
74
|
+
#' @param plot1 A plotcli object to be merged.
|
|
75
|
+
#' @param plot2 A plotcli object to be merged.
|
|
76
|
+
#' @return A new plotcli object containing the combined data from both objects.
|
|
77
|
+
#' @export
|
|
78
|
+
`+.plotcli` <- function(plot1, plot2) {
|
|
79
|
+
return(plot1$merge(plot2))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#' Generic function for combining plotcli objects horizontally
|
|
83
|
+
#'
|
|
84
|
+
#' @param ... plotcli objects to be combined.
|
|
85
|
+
#' @param deparse.level The deparsing level for the arguments.
|
|
86
|
+
#' @return A combined plot matrix.
|
|
87
|
+
#' @export
|
|
88
|
+
cbind.plotcli <- function(..., deparse.level = 1) {
|
|
89
|
+
plots <- list(...)
|
|
90
|
+
if (length(plots) != 2) {
|
|
91
|
+
stop("cbind.plotcli only supports combining exactly two plotcli objects.")
|
|
92
|
+
}
|
|
93
|
+
plot1 <- plots[[1]]
|
|
94
|
+
plot2 <- plots[[2]]
|
|
95
|
+
combined_matrix <- cbind_plots(plot1, plot2)
|
|
96
|
+
return(combined_matrix)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#' Generic function for combining plotcli objects vertically
|
|
100
|
+
#'
|
|
101
|
+
#' @param ... plotcli objects to be combined.
|
|
102
|
+
#' @param deparse.level The deparsing level for the arguments.
|
|
103
|
+
#' @return A combined plot matrix.
|
|
104
|
+
#' @export
|
|
105
|
+
rbind.plotcli <- function(..., deparse.level = 1) {
|
|
106
|
+
plots <- list(...)
|
|
107
|
+
if (length(plots) != 2) {
|
|
108
|
+
stop("rbind.plotcli only supports combining exactly two plotcli objects.")
|
|
109
|
+
}
|
|
110
|
+
plot1 <- plots[[1]]
|
|
111
|
+
plot2 <- plots[[2]]
|
|
112
|
+
combined_matrix <- rbind_plots(plot1, plot2)
|
|
113
|
+
return(combined_matrix)
|
|
114
|
+
}
|