---
title: "Retinal Development: Point-Cloud and Graph Views"
output:
  rmarkdown::html_vignette:
vignette: >
  %\VignetteIndexEntry{Retinal Development: Point-Cloud and Graph Views}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", out.width = "100%")
library(ivue)
```

This case study shows the same 12,000 mouse retinal-development cells in two
three-dimensional representations. The first is a published UMAP point cloud.
The second is a symmetric k-nearest-neighbor graph with an independently
computed graph layout. Both embeddings are fitted on 120,804 cells before
extracting the same 12,000 cells for display. UMAP uses Canberra distance;
the symmetric-kNN graph uses Euclidean distance. Holding cell identities, annotations, colors, and initial camera
settings fixed makes the representational change visible without implying that
the two geometries are equivalent.

`ivue` renders both views. It does not compute UMAP coordinates, construct the
graph, or optimize the graph layout.

The README also shows a PHATE embedding fitted to the same full-population
20-PC matrix, using Euclidean distance and 2,000 spectral landmarks before
extracting these display cells. This vignette develops the two views included
in the bundled data; PHATE coordinates and Python dependencies are not bundled.

For a task index, see [Finding your way around ivue](function-guide.html);
for small reproducible inputs, see [Example data and recipes](example-data.html).

## Load the prepared case study

The installed package includes a visualization-ready object. It contains two
coordinate matrices, a weighted edge table, categorical annotations, and
provenance. It contains no expression matrix, principal-component matrix,
barcodes, sample identifiers, or local source paths.

```{r load-data}
path <- system.file("extdata", "retinal-development.rds", package = "ivue")
if (!nzchar(path)) {
  candidates <- c(
    file.path("inst", "extdata", "retinal-development.rds"),
    file.path("..", "inst", "extdata", "retinal-development.rds")
  )
  path <- candidates[file.exists(candidates)][1]
}
retina <- readRDS(path)
dim(retina$coordinates$umap)
dim(retina$coordinates$sknn)
nrow(retina$graph$edges)
```

Rows have synthetic identifiers that agree across both coordinate matrices,
the graph, and the annotation table.

```{r identity-check}
ids <- retina$annotations$id
stopifnot(
  identical(rownames(retina$coordinates$umap), ids),
  identical(rownames(retina$coordinates$sknn), ids),
  identical(retina$graph$vertices, ids)
)
table(retina$annotations$age)
```

## Data lineage

The source is the retained mouse retinal-development single-cell RNA-seq data
from [Clark et al. (2019)](https://pmc.ncbi.nlm.nih.gov/articles/PMC6768831/),
available as [GEO GSE118614](https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE118614).
The published analysis fitted 20 principal components to `log10(CPT + 1)`
values for 3,290 high-variance genes in 120,804 cells, then retained 107,052
retinal cells. Its three-dimensional UMAP used Canberra distance.

The case-study sample contains 12,000 retained cells selected reproducibly
across nonempty developmental-stage-by-cell-type strata, with a minimum of 20
cells per stratum and seed 20190619. Both views use those exact identities in
the same order.


### Source and use terms

The published coordinates and annotations are publicly available, but the
source review found no separate dataset license. The upstream
[use agreement](https://github.com/gofflab/developing_mouse_retina_scRNASeq/blob/3bfeea29ecc957d59e4a156229449b060ffda0a8/README.md#use-agreement)
states a prepublication consent restriction tied to January 1, 2019.
Public availability and the elapsed date do not establish public-domain status
or a new permission grant. The package's GPL code license does not establish
rights in third-party values. See the installed `extdata/README.md` and
`retina$provenance$source.terms` for the documented review and limitations.

## Shared categorical scales

The bundle records display transformations in
`retina$provenance$display.transforms`. Both coordinate sets are centered on
the 12,000 selected cells. UMAP retains its coordinate scale; the graph layout
is divided by its maximum centered radius, giving a displayed radius of one.
The original graph center and radius were not retained in the historical cache
and are explicitly recorded as unavailable (`NA`). The UMAP center is recorded,
and future graph preparation runs retain both parameters.

Graph weights and fitted diagnostics are not rescaled. Do not compare displayed
segment lengths directly with distance weights. Figure-specific rigid rotations
are applied later during rendering and are not stored in either coordinate matrix.

Named palettes make annotation colors independent of factor order and reusable
across coordinate systems.

```{r scales}
age.colors <- c(
  E11 = "#512A84", E12 = "#4148A4", E14 = "#2E68B4",
  E16 = "#1686B7", E18 = "#009FA8", P0 = "#28B58B",
  P2 = "#67C36B", P5 = "#A4C84F", P8 = "#D2B943", P14 = "#E2873C"
)
cell.colors <- c(
  "Early RPCs" = "#3B6C8E", "Late RPCs" = "#7A5195",
  "Neurogenic Cells" = "#D45087", "Retinal Ganglion Cells" = "#E45756",
  "Amacrine Cells" = "#F58518", "Horizontal Cells" = "#9C755F",
  "Photoreceptor Precursors" = "#54A24B", Cones = "#72B7B2",
  Rods = "#4C78A8", "Bipolar Cells" = "#B279A2",
  "Muller Glia" = "#8F9D44"
)
age.scale <- color.scale.groups(retina$annotations$age, colors = age.colors)
cell.scale <- color.scale.groups(
  retina$annotations$cell.type, colors = cell.colors
)
camera <- camera.zup(elevation = 18, turn = -28, fov = 0, zoom = 0.57)
```

## Published UMAP as a point cloud

The UMAP coordinates are taken from the published metadata and centered only
for display. No UMAP fitting or graph construction occurs in this vignette.
`plot3D.groups()` associates annotation values with coordinate rows.

```{r umap-code, eval=FALSE}
umap.by.age <- plot3D.groups(
  retina$coordinates$umap,
  groups = retina$annotations$age,
  scale = age.scale,
  point.size = 2.2,
  alpha = 0.78,
  axes = FALSE,
  aspect = "equal",
  camera = camera
)
umap.by.age
```

```{r umap-poster, echo=FALSE}
knitr::include_graphics("figures/retinal-umap.png")
```

## Compare the embeddings without edges

Before drawing graph topology, render the symmetric-kNN layout coordinates
with the same point function used for UMAP. This isolates the change in
geometry from the visual effect of an edge overlay.

```{r sknn-points-code, eval=FALSE}
sknn.points.by.age <- plot3D.groups(
  retina$coordinates$sknn,
  groups = retina$annotations$age,
  scale = age.scale,
  point.size = 2.2,
  alpha = 0.78,
  axes = FALSE,
  aspect = "equal",
  camera = camera
)
sknn.points.by.age
```

```{r comparison-poster, echo=FALSE}
knitr::include_graphics("figures/retinal-comparison.png")
```

The code holds cell identities, stage colors, point styling, projection, and
initial camera fixed. The static poster additionally uses the independent rigid
orientations described below. The two widgets created by the code rotate
independently; synchronized capture is a separate README production step.

## Add the symmetric kNN graph

For the graph view, `dgraphs` constructed Euclidean symmetric kNN graphs from
the same 20-PC representation used upstream of UMAP, using all 120,804 cells.
The connected `k = 4` reference was selected after reviewing a response series
(`k = 3, 4, 6, 8, 12, 16, 29`) and three layout seeds at `k = 3` and `k = 4`.
`grip` fitted weighted-GRIP followed by edge-KK refinement to the full graph
with 374,597 edges, before extracting the displayed cells. Connectivity alone
does not establish geometric fidelity.

The bundled edge table contains only original edges whose endpoints both occur
in the display sample. This induced subgraph can be disconnected even though
the full fitting graph is connected. No layout is refitted on the induced graph.

`prepare.graph()` validates the bundled edge table without loading `rgl` or
recomputing a layout. The weights are declared as distances, but visual edge
width is chosen explicitly rather than inferred from them.

```{r prepare-graph}
graph <- prepare.graph(retina$graph)
nrow(graph$vertices)
nrow(graph$edges)
graph$weight.type
```

```{r graph-code, eval=FALSE}
graph.by.age <- plot3D.graph(
  graph,
  X = retina$coordinates$sknn,
  groups = retina$annotations$age,
  scale = age.scale,
  point.size = 2.1,
  alpha = 0.84,
  edge.col = "#59687324",
  edge.width = 1,
  axes = FALSE,
  aspect = "equal",
  camera = camera
)
graph.by.age
```

The README preview shows the extracted coordinates without edges; the code
above adds the induced graph's edge overlay.

```{r graph-poster, echo=FALSE}
knitr::include_graphics("figures/retinal-sknn.png")
```

## Change the annotation, not the geometry

The cell-type view reuses the coordinates, graph, camera, and rendering
settings. Only `groups` and `scale` change.

```{r cell-type-code, eval=FALSE}
umap.by.cell.type <- plot3D.groups(
  retina$coordinates$umap,
  groups = retina$annotations$cell.type,
  scale = cell.scale,
  point.size = 2.2,
  alpha = 0.78,
  axes = FALSE,
  aspect = "equal",
  camera = camera
)

