{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "plot_boulder_3d-intro",
      "metadata": {},
      "source": "# Boulder: CSV captures in a 3D plot\n\nRun the cells in order with the matching CSV frame enabled native runtime and notebook integration. Download `plot_boulder_3d_frames.csv` and preserve `examples/fixtures/plot_boulder_3d_frames.csv` relative to the process or kernel working directory. For the supplied project, open the root folder containing `examples/`; a downloaded notebook does not locate its CSV relative to the notebook file. Edit the same path in all formats if using another folder layout.\n\nThe CSV contains 378 synthetic rows: six time labels and 63 stable site IDs `B001`: `B063`. All coordinates, heights, temperatures and labels are invented teaching data, not terrain or municipal monitoring. These are local Cartesian metres without a geographic coordinate reference system. Construction needs the local CSV and no map pack or network connection. The analysis uses an explicit schema and bounded complete collection; it generates no coordinates or observations in code.\n\nDrag inside the plot with your mouse to orbit the camera. Use **Rotate plot** X, Y and Z degree fields, or their ±15° buttons, to turn the plot around each axis. **Reset** restores the authored initial view; **XY**, **XZ**, **YZ** and **Home** also clear the temporary axis rotations. These controls work offline without map packs.\n\nThe chart starts **paused on the first frame**. Use the time slider or previous/next buttons to inspect a capture, or Play/Pause to step through the six supplied frames. Playback advances uniformly once per second and stops at the end; it does not reproduce the two hour intervals written in the assigned UTC labels. No intermediate observations are interpolated. Labels are supplied text, not parsed calendar values.\n"
    },
    {
      "cell_type": "code",
      "id": "plot_boulder_3d-1",
      "metadata": {},
      "source": [
        "// Requires the matching CSV-frame-enabled native development runtime.\n",
        "// Run from the project root containing examples/fixtures/plot_boulder_3d_frames.csv.\n",
        "// Invented local coordinates and temperatures; no terrain or real sensor data.\n",
        "import aner.data\n",
        "import aner.plot\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "id": "plot_boulder_3d-2",
      "metadata": {},
      "source": [
        "// Long format: one required observation per explicit time label and stable site ID.\n",
        "var schema = data.schema()\n",
        "schema = data.column(schema, \"time\", \"String\", false)\n",
        "schema = data.column(schema, \"site\", \"String\", false)\n",
        "schema = data.column(schema, \"x\", \"Float64\", false)\n",
        "schema = data.column(schema, \"y\", \"Float64\", false)\n",
        "schema = data.column(schema, \"z\", \"Float64\", false)\n",
        "schema = data.column(schema, \"value\", \"Float64\", false)\n",
        "let source = data.scan_csv(\"examples/fixtures/plot_boulder_3d_frames.csv\", schema, missing: \"NA\")\n",
        "let bounded = data.bounded(source, max_scanned_rows: 10001,\n",
        "    max_read_bytes: 1048576, timeout_ms: 5000)\n",
        "let table = data.collect(bounded, max_rows: 10000,\n",
        "    max_bytes: 1048576, batch_rows: 64)\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "id": "plot_boulder_3d-3",
      "metadata": {},
      "source": [
        "// First-seen time and site order define frames and stable observation indices.\n",
        "// Later CSV frames deliberately shuffle rows; the adapter aligns them by site ID.\n",
        "// No map pack is needed. The chart owns the complete collected snapshot.\n",
        "let boulder = plot.scatter3d_frames_table(table)\n",
        "    .labels(\"Local east (m)\", \"Local north (m)\", \"Height above invented datum (m)\")\n",
        "    .color_label(\"Temperature (°C)\").color_limits(12.0, 24.0)\n",
        "    .aspect(\"equal\").view(azimuth: -45.0, elevation: 28.0)\n",
        "    .title(\"Boulder: six synthetic 3D temperature frames\")\n",
        "    .caption(\"63 invented sampling positions × 6 assigned UTC labels; synthetic temperatures, not terrain or municipal measurements. Equal metres; fixed 12–24 °C color scale.\")\n",
        "    .theme(plot.theme(\"light\")).size(width: 1000, height: 680)\n",
        "// Drag inside the interactive plot to orbit; Rotate plot X/Y/Z turns its axes.\n",
        "// Reset restores the initial view. Exported HTML needs no CSV or map packs to rotate.\n",
        "boulder.show()\n",
        "\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "id": "plot_boulder_3d-export",
      "metadata": {},
      "source": "The CSV headers are `time,site,x,y,z,value`, with required String time/site fields and finite Float64 local coordinates and temperature. Every time/site pair occurs exactly once; missing pairs, null cells and duplicates are errors. First appearance sets both time order and global site order. The first frame establishes `B001`: `B063`; later frames deliberately shuffle rows, and the adapter aligns them by the stable site ID rather than row number or coordinates. Site IDs define observation indices but are not displayed by the current spatial inspector.\n\nThis fixture repeats XYZ and varies temperature by offsets −1, 0, 1, 2, 1 and 0 °C. The values are already stored in the CSV: rerunning this notebook reads them without trigonometric reconstruction or generated Lists. Coordinates may move in other inputs, provided the complete time/site grid remains present. `plot.scatter3d_frames(times, x, y, z, values)` remains available for explicit frame major matrices.\n\nCoordinate domains are computed across all captures, and temperature uses a fixed 12 to 24 °C color scale. Moving the slider never refits axes or recolors an unchanged value. Rotation, zoom and the selected observation persist between captures. The inspector and paged table show the selected frame's exact values. Equal aspect gives one metre the same length on all axes; the shallow height range is intentional.\n\nThe X/Y/Z controls rotate about the scale normalized full data plot center, in fixed X then Y then Z order, before the existing azimuth/elevation camera. Their initial offsets are zero. Mouse dragging or camera angle edits retain those offsets; changing frames keeps them, the camera and observation selection. Rotation pauses playback and changes only the view: it does not alter coordinates, temperatures, domains or the CSV. Native SVG and later exports retain the source defined initial view.\n\nTime is an explicit sequence in addition to XYZ and scalar temperature. It does not add a fourth spatial axis or imply a physical simulation. Use orthogonal views and the exact value table to inspect occluded points. No map packs, tile downloads or new position datatype are needed.\n\n`data.collect` must reach end of input; an incomplete preview is rejected. This example admits at most 10,000 rows, with 10,001 scanned rows for the end of file check, 1 MiB read and managed collection budgets, a five second timeout and batches of 64. A bound failure creates no partial chart. The adapter supports 1 to 120 frames and at most 10,000 total time/site pairs. Snapshot construction copies the validated table; changing or removing the CSV afterward does not alter a retained Chart. Rerun the read and construction to adopt edited CSV values.\n\nRun All displays the chart and writes no files. To export, add a new cell with fresh paths:\n\n```aner\nboulder.save(\"fresh-boulder-3d.html\")\nboulder.save(\"fresh-boulder-3d.svg\")\n```\n\nHTML embeds every capture and local control, starts paused and needs neither the CSV, map packs nor a running kernel to view offline. SVG exports the labeled first frame using the same shared scales. Existing paths are never overwritten. The `.aner` source appends these exports to the same analysis; browser camera and playback choices do not change the reproducible source export.\n"
    }
  ],
  "metadata": {
    "kernelspec": {
      "name": "aner",
      "display_name": "Aner",
      "language": "aner"
    },
    "language_info": {
      "name": "aner",
      "file_extension": ".aner",
      "mimetype": "text/x-aner"
    },
    "aner": {
      "status": "example",
      "execution_mode": "persistent-session"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
