{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "boulder-story-intro",
      "metadata": {},
      "source": "# A Boulder story in three views\n\nA finished figure should communicate before anyone clicks. This example uses one small, explicit dataset to explain a comparison over time, rank six sites, then locate the differences on a map. Select a site in any view to highlight the same site throughout the notebook. The first two charts need **no map pack**. Run the optional final chart after installing only `usa.co`.\n\n**These are invented teaching data, not Boulder observations.** All daily bicycle counts and the six approximate Boulder area coordinates are illustrative. Names suggest settings, not real monitoring stations; the dates October 12 to 18, 2026 are assigned only for teaching. No official city traffic claim, spatial membership test or causal explanation is implied. The standalone browser explorer adds inclusive date filters and a multi site checklist using this same fixture. These prepared browser controls recompute values locally; native `.link` notebook outputs retain their single site emphasis behavior. The JSON fixture in `examples/fixtures/plot_boulder_story.json` records the same daily inputs and coordinates; this notebook embeds its data and does not read that file.\n\nUse the matching native development runtime. Interactive maps need the compatible Aner VS Code extension; static SVG remains useful without interaction. The runtime and extension supply all plotting code locally.\n"
    },
    {
      "cell_type": "code",
      "id": "boulder-story-imports",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "// Development example: requires the matching chart-enabled native runtime.\n",
        "// Every count and coordinate below is invented for teaching, for October 12-18, 2026.\n",
        "// Names suggest Boulder-area settings, not real monitoring stations.\n",
        "import aner.plot\n",
        "import aner.geo\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "boulder-story-data",
      "metadata": {},
      "source": "## The same week underlies every view\n\n| Illustrative site | Mon | Tue | Wed | Thu | Fri | Sat | Sun | Week total | Weekday daily mean | Weekend daily mean | Weekend uplift |\n|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n| Creek | 400 | 420 | 440 | 460 | 480 | 840 | 920 | 3,960 | 440 | 880 | +100% |\n| Pearl | 450 | 475 | 500 | 525 | 550 | 600 | 650 | 3,750 | 500 | 625 | +25% |\n| Campus | 650 | 675 | 700 | 725 | 750 | 420 | 420 | 4,340 | 700 | 420 | −40% |\n| North | 220 | 235 | 250 | 265 | 280 | 350 | 400 | 2,000 | 250 | 375 | +50% |\n| East | 320 | 335 | 350 | 365 | 380 | 315 | 315 | 2,380 | 350 | 315 | −10% |\n| Chautauqua | 180 | 190 | 200 | 210 | 220 | 500 | 540 | 2,040 | 200 | 520 | +160% |\n\nWeekly total = sum of all seven daily counts. **Weekend uplift = 100 × (weekend daily mean / weekday daily mean − 1)**, where weekend mean uses two days and weekday mean uses five. Comparing raw two day and five day totals would answer a different question. Counts represent invented events, not unique people; there is no uncertainty model or sampling claim.\n"
    },
    {
      "cell_type": "code",
      "id": "boulder-story-inputs",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "// Mon Oct 12 through Sun Oct 18, 2026: one invented teaching week.\n",
        "let days = List(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0)\n",
        "let creekDaily = List(400.0, 420.0, 440.0, 460.0, 480.0, 840.0, 920.0)\n",
        "let pearlDaily = List(450.0, 475.0, 500.0, 525.0, 550.0, 600.0, 650.0)\n",
        "let campusDaily = List(650.0, 675.0, 700.0, 725.0, 750.0, 420.0, 420.0)\n",
        "let northDaily = List(220.0, 235.0, 250.0, 265.0, 280.0, 350.0, 400.0)\n",
        "let eastDaily = List(320.0, 335.0, 350.0, 365.0, 380.0, 315.0, 315.0)\n",
        "let chautauquaDaily = List(180.0, 190.0, 200.0, 210.0, 220.0, 500.0, 540.0)\n",
        "let boulderTheme = plot.theme(\"light\").palette(List(\"#0F766E\", \"#B45309\", \"#6366A0\", \"#A43F62\", \"#2563A6\", \"#6C7830\"))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "boulder-story-trend-context",
      "metadata": {},
      "source": "## 1. Explain a change before adding controls\n\nCreek's invented weekend daily mean is **880**, twice its weekday mean of **440**. Campus moves in the other direction: **420** versus **700**, a **40% decrease**. The two lines reverse their ordering on Saturday. Numeric x positions are explicitly mapped to Monday through Sunday.\n\nAll six daily series share fixed axes. Teal is Creek; amber is Campus. Each series has a stable site ID. Click a line, bar or map point to highlight that site across the views, or choose its site button with the keyboard. Selection emphasizes existing values without filtering observations or recalculating scales. Select the same site again, use Clear selection or press Escape to restore every series.\n"
    },
    {
      "cell_type": "code",
      "id": "boulder-story-trend",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "// No map pack: all six daily series share fixed axes and the same site IDs.\n",
        "let boulderTrend = plot.line(x: days, y: creekDaily)\n",
        "    .add_line(x: days, y: campusDaily, name: \"Campus\")\n",
        "    .add_line(x: days, y: pearlDaily, name: \"Pearl\")\n",
        "    .add_line(x: days, y: northDaily, name: \"North\")\n",
        "    .add_line(x: days, y: eastDaily, name: \"East\")\n",
        "    .add_line(x: days, y: chautauquaDaily, name: \"Chautauqua\")\n",
        "    .link(group: \"boulder-sites\", ids: List(\"Creek\", \"Campus\", \"Pearl\", \"North\", \"East\", \"Chautauqua\"))\n",
        "    .title(\"Creek rises on weekends; Campus peaks on weekdays\")\n",
        "    .labels(x: \"Day (1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat, 7=Sun)\",\n",
        "        y: \"Invented bicycle count per day\")\n",
        "    .annotate(x: 6.0, y: 840.0, text: \"Creek\")\n",
        "    .annotate(x: 6.0, y: 420.0, text: \"Campus\")\n",
        "    .caption(\"Synthetic week, Oct 12-18 2026. Select a site across all views. Counts are invented, not observed bicycle traffic.\")\n",
        "    .theme(boulderTheme)\n",
        "    .size(width: 1000, height: 640)\n",
        "boulderTrend.show()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "boulder-story-comparison-context",
      "metadata": {},
      "source": "## 2. Compare totals on a common zero baseline\n\nCampus totals **4,340** invented bicycle events across the week, compared with Creek's **3,960**. The ordering differs from a simple weekend impression. The bar chart sorts all six sites by total, uses a common zero baseline and supplies exact count labels. No map is needed to make this comparison.\n"
    },
    {
      "cell_type": "code",
      "id": "boulder-story-comparison",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "// No map pack: totals equal the sum of all seven daily counts above.\n",
        "// Sort the categories by total to make comparisons immediate.\n",
        "let rankedSites = List(\"Campus\", \"Creek\", \"Pearl\", \"East\", \"Chautauqua\", \"North\")\n",
        "let rankedTotals = List(4340.0, 3960.0, 3750.0, 2380.0, 2040.0, 2000.0)\n",
        "let boulderComparison = plot.bar(labels: rankedSites, values: rankedTotals)\n",
        "    .link(group: \"boulder-sites\", ids: rankedSites)\n",
        "    .title(\"Campus has the highest invented weekly bicycle count\")\n",
        "    .labels(x: \"Illustrative Boulder-area site\", y: \"Invented bicycle count across seven days\")\n",
        "    .annotate(x: 0.0, y: 4340.0, text: \"4,340\")\n",
        "    .annotate(x: 1.0, y: 3960.0, text: \"3,960\")\n",
        "    .annotate(x: 2.0, y: 3750.0, text: \"3,750\")\n",
        "    .annotate(x: 3.0, y: 2380.0, text: \"2,380\")\n",
        "    .annotate(x: 4.0, y: 2040.0, text: \"2,040\")\n",
        "    .annotate(x: 5.0, y: 2000.0, text: \"2,000\")\n",
        "    .caption(\"Synthetic seven-day totals, Oct 12-18 2026. Sites are sorted by count. No municipal or observed traffic data.\")\n",
        "    .theme(boulderTheme)\n",
        "    .size(width: 1000, height: 640)\n",
        "boulderComparison.show()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "boulder-story-map-context",
      "metadata": {},
      "source": "## 3. Locate the differences, then explore if useful\n\nOnly this chart needs the optional **`usa.co`** pack. Transfer its matching `.anermap` file and install it locally with `aner maps install FILE.anermap`. If you override `ANER_MAPS_DIR`, use the same absolute map store directory for the runtime and notebook process. No network request occurs during plotting or navigation.\n\n**Point color is weekend uplift in percent**: pale teal = −40%, dark teal = +160%, with a continuous sequential scale between them. The color encodes a numeric range; it is not a categorical `below/above-zero` scheme. **Circle area is the same seven day count used by the bars**; it does not encode physical area, population or uncertainty. Position stays fixed when labels are rearranged. Region shading is neutral because no area level measure is joined; its `N/A` key means unavailable, not zero.\n\nCoordinates are longitude first in WGS84 degrees. The local Lambert azimuthal equal area display projection centers the Boulder extent. Generalized Census 2025 place boundaries provide context, including nearby places in the window; they are not streets, trails, current cadastral boundaries or evidence that a supplied point belongs to a municipality.\n"
    },
    {
      "cell_type": "code",
      "id": "boulder-story-positions",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "// Optional final chart: only the usa.co pack is needed.\n",
        "// These approximate Boulder-area coordinates do not identify monitoring stations.\n",
        "let siteNames = List(\"Creek\", \"Pearl\", \"Campus\", \"North\", \"East\", \"Chautauqua\")\n",
        "let sitePositions = List(\n",
        "    geo.position(longitude: -105.280, latitude: 40.010),\n",
        "    geo.position(longitude: -105.277, latitude: 40.018),\n",
        "    geo.position(longitude: -105.264, latitude: 40.006),\n",
        "    geo.position(longitude: -105.269, latitude: 40.051),\n",
        "    geo.position(longitude: -105.231, latitude: 40.017),\n",
        "    geo.position(longitude: -105.292, latitude: 39.997))\n",
        "let weeklyTotals = List(3960.0, 3750.0, 4340.0, 2000.0, 2380.0, 2040.0)\n",
        "// Compare daily means, not a two-day total against a five-day total:\n",
        "// 100 * (((Sat + Sun) / 2) / ((Mon + Tue + Wed + Thu + Fri) / 5) - 1).\n",
        "let weekendUplift = List(100.0, 25.0, -40.0, 50.0, -10.0, 160.0)\n",
        "let boulderMapTheme = plot.theme(\"light\").palette(List(\"#DDEFF0\", \"#74B9C1\", \"#0F6578\"))\n"
      ]
    },
    {
      "cell_type": "code",
      "id": "boulder-story-map",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "// Empty region joins leave boundary context neutral: N/A means no area-level measure.\n",
        "let boulderMap = plot.map(\"usa.co.places\", keys: List(), values: List())\n",
        "    .add_points(positions: sitePositions, values: weekendUplift, labels: siteNames)\n",
        "    .point_sizes(values: weeklyTotals, max_radius: 13.0)\n",
        "    .point_labels(\"avoid\")\n",
        "    .link(group: \"boulder-sites\", ids: siteNames)\n",
        "    .extent(west: -105.34, south: 39.96, east: -105.20, north: 40.09)\n",
        "    .projection(\"lambert_azimuthal_equal_area\")\n",
        "    .title(\"Weekend patterns differ across six illustrative Boulder sites\")\n",
        "    .caption(\"Invented sites/counts. Color = weekend daily uplift (%); area = week total. Census 2025 context; no region measure.\")\n",
        "    .theme(boulderMapTheme)\n",
        "    .size(width: 1000, height: 640)\n",
        "boulderMap.show()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "boulder-story-navigation",
      "metadata": {},
      "source": "## Optional interaction with a useful static starting point\n\nThe map already shows the six site comparison. Its `.link(group: \"boulder-sites\", ids: siteNames)` uses the same group and exact String IDs as the line and bar charts. IDs follow marks, not row positions: the bar order differs from the map order. A distinct group keeps unrelated analyses independent. Selection is local to the notebook document and does not send events to the kernel. In the compatible notebook renderer, drag to pan; use **+ / −**, double click, or **Ctrl/Cmd + wheel** to zoom. Arrow keys pan with the map focused; **Home** and **Reset** restore the initial view. Plain wheel scrolling continues to scroll the notebook.\n\nWith `point_labels(\"avoid\")`, labels appear when nearby space becomes available. The marker positions never move, while marker sizes, text sizes and value domains stay fixed. `point_labels(\"all\")` allows overlap and `point_labels(\"none\")` removes point text; complete labels and values remain in tooltips. Coincident points can remain crowded at every zoom.\n\nNavigation is bounded by the initial extent and keeps its projection. Zoom cannot add street detail or recover geometry omitted from the generalized source. Edit `extent(...)` and rerun for a different reproducible window. Other clients and oversized scenes retain static SVG.\n\nSaved `.anernb` outputs contain bounded scenes and SVG fallbacks. Reopening with the compatible extension can navigate them offline without a running native session or source packs; rerunning boundary code requires `usa.co` again. Navigation and selection changes are temporary and do not modify the original chart or SVG export. Saved scenes reopen with no selection. A selected site keeps its highlight while the map moves; its label receives priority when a collision free placement is available. Site buttons remain usable when its point is outside the current map view.\n\nTo export the initial figures, evaluate `boulderTrend.save(\"boulder-trend.svg\")`, `boulderComparison.save(\"boulder-comparison.svg\")` and `boulderMap.save(\"boulder-map.svg\")` in a writable working directory. The standalone source `examples/plot_boulder_story.aner` contains exactly the same code cells in order.\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
}