graph.by.cell.type <- plot3D.graph(
  graph,
  X = retina$coordinates$sknn,
  groups = retina$annotations$cell.type,
  scale = cell.scale,
  point.size = 2.1,
  alpha = 0.84,
  edge.col = "#59687324",
  edge.width = 1,
  axes = FALSE,
  aspect = "equal",
  camera = camera
)
```

This separation is useful when comparing views: changing geometry should not
silently change the sampled observations, annotation mapping, or palette.

## Interpretation and boundaries

The UMAP and graph-layout coordinates answer different visualization
questions. UMAP displays a nonlinear embedding produced by its own neighbor
and optimization choices. The graph layout displays one selected symmetric
kNN topology and its weighted drawing objective. Apparent proximity in either
view is not evidence that the other representation contains the same
neighborhoods.

## Reproduction and provenance

The README's graph animation and the original two-panel comparison use the
same bundled visualization values but are captured as synchronized 72-frame
rotations, each lasting 14.4 seconds.
Both comparison panels use the same point
styling, orthographic projection, and camera angles at each frame. Before
rotation, each embedding is independently oriented so that its P14 centroid
faces the viewer. These are rigid display rotations, not refits or
deformations of the coordinates.

In the source repository,
`make readme-retinal` reconstructs the upstream PC matrix and graph layout,
renders the point-cloud hero and UMAP comparison, verifies their WebGL
canvases, and encodes the GIFs.
The graph animation, two-panel comparison, case-study object, and vignette
posters are regenerated with:

```{r regeneration, eval=FALSE}
make retinal-vignette
```

The README's three-panel comparison adds the separately computed PHATE
coordinates. The repository includes its
[PHATE fitting script](https://github.com/pgajer/ivue/blob/main/tools/fit-retinal-phate.py)
and [rendering script](https://github.com/pgajer/ivue/blob/main/tools/render-retinal-readme.R).

The stored provenance records source checksums, graph-selection diagnostics,
layout information, and the versions of the packages that computed the graph
and layout.

```{r provenance}
retina$provenance[c("citation", "sample", "umap")]
retina$provenance$graph.input
retina$provenance$graph.selection$selected
retina$provenance$fitting.graph
retina$provenance$graph.layout$method
```
