Nothing
# PURPOSE: ggmca_3d() -- the plotly 3D (or 2D) view of an MCA cloud.
# ROLE: Uses ggmca(get_data = TRUE) purely as a data getter, then draws with plotly instead of
# ggplot2. Nothing in the 2D path depends on this file.
# KEY CONSTRAINTS:
# - plotly is a Suggests: the entry point is guarded with requireNamespace().
# - profiles = TRUE is forced -- a 3D cloud with no individuals in it has nothing to show.
# - It reads the plot model's rows by `role` (R/plot-model.R), never by a colour group's name.
# See: CLAUDE.md section Repository Map.
#' Interactive 3D Plot for Multiple Correspondence Analyses (plotly::)
#'
#' @param res.mca An object created with \code{\link{multiple_correspondence_analysis}} or
#' \code{FactoMineR::\link[FactoMineR]{MCA}}.
#' @param data The data frame the analysis was made on, in which to find the clusters.
#' @param clust The variable of `data` holding the clusters, typically made with
#' \code{\link{hierarchical_clust}}, as a bare name or a string: the answer profiles of one
#' cluster are coloured alike and linked at mouse hover.
#' @param cah Deprecated former name of `clust`.
#' @param axes The axes to print, as a numeric vector of length 3.
#' @param dat Deprecated former name of `data`. Still accepted, with a warning;
#' use `data` instead.
#' @param base_zoom The base level of zoom.
#' @param remove_buttons Set to TRUE to remove buttons to change view.
#' @param cone_size The size of the conic arrow at the end of each axe.
#' @param view The starting point of view (in 3D) :
#' \itemize{
#' \item \code{"Plane 1-2"} : Axes 1 and 2.
#' \item \code{"Plane 1-3"} : Axes 1 and 3.
#' \item \code{"Plane 2-3"} : Axes 2 and 3.
#' \item \code{"All" } : A 3D perspective with Axes 1, 2, 3.
#' }
#' @param camera_view Possibility to add a (replace `view`)
#' @param aspectratio_from_eig Set to `TRUE` to modify axes length based on
#' eigenvalues.
#' @param title The title of the graph.
#' @param ind_name.size The size of the names of individuals.
#' @param max_point_size The size of the biggest point.
#' @param ... Additional arguments to pass to \code{\link[ggfacto:ggmca]{ggmca}}.
#'
#' @return A \code{\link[plotly]{plotly}} html interactive 3d (or 2d) graph.
#' @export
#'
#' @examplesIf requireNamespace("plotly", quietly = TRUE)
#' \donttest{
#' data(tea, package = "FactoMineR")
#' res.mca <- multiple_correspondence_analysis(tea, 1:18)
#' ggmca_3d(res.mca)
#'
#' # 3D graph with colored clusters
#' tea <- tea |>
#' dplyr::mutate(clust = hierarchical_clust(res.mca, ncp = 3, nb_clust = 6))
#' ggmca_3d(res.mca, tea, clust = clust)
#' }
ggmca_3d <- function(res.mca, data, clust, axes = 1:3, # color_groups,
base_zoom = 1, remove_buttons = FALSE, cone_size = 0.15,
view = "All",
camera_view, aspectratio_from_eig = FALSE, title,
ind_name.size = 10, max_point_size = 30, # ind.size = 4,
...,
dat, cah) {
if (!requireNamespace("plotly", quietly = TRUE)) stop(
"ggmca_3d() needs the plotly package: install.packages(\"plotly\").", call. = FALSE)
# Renamed in 0.4.0; after `...` they can only be supplied by name.
if (!missing(dat) && missing(data)) data <- renamed_arg(dat, "dat", "data", "ggmca_3d")
clust <- if (!missing(cah)) {
rlang::quo(!!renamed_arg(cah, "cah", "clust", "ggmca_3d"))
} else {
rlang::enquo(clust)
}
clust_name <- if (rlang::quo_is_missing(clust) || rlang::quo_is_null(clust)) {
character()
} else {
need_data(missing(data), "the clusters", "ggmca_3d")
resolve_clust(clust, data)$name
}
D2 <- length(axes) == 2 ; stopifnot(length(axes) %in% 2:3 )
if (D2) axes <- c(axes, NA)
dim1 <- rlang::sym(str_c("Dim ", axes[1]))
dim2 <- rlang::sym(str_c("Dim ", axes[2]))
# if (missing(color_groups)) color_groups <- "^.{1}"
# The 3D view hovers on the profiles alone: no level crosstab to build, unless asked for.
acm <- if ("active_tables" %in% ...names()) {
ggmca(res.mca, data = data, clust = !!clust, profiles = TRUE, get_data = TRUE, ...)
} else {
ggmca(res.mca, data = data, clust = !!clust, profiles = TRUE, get_data = TRUE,
active_tables = NULL, ...)
}
acm_clust <- acm$vars_data |>
dplyr::filter(.data$role == "clust")
# a plain plotting tibble: no fmt column, so the tab class bought nothing
acm_vars <- acm$vars_data |>
dplyr::filter(!.data$vars %in% clust_name) |>
dplyr::mutate(face = dplyr::if_else(.data$role != "active", "italic", "bold") )
acm_profiles <- acm$profiles_coord
if(length(clust_name) > 0) {
acm_profiles <- acm_profiles |> dplyr::filter(!is.na(.data$clust))
clust_name_with_pct <- acm_clust |>
dplyr::select("lvs", "wcount") |>
dplyr::mutate(pct = round(.data$wcount/sum(.data$wcount)*100), 0) |>
dplyr::mutate(recode_vect = purrr::set_names(as.character(.data$lvs),
paste0(.data$lvs, " (",
.data$pct, "%)"))) |>
dplyr::pull("recode_vect")
acm_clust <- acm_clust |>
dplyr::mutate(lvs = forcats::fct_recode(.data$lvs, !!!clust_name_with_pct),
lvs = paste0("<b>", .data$lvs, "</b>"))
}
# height_width_ratio <- (0.8 + 1.2) / (0.8 + 1.1)
plot_range <-
dplyr::bind_rows(dplyr::select(acm_profiles, tidyselect::starts_with("Dim ")),
dplyr::select(acm_clust, tidyselect::starts_with("Dim ")),
dplyr::select(acm_vars, tidyselect::starts_with("Dim ")),
#dplyr::select(base_axis_in_princ, tidyselect::starts_with("Dim."))
) |>
purrr::map(~ range(.) |> abs() |> max())
plot_range <- plot_range |> purrr::map(~ c(-., .))
princ_axes <-
plot_range |> purrr::map(~ scales::breaks_extended(n = 4)(.)) |>
purrr::imap_dfr(~ tibble::tibble(name = .y, !!rlang::sym(.y) := .x, base_coord = .x)) |>
dplyr::mutate(dplyr::across(tidyselect::starts_with("Dim "), ~ tidyr::replace_na(., 0)),
name = forcats::as_factor(.data$name), #name = paste0(name, ".", base_coord)
pair_id = as.integer(.data$name),
) |>
dplyr::mutate(name = str_replace(.data$name, "Dim ", "Axe ") ) |>
dplyr::select("name", "pair_id", "base_coord", tidyselect::starts_with("Dim ") )
plot_range <-
purrr::map2(plot_range,
princ_axes |>
dplyr::group_by(.data$name) |>
dplyr::group_split() |>
purrr::map(~.$base_coord |> range()),
~ range(c(.x, .y))
)
# # Base ggplot 2D
# ggplot(acm_vars, ggplot2::aes(x = !!dim1, y = !!dim2)) +
# acm$graph_theme_acm +
# ggplot2::geom_point(
# data = acm_profiles, ggplot2::aes(size = wcount, color = color_group),
# na.rm = TRUE, show.legend = FALSE, stroke = 0, alpha = 0.5
# ) +
# ggplot2::geom_point(
# data = acm$mean_point_data,
# color = "black", fill = "#eeeeee", shape = 3, size = 5, stroke = 1.5, na.rm = TRUE
# ) +
# ggrepel::geom_text_repel(
# ggplot2::aes(label = lvs, fontface = face),
# size = text_size, na.rm = TRUE, direction = "both", min.segment.length = 0.01,
# force = 0.5, force_pull = 1, point.padding = 0, box.padding = 0,
# point.size = NA, arrow = ggplot2::arrow(length = ggplot2::unit(0.25, "lines"))
# ) +
# # ggplot2::geom_segment(
# # data = acm_clust |>
# # dplyr::mutate(!!dim1 = pmin(1.3, pmax(!!dim1, -0.9)),
# # !!dim2 = pmin(1.3, pmax(!!dim2, -0.85)),
# # start1 = pmin(0.95, pmax(!!dim1, -0.5)),
# # start2 = pmin(1.25, pmax(!!dim2, -0.775)),
# # ),
# # ggplot2::aes(x = start1, xend = !!dim1, y = start2, yend = !!dim2,
# # color = color_group),
# # arrow = ggplot2::arrow(length = ggplot2::unit(0.3, "lines")), na.rm = TRUE
# # ) +
# ggrepel::geom_label_repel(
# data = acm_clust,
# ggplot2::aes(label = lvs, color = color_group), fill = grDevices::rgb(1, 1, 1, alpha = 0.7),
# direction = "y", force = 0.5, force_pull = 1, point.padding = 0, point.size = NA,
# arrow = ggplot2::arrow(length = ggplot2::unit(0.25, "lines")),
# fontface = "bold", size = text_size, na.rm = TRUE
# )
if (length(clust_name) > 0) {
acm_lv <- acm_clust$color_group |> forcats::fct_drop() |> levels()
acm_lv <- purrr::set_names(acm_lv,
material_colors_light()[1:length(acm_lv)],
)
acm_clust <- acm_clust |>
dplyr::mutate(
color_group = forcats::fct_recode(.data$color_group, !!!acm_lv) |>
forcats::fct_drop()
)
acm_profiles <- acm_profiles |>
dplyr::mutate(
color_group = forcats::fct_recode(.data$color_group, !!!acm_lv) |>
forcats::fct_drop()
)
} else {
acm_profiles <- acm_profiles |> dplyr::mutate(color_group = factor("#bbbbbb"))
}
acm_profiles <- acm_profiles |>
dplyr::mutate(
size_scaled = scales::abs_area(max = max_point_size)(.data$wcount),
size_scaled =
.data$size_scaled/max(.data$size_scaled, na.rm = TRUE)*max_point_size,
)
# acm_profiles |>
# dplyr::select(wcount, size_scaled ) |>
# dplyr::slice(4500:5000) |>
# print(n = 900)
# Axes
axes_common_infos <- list(
showspikes = FALSE, # projections lines
showgrid = FALSE,
zeroline = FALSE,
showticklabels = FALSE #,
# backgroundcolor="rgb(200, 200, 230",
# gridcolor="rgb(255,255,255)",
# zerolinecolor="rgb(255,255,255"
# ticketmode = 'array',
# ticktext = c("Huey", "Dewey", "Louie"),
# tickvals = c(0,25,50),
# range = c(-25,75)
# nticks = 4,
)
axes_params <- purrr::map(plot_range,
~ c(list(range = ., title = ""), axes_common_infos)
)
## Assemble plot ----
dim1 <- rlang::sym(str_c("Dim ", axes[1]))
dim2 <- rlang::sym(str_c("Dim ", axes[2]))
dim3 <- if (D2) {NULL} else {rlang::sym(str_c("Dim ", axes[3]))}
# To get a fixed aspect ratio, put a point in max range * aspectratio on all axes
if (aspectratio_from_eig) {
vs <- mca_model(res.mca)$vs
aspectratio <- list(x = vs[axes[1]],
y = vs[axes[2]],
z = if (D2) {NULL} else {vs[axes[3]]}
)
} else {
aspectratio <- list(x = 1, y = 1, z =if (D2) {NULL} else {1})
}
aspectratio_range <- tibble::as_tibble(plot_range) |>
## dplyr::mutate(Dim.2 = Dim.2 * 2) |> # test
#dplyr::mutate(dplyr::across(tidyselect::everything(), ~pmax(!!!rlang::syms(names(plot_range))))) |>
dplyr::mutate(dplyr::across(axes[1], ~ . * aspectratio[[1]]),
dplyr::across(axes[2], ~ . * aspectratio[[2]]),
dplyr::across(if (D2) {NULL} else {axes[3]}, ~ . * aspectratio[[3]]),
)
# se calcule ensuite, pour chaque axe, par rapport a son propre range ?
# camera_title <- names(camera_view)
if (!missing(camera_view)) {
camera_view <- camera_view |>
purrr::set_names(paste0("scene", 1:length(camera_view)) |>
str_replace("scene1", "scene") )
scene_name <- names(camera_view)
} else {
scene_name <- "scene"
}
# i <- 1
dual_plots <- vector("list", length(scene_name))
for (i in 1:length(scene_name)) {
dual_plots[[i]] <- plotly::plot_ly(scene = if (!D2) scene_name[i])
# Individus colores selon CAH
dual_plots[[i]] <- dual_plots[[i]] |>
plotly::add_trace(
data = acm_profiles, scene = if (!D2) scene_name[i],
x = ~eval(dim1), y = ~eval(dim2), z = ~eval(dim3), # color = df$color_col
text = ~interactive_text,
#textfont = list(color = "#00600f", size = ind_name.size), # "#0077c2"
marker = list(color = ~color_group, size = ~size_scaled), # "#0077c2"
# hovertemplate = paste(
# "<b>%{text}</b><br>", # <br>
# "%{yaxis.title.text}: %{y:$,.0f}<br>",
# "%{xaxis.title.text}: %{x:.0%}<br>",
# #"Number Employed: %{marker.size:,}",
# "<extra></extra>"
# ),
hoverinfo = "text",
hoverlabel = list(align = "right"),
# text = ~paste("Price: ", price, '$<br>Cut:', cut),
type = if (D2) {"scatter"} else {"scatter3d"}, # type = "scatter3d",
mode = "markers", showlegend = FALSE, inherit = FALSE)
# variables actives
dual_plots[[i]] <- dual_plots[[i]] |>
plotly::add_trace(
data = acm_vars, scene = if (!D2) scene_name[i],
x = ~eval(dim1), y = ~eval(dim2), z = ~eval(dim3), # color = df$color_col
text = ~lvs,
textfont = list(color = "black", size = ind_name.size), # "#0077c2"
hoverinfo = "skip",
# text = ~paste("Price: ", price, '$<br>Cut:', cut),
type = if (D2) {"scatter"} else {"scatter3d"}, # type = "scatter3d",
mode = "text", showlegend = FALSE, inherit = FALSE)
# labels clust
if (length(clust_name) > 0) {
dual_plots[[i]] <- dual_plots[[i]] |>
plotly::add_trace(
data = acm_clust, scene = if (!D2) scene_name[i],
x = ~eval(dim1), y = ~eval(dim2), z = ~eval(dim3), # color = df$color_col
text = ~lvs,
textfont = list(color = ~color_group, size = ind_name.size), # "#0077c2"
hoverinfo = "skip",
# text = ~paste("Price: ", price, '$<br>Cut:', cut),
type = if (D2) {"scatter"} else {"scatter3d"}, # type = "scatter3d",
mode = "text", showlegend = FALSE, inherit = FALSE)
}
# Axes principaux de l'ACP
dual_plots[[i]] <- dual_plots[[i]] |>
plotly::add_trace(
data = princ_axes |>
dplyr::filter(.data$name %in% paste0("Axe ", axes) ) |>
dplyr::group_by(.data$name) |>
dplyr::slice(-dplyr::n()) |> dplyr::ungroup(),
# dplyr::mutate(remove_last_if_not_1 = dplyr::row_number() == dplyr::n() & base_coord != 1) |>
# dplyr::filter(!remove_last_if_not_1) |> dplyr::ungroup(),
scene = if (!D2) scene_name[i],
x = ~eval(dim1), y = ~eval(dim2), z = ~eval(dim3),
marker = list(color = "black",
symbol = "cross",
size = 5), # 3
text = ~base_coord, textfont = list(color = "black", size = 10),
textposition = "bottom center", hoverinfo = "skip",
type = if (D2) {"scatter"} else {"scatter3d"}, # type = "scatter3d",
mode = 'markers+text', showlegend = FALSE, inherit = FALSE) |>
plotly::add_trace(
data = princ_axes |>
dplyr::filter(.data$name %in% paste0("Axe ", axes) ) |>
dplyr::group_by(.data$name) |> dplyr::slice(1, dplyr::n()) |>
dplyr::mutate(
name = dplyr::if_else(
dplyr::row_number() == 1,
true = "",
false = paste0("<b>", .data$name, "</b>")
)
) |>
dplyr::ungroup(),
scene = if (!D2) scene_name[i],
x = ~eval(dim1), y = ~eval(dim2), z = ~eval(dim3), split = ~ pair_id,
line = list(color = "black", width = 5),
text = ~name, textfont = list(color = "black", size = 15),
#textposition = "top center",
hoverinfo = "skip",
type = if (D2) {"scatter"} else {"scatter3d"}, # type = "scatter3d",
mode = 'lines+text', showlegend = FALSE, inherit = FALSE)
if (!D2) {
dual_plots[[i]] <- dual_plots[[i]] |>
plotly::add_trace( # cone au bout des axes
data = princ_axes |>
dplyr::filter(.data$name %in% paste0("Axe ", axes) ) |>
dplyr::group_by(.data$name) |>
dplyr::slice(dplyr::n()) |>
dplyr::ungroup(),
scene = if (!D2) scene_name[i],
x = ~eval(dim1), y = ~eval(dim2), z = ~eval(dim3), split = ~ name,
u = ~eval(dim1)*9/10, v = ~eval(dim2)*9/10, w = ~eval(dim3)*9/10,
sizeref = cone_size, sizemode = "absolute",
colorscale = list(list(0, "black"), list(1, "black")), #autocolorscale = FALSE,
showscale = FALSE, hoverinfo = "skip",
# lighting = list(ambient = 1), lightposition= list(x=0, y=0, z=1e5),
type = "cone", anchor = "center", #dplyr::if_else(max(princ_axes_print) == 1, "tip", "center"),
showlegend = FALSE, inherit = FALSE
)
}
# To get a fixed aspect ratio, put a point in max ranges on all axes
if (!D2) { # Also in 2D ?
dual_plots[[i]] <- dual_plots[[i]] |>
plotly::add_trace(
data = aspectratio_range, scene = if (!D2) scene_name[i],
x = ~eval(dim1), y = ~eval(dim2), z = ~eval(dim3), # color = df$color_col
hoverinfo = "skip", opacity = 0, visible = TRUE,
type = if (D2) {"scatter"} else {"scatter3d"}, # type = "scatter3d",
mode = "text", showlegend = FALSE, inherit = FALSE
)
}
# # Plan Axe 1/Axe 2 et projections des points
# if ("projections" %in% type) {
# dual_plots[[i]] <- dual_plots[[i]] |>
# plotly::add_trace(data = dplyr::bind_rows(ind_coords, dplyr::mutate(ind_coords, Dim.3 = 0)),
# scene = scene_name[i],
# x = ~eval(dim1), y = ~eval(dim2), z = ~eval(dim3), split = ~ name,
# line = list(color = "#9575cd"), # dash = "longdash", width = 4 #( "dash" | "dashdot" | "dot" | "longdash" | "longdashdot" | "solid" )
# type = if (D2) {"scatter"} else {"scatter3d"}, # type = "scatter3d",
# mode = "lines", showlegend = FALSE, inherit = FALSE,
# hoverinfo = "skip") |>
# plotly::add_trace(data = dplyr::mutate(ind_coords, Dim.3 = 0), scene = scene_name[i],
# x = ~eval(dim1), y = ~eval(dim2), z = ~eval(dim3),
# marker = list(color = "#9575cd", size = 2), # "#65499c"
# type = if (D2) {"scatter"} else {"scatter3d"}, # type = "scatter3d",
# mode = "markers", showlegend = FALSE, inherit = FALSE,
# hoverinfo = "skip")
# }
#
#
# if ("main_plan" %in% type) {
# dual_plots[[i]] <- dual_plots[[i]] |>
# plotly::add_trace(data = planDf, scene = scene_name[i],
# x = ~eval(dim1), y = ~eval(dim2), z = ~eval(dim3),
# opacity = 0.5, #color = "A", colorscale = c("A" = "#65499c"), #vertexcolor = "#65499c",
# facecolor = rep('#CFC0E8', nrow(planDf)), # "#65499c"
# hoverinfo = "skip",
# type = "mesh3d", showlegend = FALSE, inherit = FALSE)
# }
}
if (!D2) {
# Buttons to set plans
plan12 <- paste0("Plane ", axes[1], "-", axes[2]) # 1-2
plan13 <- paste0("Plane ", axes[1], "-", axes[3]) # 1-3
plan23 <- paste0("Plane ", axes[2], "-", axes[3]) # 2-3
plan123 <- "All"
if (!remove_buttons) {
updatemenus <- list(
list(
active = -1,
# switch(view,
# plan12 = 0,
# "Plane 1-3" = 1,
# "Plane 2-3" = 2,
# "All" = 3,
# stop("'view' argument is not recognized")) , # -1,
type = 'buttons', # uirevision = FALSE, # showactive = FALSE, # visible = TRUE,
buttons = list(
list(
label = plan12,
method = "relayout",
args = list(list(scene = list(
xaxis = axes_params[[axes[1]]],
yaxis = axes_params[[axes[2]]],
zaxis = axes_params[[axes[3]]],
aspectratio = aspectratio, aspectmode = "data",
camera = list(
center = list(x = 0 , y = 0 , z = 0 ),
eye = list(x = 0 , y = 0 , z = base_zoom),
up = list(x = 0 , y = 1 , z = 0 ),
projection = "orthographic"
)
)
)) #,
# args2 = list(list(scene = list(xaxis = axx, yaxis = axy, zaxis = axz,
# aspectratio = = aspectratio, aspectmode = "data",
# camera = list(
# center = list(x = 0 , y = 0 , z = 0 ),
# eye = list(x = 0 , y = 0 , z = base_zoom),
# up = list(x = 0 , y = 1 , z = 0 )
# )
# )
# ))
),
#list(list(shapes = list(cluster0, c(), c())))),
list(
label = plan13,
method = "relayout",
args = list(list(scene = list(
xaxis = axes_params[[axes[1]]],
yaxis = axes_params[[axes[2]]],
zaxis = axes_params[[axes[3]]],
aspectratio = aspectratio, aspectmode = "data",
camera = list(
center = list(x = 0 , y = 0 , z = 0 ),
eye = list(x = 0 , y = -base_zoom , z = 0 ),
up = list(x = 0 , y = 0 , z = 1 ),
projection = "orthographic"
)
)
)
) #,
# args2 = list(list(scene = list(xaxis = axx, yaxis = axy, zaxis = axz,
# aspectratio = = aspectratio, aspectmode = "data",
# camera = list(
# center = list(x = 0 , y = 0 , z = 0 ),
# eye = list(x = 0 , y = -base_zoom , z = 0 ),
# up = list(x = 0 , y = 0 , z = 1 )
# )
# )
# )
#)
),
#list(list(shapes = list(c(), cluster1, c())))),
list(
label = plan23,
method = "relayout",
args = list(list(scene = list(
xaxis = axes_params[[axes[1]]],
yaxis = axes_params[[axes[2]]],
zaxis = axes_params[[axes[3]]],
aspectratio = aspectratio, aspectmode = "data",
camera = list(
center = list(x = 0 , y = 0 , z = 0 ),
eye = list(x = base_zoom , y = 0 , z = 0 ),
up = list(x = 0 , y = 0 , z = 1 ),
projection = "orthographic"
)
)
)
) #,
# args2 = list(list(scene = list(xaxis = axx, yaxis = axy, zaxis = axz,
# aspectratio = = aspectratio, aspectmode = "data",
# camera = list(
# center = list(x = 0 , y = 0 , z = 0 ),
# eye = list(x = base_zoom , y = 0 , z = 0 ),
# up = list(x = 0 , y = 0 , z = 1 )
# )
# )
# )
# )
),
list(
label = plan123,
method = "relayout",
args = list(list(scene = list(
xaxis = axes_params[[axes[1]]],
yaxis = axes_params[[axes[2]]],
zaxis = axes_params[[axes[3]]],
aspectratio = aspectratio, aspectmode = "data",
camera = list(
center = list(x = 0 , y = 0 , z = 0 ),
eye = list(x = base_zoom/6, y = -base_zoom, z = base_zoom),
# list(x = 0.6 * base_zoom, y = -0.4 * base_zoom, z = 0.7 * base_zoom),
up = list(x = 0 , y = 0 , z = 1 ),
projection = "orthographic"
)
)
)) #,
# args2 = list(list(scene = list(xaxis = axx, yaxis = axy, zaxis = axz,
# aspectratio = = aspectratio, aspectmode = "data",
# camera = list(
# center = list(x = 0 , y = 0 , z = 0 ),
# eye = list(x = base_zoom/6, y = -base_zoom, z = base_zoom),
# # list(x = 0.6 * base_zoom, y = -0.4 * base_zoom, z = 0.7 * base_zoom),
# up = list(x = 0 , y = 0 , z = 1 )
# )
# )
# ))
) # ,
#list(list(shapes = c()))),
)
)
)
} else {
updatemenus <- NULL
}
} else { # 2D
updatemenus <- NULL
}
if (!D2 & !missing(camera_view)) {
scenes <- purrr::map(camera_view,
~ list(
xaxis = axes_params[[axes[1]]],
yaxis = axes_params[[axes[2]]],
zaxis = axes_params[[axes[3]]],
aspectratio = aspectratio, aspectmode = "data",
#aspectratio = list(x = 1, y = 1, z = 1),
#domain = list(x = c(0, 0.5), y = c(0, 1)),
camera = .x
)
)
} else if (!D2) {
scenes <- list("scene" = first_case(
view == plan12 ~ list(
xaxis = axes_params[[axes[1]]],
yaxis = axes_params[[axes[2]]],
zaxis = axes_params[[axes[3]]],
aspectratio = aspectratio, aspectmode = "data",
camera = list(
center = list(x = 0 , y = 0 , z = 0 ),
eye = list(x = 0 , y = 0 , z = base_zoom),
up = list(x = 0 , y = 1 , z = 0 ),
projection = "orthographic"
)
),
view == plan13 ~ list(
xaxis = axes_params[[axes[1]]],
yaxis = axes_params[[axes[2]]],
zaxis = axes_params[[axes[3]]],
aspectratio = aspectratio, aspectmode = "data",
camera = list(
center = list(x = 0 , y = 0 , z = 0 ),
eye = list(x = 0 , y = - base_zoom , z = 0),
up = list(x = 0 , y = 0 , z = 1 ),
projection = "orthographic"
)
),
view == plan23 ~ list(
xaxis = axes_params[[axes[1]]],
yaxis = axes_params[[axes[2]]],
zaxis = axes_params[[axes[3]]],
aspectratio = aspectratio, aspectmode = "data",
camera = list(
center = list(x = 0 , y = 0 , z = 0 ),
eye = list(x = base_zoom, y = 0 , z = 0),
up = list(x = 0 , y = 0 , z = 1 ),
projection = "orthographic"
)
),
view == plan123 ~ list(
xaxis = axes_params[[axes[1]]],
yaxis = axes_params[[axes[2]]],
zaxis = axes_params[[axes[3]]],
aspectratio = aspectratio, aspectmode = "data",
camera = list(
center = list(x = 0 , y = 0 , z = 0 ),
eye = list(x = base_zoom/6, y = -base_zoom, z = base_zoom),
# list(x = 0.6 * base_zoom, y = -0.4 * base_zoom, z = 0.7 * base_zoom),
up = list(x = 0 , y = 0 , z = 1 ),
projection = "orthographic"
)
),
TRUE ~ list(a = NULL)
)
)
#print(scenes)
if (is.null(scenes$scene[[1]])) stop(paste0("view argument must be among: ",
paste0(
paste0("'", c(plan12, plan13, plan23, plan123), "'"),
collapse = ", "),
collapse = ""
))
} else { # 2D
scenes <- list("scene" = list(
xaxis = axes_params[[axes[1]]],
yaxis = axes_params[[axes[2]]],
aspectratio = aspectratio, aspectmode = "data" #,
))
}
final_plots <-
plotly::subplot(purrr::list_flatten(dual_plots), margin = 0.1, #0,
nrows = ceiling(length(scene_name)/2L)
)
final_plots <- do.call(plotly::layout,
c(list(p = final_plots,
margin = list(b = 0, l = 0, r = 0, t = 0),
updatemenus = updatemenus
),
if (missing(title)) {NULL} else {list(title = title)},
scenes
)
)
#print(aspectratio)
# final_plots$data$ind_coords <- ind_coords
# final_plots$data$base_axis_in_princ <- base_axis_in_princ
# final_plots$data$princ_axes <- princ_axes
# final_plots$data$mean_projs <- mean_projs
# final_plots$data$planDf <- planDf
# No aspect ratio: a 3D scene has no 2D one to preserve, so a knitted iframe falls back on the
# chunk's fig.width/fig.height.
as_ggfacto_widget(final_plots)
# plotly::layout(#title = "Title",
# scene = list(title = ,
# xaxis = axx, yaxis = axy, zaxis = axz,
# #domain = list(x = c(0, 0.5), y = c(0, 1)),
# camera = list(
# center = list(x = 0 , y = 0 , z = 0 ),
# eye = list(x = 0 , y = 0 , z = 1),
# up = list(x = 0 , y = 1 , z = 0 )
# )
# )#,
# ) |>
# plotly::layout(#title = "Title",
# scene2 = list(title = ,
# xaxis = axx, yaxis = axy, zaxis = axz,
# #domain = list(x = c(0.5, 1), y = c(0, 1)),
# camera = list(
# center = list(x = 0 , y = 0 , z = 0 ),
# eye = list(x = 0 , y = -1 , z = 0 ),
# up = list(x = 0 , y = 0 , z = 1 )
# )
# )
# )
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.