qsv viz examples

September 2, 2026 · View on GitHub

Sample datasets and ready-to-run commands that showcase every qsv viz chart type. All commands assume you're in this directory and qsv is on your PATH (built with the viz feature). Each writes a self-contained, interactive HTML file you can open in any browser; swap the .html extension for .png/.svg/ .pdf/.jpeg/.webp to export a static image (requires a viz_static build and a local Chrome/Firefox).

gallery.html is a single, lightweight page rendering every chart type below from these datasets — handy for linking from the wiki. It loads plotly from the CDN (so the file stays small), and is a generated artifact: regenerate it (after changing viz output or the datasets) by building qsv and running gen_gallery.py from the repo root — python3 examples/viz/gen_gallery.py. Individual qsv viz outputs are instead fully self-contained (plotly embedded), so they work offline.

The sixteen smart dashboards are embedded as <iframe>s of their genuine qsv viz smart HTML output (smart_*.html) rather than reconstructed inline, so the full-width overview panels (map, choropleth, correlation heatmap, time-series, treemap/sunburst hierarchy), themes and map zoom buttons render exactly as the CLI produces them. Those iframe sources are the real output with the inline plotly bundle swapped for the same CDN tag (so they stay a few KB each); they need a network connection to render. Four of them (smart_dict_treemap.html, smart_dict_sunburst.html, smart_world_choropleth.html, smart_geospatial.html) are --dictionary infer examples that need a local LLM to regenerate, so gen_gallery.py reuses the committed copies instead of re-running the LLM.

To refresh those four, run the generator with a local LLM up (LM Studio / Ollama) and QSV_LLM_BASE_URL / QSV_LLM_MODEL set:

  • QSV_VIZ_REGEN_LLM=1 rebuilds the four dashboards, but reuses their existing dictionaries--dictionary infer reuses the <stem>.schema.json sidecar beside each input, and describegpt replays its own disk-cached completion. Use this when only the Data Schematic rendering changed.
  • QSV_VIZ_REGEN_LLM=2 additionally re-infers the dictionaries from scratch: it deletes those four sidecars and sets QSV_VIZ_DICT_FRESH=1, which makes viz skip sidecar reuse and pass --fresh to describegpt. Use this after a model or prompt-file change. It makes real LLM calls, so the four figures are not byte-stable across runs — review the diff before committing. The curated dictionaries (*_dict.schema.json, boston311, pitt311data) are never touched.

Reuse is the default for a reason: re-inferring is the one step here that is not reproducible. See Curating a dictionary for why a committed sidecar is the artifact of record.

The gallery closes with six clickable screenshot link-outssmart_nyc311.html, pitt311data.html, smart_boston_311_2025.html, smart-brazil-lpg-4-semanas.html, smart-colombia-calidad-aire.html and smart_pa_crashes.html — full --dict-info visual data dictionaries: three over real municipal 311 data, two localized dashboards rendered entirely in Portuguese (--language pt) and Spanish (--language es), and one per-capita rate map over 3.1M PennDOT crash records where --denominator census fetches ACS county population so the Data Schematic draws the raw-count map and the rate map side by side (it is also the only light dashboard, --theme plotly_white). They are shown as scaled preview images that open the Data Schematic in its own popup window, rather than embedded, so the gallery page stays light (those pages run 3.9–20.9 MB each). gen_gallery.py still regenerates the NYC one from the committed nyc_311.csv; the other five are committed artifacts whose source data is not in the repo, so they are reused as-is.

▶ View it rendered (GitHub Pages, served with the correct text/html type):

https://dathere.github.io/qsv/gallery.html

This directory is published to GitHub Pages by .github/workflows/viz-gallery-pages.yml on every push to master, so the gallery — and its embedded smart_*.html iframes — render directly, no third-party proxy needed.

Raw file (downloads / shows source — raw.githubusercontent.com serves HTML as text/plain, so a browser won't render it):

https://raw.githubusercontent.com/dathere/qsv/master/examples/viz/gallery.html

Datasets

FileShapeUsed by
sales_sample.csv500 e-commerce orders: categoricals, a boolean, a rating, several correlated numerics, an ID and a high-cardinality text columnsmart, bar, line, scatter (incl. bubble & 3D), histogram, box, pie, heatmap, contour
stock_prices.csv90 trading days of date,open,high,low,close,volumesmart (time-series), candlestick, ohlc, line
web_flows.csvsource,target,sessions funnel edgessankey
signup_funnel.csv15 rows of stage,channel,users — 5 signup stages x 3 channels, the stages already in process orderfunnel (stages as ROWS, summed per stage; no dictionary needed)
nyc_capital_projects.csv12,587 NYC capital projects (CPDB): managing agency, project type, and three budget aggregates that do NOT nestsmart (pipeline bridge, Lorenz/Gini)
onboarding_funnel.csv60 channel-weeks of visits,signups,activated,subscribed — four stage columns that nest in every rowsmart (pipeline funnel)
product_ratings.csvbrand + 6 numeric score axes (multiple reviews per brand)radar
quakes.csv40 world cities with lat,lon,magnitude,depth_km,regionsmart (auto geo panel — global extent), map (points & density), geo (projection)
country_stats.csv20 countries with iso3,country,gdp_usd_tnchoropleth (fill countries by GDP, matched by ISO-3 code)
us_state_stats.csv20 US states with state,renewable_electricity_pctchoropleth --location-mode usa-states (built-in state geometry, albers-usa)
northeast_states.csv + northeast_states.geojson11 Northeast states (MD → ME) with state,people_per_sq_mi, plus real Census TIGER boundaries keyed by properties.STUSAB (with properties.NAME auto-detected for hover)choropleth --map --geojson … --feature-id-key properties.STUSAB --agg mean (filled regions on a MapLibre tile basemap; --agg mean because density is not additive)
tristate_county_broadband.csv13 counties across PA, OH and WV keyed by 5-digit county FIPS (county_fips), with a synthetic broadband_pct measurechoropleth --geojson auto (no boundary file supplied — the county FIPS codes name the states, and just those states' county boundaries are fetched from Census TIGERweb, with --feature-id-key properties.GEOID set automatically)
world_cities.csv1,179 cities with population over 500,000 across six inhabited continents (GeoNames-derived): country, continent, lat/lon, metro_population_m, elevation_m (real), avg_annual_temp_c (synthesized from latitude + elevation). continent uses the plotly.js geo scope vocabulary (Oceania, North America, …)smart --dictionary infer (dense global geo map + per-COUNTRY choropleth via fitbounds with geocode + a six-continent bar + box panels)
us_cities.csv54 US cities across ~35 states: lat/lon, census_region, population_m, median_agesmart (US point map + per-US-STATE choropleth with geocode + box/bar/correlation panels)
customer_spend.csv300 customers: a bimodal monthly_spend, a right-skewed account_age_days, plan/region categoricals, an IDsmart --smarter (moarstats-informed: histogram + box hints)
seismic_events.csv + japan_prefectures.geojson417 synthetic Japanese earthquakes (timestamp, lat/lon, a bimodal depth_km, a right-skewed magnitude correlated with felt_reports, a tsunami boolean, region, an ID), plus a GeoJSON of the 47 prefectures keyed by properties.id (ISO 3166-2), with a top-level id too so no --feature-id-key is neededsmart --smarter --geojson japan_prefectures.geojson (the full geospatial Data Schematic: map + prefecture choropleth via point-in-polygon binning + time-series + correlation + scatter + histogram + boxes + bars)
delivery_stops.csv90 delivery stops clustered in metro Denver + 4 bad-geocode strays in neighboring states, with zone/vehicle categoricals, packages, and correlated weight_kg/distance_km/delivery_minutes numerics over a delivered_datesmart (geographic outlier markers + core/full extent boxes, Core/Full zoom buttons & spatial-extent call-out with geocode; plus boxes, bars, correlation heatmap, strongest-pair scatter & a time-series — no --smarter needed)
cms_medicare_providers.csv30,007 Medicare practitioners (CMS sample): provider_type (87 values), state (58), and the additive total_patients / medical_payment measuressmart --smarter (Lorenz curve + Gini per inequality measure, plus log-axis boxes for the right-skewed money columns)
regions_growth.csv844 monthly observations for 6 synthetic regions over 24 months: region, month_date, gdp_index, wellbeing_index, population_mscatter (Gapminder bubble animation--slider for time, --series for the entity, --size for the third data variable), smart (auto-selects the same chart — one bubble per region tracing a curved path through the measure space, Play/Pause + scrub slider)
world_events_dated.csv264 dated events across 110 places on six continents (2024-01-07 → 2024-12-30): place, lat/lon, event_date, magnitudesmart (animated geographic reveal on a ScatterGeo projection basemap — points accumulate over monthly buckets; the animation only fires for continental/global extents)
nyc_311.csv + nyc_neighborhoods.geojson + nyc311_dict.schema.json10,000-row sample of NYC 311 service requests (2010–2020): 41 columns incl. Latitude/Longitude, Borough, Agency, Complaint Type, Status, several date columns, and X/Y Coordinate (State Plane), plus a custom GeoJSON of 188 NYC neighborhoods keyed by top-level id and a committed 41-field dictionarysmart --smarter --bivariate --dict-info --dictionary nyc311_dict.schema.json --geojson nyc_neighborhoods.geojson (full municipal Data Schematic: dense point map + neighborhood choropleth on a MapLibre tile basemap via point-in-polygon binning + correlation + time-series + boxes + bars + an NMI association heatmap, with the committed dictionary's field labels — no LLM needed)
district_requests.csv + district_boundaries.geojson + district_requests_dict.schema.json1,332 SYNTHETIC municipal service requests across 6 invented districts (district, population, request_type, response_hours), plus a GeoJSON of those districts keyed by a top-level id and carrying a properties.POP2020, and a curated dictionary whose region column declares x-qsv.denominator: {"column": "population"}. The districts are laid out on a grid at the geographic centre of the contiguous US — only the shape of the fallacy is realsmart --dict-info --dictionary district_requests_dict.schema.json --geojson district_boundaries.geojson (denominator-aware region map: the raw-count choropleth is led by the most populous district, while the rate panel beside it — requests per 1,000 residents — inverts the ranking)
allegheny_dog_licenses.csv + allegheny_zip_boundaries.geojson + allegheny_dogs_dict.schema.jsonAll 50,013 Allegheny County lifetime dog licenses (LicenseType, Breed, Color, DogName, OwnerZip, ExpYear, ValidDate) — no lat/lon, only the OwnerZip region code — plus a GeoJSON of 125 county zip boundaries keyed by properties.ZIP and a curated dictionary tagging OwnerZip as geo.zip_codesmart --smarter --bivariate --dict-info --dictionary allegheny_dogs_dict.schema.json --geojson allegheny_zip_boundaries.geojson --feature-id-key properties.ZIP (summary choropleth keyed off a region-code COLUMN, not coordinates: licenses-per-zip filling the boundary polygons on a MapLibre tile basemap, + Breed/Color/LicenseType bars + NMI association heatmap)

Committed data dictionaries

--dictionary <file> takes a JSON Schema Data Dictionary — either hand-authored or generated by describegpt and then reviewed — so the Data Schematic's field labels and semantic routing are deterministic and offline (unlike --dictionary infer, which calls a local LLM on every run). Pair it with --dict-info to render the dictionary as an in-page Data Schematic tab. The committed ones here:

DictionaryFieldsUsed by
sales_kpi_dict.schema.json5sales_sample.csv — hand-authored, and the only one carrying the optional x-qsv KPI hints: gauge_range draws a measure as a gauge on its canonical scale, target adds a "vs target" delta. target is a business goal you choose; describegpt never emits it
nyc311_dict.schema.json41nyc_311.csv — tags identifier columns (Unique Key, BBL) so they're skipped, labels the State-Plane coordinates, and supplies friendly names
allegheny_dogs_dict.schema.json8allegheny_dog_licenses.csv — tags OwnerZip as geo.zip_code, the signal that turns a numeric zip into a choropleth key
district_requests_dict.schema.json4district_requests.csv — the only one carrying x-qsv.denominator, which names the column holding each region's population so a region map can chart a rate beside its raw count
boston311.schema.json31boston311-2025.tsv (not committed — 153 MB) for the Boston 311 link-out Data Schematic
pitt311data.schema.json26pittsburgh_311.tsv (not committed) for the Pittsburgh 311 link-out Data Schematic
nyc_capital_projects_dict.schema.json7nyc_capital_projects.csv — declares the three budget columns as an x-qsv.relationships pipeline. Their totals grow, so the panel is drawn as a bridge rather than a funnel
onboarding_funnel_dict.schema.json6onboarding_funnel.csv — declares the four stage columns as a pipeline. Their totals nest, so the same declaration earns a funnel
ultimas-4-semanas-glp.schema.json16ultimas-4-semanas-glp.ssv (not committed) for the Portuguese LPG-prices link-out Data Schematic
pa_crashes.schema.json19pa_crashes.csv (not committed — 396 MB, 3,088,272 PennDOT crash records) for the Pennsylvania per-capita link-out Data Schematic — hand-tuned: describegpt tagged the four outcome counts (fatal_count, injury_count, person_count, vehicle_count) as role=dimension with an unknown concept, so they charted as categories and produced no KPI totals; they are additive counts and carry role=measure / measure.count / aggregation: sum here. hour_of_day was inferred as a timestamp and got skipped entirely — it is a cyclical category.type dimension. The geo concepts (county_namegeo.county, stategeo.state) were inferred correctly and are load-bearing: geo.state is what disambiguates county names that occur in several states, and viz smart takes it from the dictionary rather than --region-state
calidad-aire-pm-colombia.schema.json25calidad-aire-pm-colombia.csv (not committed) for the Spanish air-quality link-out Data Schematic — hand-tuned twice: Latitud/Longitud carry explicit geo.latitude/geo.longitude concepts (viz's header-name fallback only matches the English lat/lon, so without them there is no map at all), and only the additive columns keep role=measure, since viz detects non-additive measures from an English token list and would otherwise SUM Spanish Promedio/Mediana/Porcentaje into meaningless KPI totals

Curating a dictionary

Every dictionary in that table started as a --dictionary infer draft. That is the intended workflow, and it has four steps:

infer → review → correct → keep.

An inferred dictionary is a draft, not an oracle. The deterministic half of a Data Schematic (statistics, heuristics, panel selection) is reproducible; the semantic half is an LLM's opinion and is not. Inferring twice over the same data can return different role/concept assignments, and role decides which panel a column gets — so a re-infer can hand you a structurally different dashboard from identical inputs. The corrected sidecar, not the model, is what makes a render reproducible. That is exactly why QSV_VIZ_REGEN_LLM=1 (above) reuses the committed dictionaries: the gallery is immune to re-inference drift because its dictionaries are curated artifacts under version control.

calidad-aire-pm-colombia.schema.json is the clearest case for the review step. Straight from the model it produced no map at all and KPI tiles that summed Spanish Promedio/Mediana columns into meaningless totals. Two hand-corrections fixed both. (Since #4404 the second correction has a more direct expression: x-qsv.aggregation: "mean" states non-additivity per column, in any language, instead of demoting the column out of role: measure.)

Working in a qsv checkout, the visual-data-dictionary Claude Code skill drives this whole flow — clean, infer, fine-tune, render. Its Stage 2.5 edit_dictionary.py is a small terminal UI that walks every column, edits the five fields that steer panel selection (role, concept, label, description, aggregation), and previews how each edit reroutes the column before you render:

# non-interactive view of the current routing — safe anywhere
python3 .claude/skills/visual-data-dictionary/edit_dictionary.py --summary examples/viz/sales_kpi_dict.schema.json

# the TUI itself — needs a real terminal
python3 .claude/skills/visual-data-dictionary/edit_dictionary.py <stem>.schema.json

gauge_range, target, currency and the dataset-level relationships (the pipeline funnel/bridge declaration) are hand-edited in the JSON. qsv re-verifies all of these on read, so a hand-edit can correct the model but cannot assert a hint the data does not support.

The Data Schematic

viz smart auto-profiles the dataset (from qsv's stats cache) and picks a panel per column: a correlation heatmap over the numeric columns (shown as the lower triangle only — the mirror half and the trivial 1.0 diagonal are dropped), box plots for continuous numerics, and frequency bars for low-cardinality categoricals, booleans, and ratings. The box plots overlay sample points based on the column's non-null value count — every point for a small column (≤ 1,000 values, demoted to outliers-only once the Data Schematic runs past 8 panels, where a thousand jittered points per cell turns to noise), and just the Tukey outliers for a medium one (≤ 10,000). Above that the box is a fast cache-only quartile summary — but a column that actually has outliers still gets them overlaid as points, collected by a single fence-filtered pass and capped at 5,000; pass --box-points <outliers|all|suspected|none> to force a mode. When the numeric columns have a strongly correlated pair, a scatter of that pair is added next to the heatmap as a drill-down — or, for large datasets where a scatter would overplot, a 2D density contour of the pair instead. The drill-down title also reports Spearman's rho when it diverges from Pearson's r enough to mean the relationship is monotonic but nonlinear (so the single r isn't read as proof of linearity). With three or more numeric columns, a 3D scatter of the strongest pair plus the least-redundant third axis is added as well — unless the strongest pair is itself near-collinear, in which case the 3D would collapse to a plane and is skipped. When the data has a date/datetime column (auto-detected via stats date inference) plus a continuous numeric column, a time-series trend panel of that column over time is added too. When a latitude/longitude column pair is detected, a geographic map panel leads the Data Schematic — drawn on MapLibre tiles for local extents, or as an offline projection world-overview (ScatterGeo, no tiles or token) when the coordinates span a continental/global area. Points far from the cluster centroid (beyond the Tukey far-out fence of their distances) are flagged as geographic outliers: they're drawn with a distinct marker, excluded from the spatial extent (so a few bad geocodes can't inflate it), and excluded from the auto-zoom (so the default view stays tight on the core cluster). When qsv is built with the geocode feature, the (core) extent is reverse-geocoded into a one-line location summary and outlined with a filled box; when there are outliers, a second dashed no-fill box marks the full extent (core + outliers) so the strays' span stays legible, the interactive HTML map gains Core extent / Full extent buttons to jump between the two views, and any outliers in a different jurisdiction are named in the summary (e.g. Colorado, United States — 4 outliers (Wyoming, Kansas & Nebraska)) while outliers within the core's own jurisdiction are folded in silently. (The extent boxes, buttons, and summary need geocode; the outlier markers render in any viz build.) ID-like and high-cardinality text columns are skipped.

On large datasets viz smart keeps the page light and interactive: each data-heavy panel (map, time-series, correlated-pair scatter, 3D scatter) is uniformly downsampled to at most 150,000 points (tunable with QSV_VIZ_MAX_POINTS; the correlated-pair density contour instead embeds only a fixed bin grid, so it stays compact at any row count), and the map view is framed to the core extent (geographic outliers excluded) so a few stray geocodes can't zoom it out to nothing. The map panel also adapts to volume — a dense point map opens as semi-transparent markers with an in-map Clusters/Points toggle that collapses them into interactive count bubbles on demand (--cluster, offered automatically from 1,000 points). Static density heatmap rendering is off by default; opt back into it with --heatmap-density <n> to aggregate the core at or above n mappable points. (These caps apply only to the smart Data Schematic; the standalone chart commands below plot every row and frame the full extent.)

moarstats-informed dashboards

Pass --smarter to have viz smart run qsv moarstats --advanced itself before building the Data Schematic (or run qsv moarstats first by hand) — either way viz smart reads the extended statistics from the stats cache and makes better chart choices (with neither, the behavior is unchanged):

  • a bimodal/multimodal continuous column (high bimodality coefficient) renders as a histogram instead of a box plot, which would hide the separate peaks;
  • box panels are annotated with the column's skew direction and outlier share (e.g. account_age_days (right-skewed, 4.7% outliers)), from the Pearson skewness and outlier-percentage stats;
  • a concentrated high-cardinality categorical that would normally be skipped as ID-like noise is kept as a top-N bar (when its normalized entropy is low).
# one step: --smarter runs `qsv moarstats --advanced` itself, then builds the Data Schematic
qsv viz smart customer_spend.csv --smarter -o spend_dashboard.html
# monthly_spend (bimodal) -> histogram; account_age_days (skewed) -> annotated box

# or do it in two steps — extend the stats cache first, then let viz smart reuse it
qsv moarstats --advanced customer_spend.csv
qsv viz smart customer_spend.csv -o spend_dashboard.html

moarstats --advanced (which --smarter runs for you) reads the whole file and auto-creates an .idx index; the bimodality test needs the advanced stats, while the skew/outlier box hints work from a plain qsv moarstats run. --smarter applies only with default parsing — inputs using --no-headers or a custom --delimiter fall back to the standard Data Schematic.

# 12 panels from sales_sample.csv (>8, so it renders as an inline-div grid)
qsv viz smart sales_sample.csv -o dashboard.html

# cap the panel count, lay out 3 columns, top-5 categories per bar
qsv viz smart sales_sample.csv --max-charts 6 --grid-cols 3 --limit 5 -o dashboard.html

# stock_prices has a date column, so the Data Schematic leads with a time-series trend
qsv viz smart stock_prices.csv -o stocks_dashboard.html

# quakes has lat/lon, so the Data Schematic leads with a geographic map panel
qsv viz smart quakes.csv -o quakes_dashboard.html

# delivery_stops clusters in metro Denver with a few bad-geocode strays: the map flags them as
# distinct outlier markers and keeps the auto-zoom tight on the core. With the geocode feature it
# also draws a filled core extent box, a dashed-magenta no-fill box around the full extent (core +
# strays), adds Core extent / Full extent zoom buttons to switch between the two views, and calls
# them out in the spatial-extent label, e.g. "... — 4 outliers (Wyoming, Kansas & Nebraska)"
qsv viz smart delivery_stops.csv -o delivery_dashboard.html

# the full geospatial Data Schematic: a map, a prefecture choropleth, a time-series, a correlation
# heatmap + drill-down scatter, a bimodal-depth histogram, annotated boxes and frequency bars —
# all auto-chosen. --geojson adds a point-in-polygon prefecture choropleth (the feature id key
# defaults to id): each quake is binned into the GeoJSON region that contains it (no geocoding).
# This catalog is mostly offshore, so under the auto-derived snap cap (18 km here, derived from the
# boundary file's median region size and the coordinates' precision) near-coast quakes snap to their
# nearest prefecture (44 of them) while far-offshore quakes are dropped (269 of 417; the panel title
# reports both), and on-land/near-coast prefectures are colored; raise --snap-max-dist to snap distant
# quakes to the nearest prefecture, or --no-snap to drop every offshore point. A stderr note reports
# coverage either way.
# Recognized lat/lon columns are charted on the map only, not as redundant distribution panels.
# Rendered with the built-in plotly_dark theme (--theme works on every chart type, incl. smart).
qsv viz smart seismic_events.csv --smarter --theme plotly_dark --grid-cols 3 \
    --geojson japan_prefectures.geojson -o seismic_dashboard.html

# the same point-in-polygon choropleth, but on a CITY-scale dataset: 10k NYC 311 requests binned
# into 188 neighborhood polygons. Because the matched regions span a metro extent (under ~8° in
# both lat and lon), viz smart draws the filled regions on an interactive MapLibre TILE basemap
# (token-free carto tiles, fine street/coastline detail) instead of the coarse projection basemap
# it uses for country/continental choropleths — see the metro-choropleth gallery figure. (The
# --feature-id-key defaults to the GeoJSON's top-level `id`, so it's omitted here.) The committed
# --dictionary supplies the field labels and identifier tags, so this run is fully deterministic
# and needs NO LLM; swap it for `--dictionary infer` to have describegpt infer them live instead
# (that needs a reachable local LLM — set QSV_TIMEOUT generously for slower models).
qsv viz smart nyc_311.csv --smarter --bivariate --dict-info \
    --dictionary nyc311_dict.schema.json \
    --geojson nyc_neighborhoods.geojson -o nyc311_dashboard.html

# A summary choropleth can also be keyed off a region-code COLUMN (a zip/county/state/country
# dimension), with NO lat/lon at all. Here the dog-license log has only an OwnerZip column: viz
# smart aggregates licenses-per-zip and fills the matching --geojson boundary polygons. The key
# column is auto-chosen by matching each geo-dimension column's values against the boundary ids;
# the curated --dictionary tags OwnerZip as geo.zip_code (the signal that makes a numeric zip a
# choropleth key instead of a frequency bar). Only the per-zip COUNT map is drawn here because the
# data carries no numeric measure; a dataset that also tags a measure column gets a per-region
# MEDIAN-of-measure choropleth beside it.
qsv viz smart allegheny_dog_licenses.csv --smarter --bivariate --dict-info \
    --dictionary allegheny_dogs_dict.schema.json \
    --geojson allegheny_zip_boundaries.geojson --feature-id-key properties.ZIP \
    -o allegheny_dogs_dashboard.html

dictionary-guided hierarchy panels (treemap / sunburst / icicle)

When the dataset has 2+ low-cardinality categorical dimensions, viz smart adds a part-to-whole hierarchy panel nesting them (the chosen dimensions still keep their own frequency bars). The chart type is auto-selected by depth, following visualization best practice: a treemap for a shallow 2-level hierarchy (area encodes size, for accurate comparison) and a sunburst for a deeper 3-level one (concentric rings emphasize parent-child structure). Override with --hierarchy-style auto|treemap|sunburst|icicleicicle is an opt-in level-aligned alternative that keeps deep labels readable where a sunburst's outer ring would crowd.

The auto path also checks that the candidate dimensions are statistically associated (bias- corrected Cramér's V): nesting independent categoricals just replicates each level's marginal at every branch and tells you nothing the separate frequency bars don't, so that hierarchy is skipped (with a note on stderr). Pass an explicit --hierarchy-style treemap|sunburst|icicle to force the panel anyway.

Pairing this with --dictionary infer lets a local LLM (via describegpt, default endpoint LM Studio at http://localhost:1234/v1) infer each field's semantic role and a friendly label first, so the dimensions are picked from semantics (not just statistics) and the panels are nicely titled:

# two associated dimensions (plan, region) -> a TREEMAP, with LLM-inferred field labels
qsv viz smart customer_spend.csv --dictionary infer -o spend_dashboard.html

# sales_sample's three dimensions are independent, so the auto hierarchy is skipped; force a
# SUNBURST to showcase the deeper-hierarchy chart anyway
qsv viz smart sales_sample.csv --dictionary infer --hierarchy-style sunburst -o sales_dashboard.html

--dictionary infer needs a reachable LLM endpoint; set QSV_LLM_MODEL (and QSV_LLM_BASE_URL / QSV_LLM_APIKEY as needed). Without a dictionary, the same hierarchy is still built from column statistics — only the dimension labels and semantic routing differ.

Individual chart types

# bar — revenue by region (aggregated)
qsv viz bar sales_sample.csv --x region --y revenue --agg sum -o bar.html

# bar (animated slider) — animate revenue by category over the satisfaction rating
# (each distinct value becomes a frame; Play/Pause + a scrub slider; axes pinned across frames)
qsv viz bar sales_sample.csv --x product_category --y revenue --agg sum --slider satisfaction -o bar_animated.html

# line — closing price over time
qsv viz line stock_prices.csv --x date --y close -o line.html

# scatter — units sold vs revenue
qsv viz scatter sales_sample.csv --x units_sold --y revenue -o scatter.html

# scatter (bubble) — encode numeric columns as marker size & color (continuous colorscale)
qsv viz scatter sales_sample.csv --x units_sold --y revenue --size shipping_cost --color profit_margin_pct -o bubble.html

# scatter (Gapminder bubble animation) — --series is the entity, --size the third data variable
# each region×month cell collapses to one bubble via --agg (mean by default); axes pinned, sizes scaled across all frames
qsv viz scatter regions_growth.csv --x gdp_index --y wellbeing_index --size population_m --series region --slider month_date -o gapminder.html

# scatter3d — three numeric columns in 3D; marker color by a fourth
qsv viz scatter3d sales_sample.csv --x units_sold --y revenue --z shipping_cost --color profit_margin_pct -o scatter3d.html

# histogram — distribution of unit price
qsv viz histogram sales_sample.csv --x unit_price -o histogram.html

# box — spread of revenue (true Tukey whiskers; points beyond the fences are outliers)
qsv viz box sales_sample.csv --y revenue -o box.html

# box (grouped) — revenue per region; --box-points all overlays every (jittered) point
qsv viz box sales_sample.csv --y revenue --x region --box-points all -o box_grouped.html

# violin — revenue per region as a KDE density silhouette around an inner quartile box
qsv viz violin sales_sample.csv --y revenue --x region -o violin.html

# pie (donut) — revenue share by product category
qsv viz pie sales_sample.csv --x product_category --y revenue --donut -o pie.html

# heatmap (correlation) — Pearson matrix over all numeric columns
qsv viz heatmap sales_sample.csv -o heatmap_corr.html

# scatter (correlated pair) — the strongest pair from the matrix; viz smart auto-adds this
qsv viz scatter sales_sample.csv --x discount_pct --y profit_margin_pct -o corr_pair.html

# contour — 2D density of two numeric columns (binned); viz smart uses this for big data
qsv viz contour sales_sample.csv --x units_sold --y revenue --bins 20 -o contour.html

# heatmap (pivot) — region x category grid of revenue (give --x, --y and --z)
qsv viz heatmap sales_sample.csv --x region --y product_category --z revenue -o heatmap_pivot.html

# candlestick — OHLC price action
qsv viz candlestick stock_prices.csv --x date --ohlc-open open --high high --low low --close close -o candlestick.html

# ohlc — open-high-low-close bars
qsv viz ohlc stock_prices.csv --x date --ohlc-open open --high high --low low --close close -o ohlc.html

# sankey — session funnel (duplicate source->target pairs are aggregated)
qsv viz sankey web_flows.csv --source source --target target --value sessions -o sankey.html

# radar — multi-axis brand comparison (one polygon per --series value, per-axis mean)
qsv viz radar product_ratings.csv --cols battery,camera,performance,display,value,design --series brand -o radar.html

# treemap — part-to-whole hierarchy (--cols are the levels, outer first); sized by a --value sum
qsv viz treemap customer_spend.csv --cols plan,region --value monthly_spend --agg sum -o treemap.html

# sunburst — deeper hierarchy as concentric rings; sized by row count when --value is omitted
qsv viz sunburst sales_sample.csv --cols region,product_category,payment_method -o sunburst.html

# icicle — the same hierarchy laid out rectangularly (parents left, children fanning right), so
# deep labels stay readable where a sunburst's outer ring would crowd; click to zoom into a branch
qsv viz icicle sales_sample.csv --cols region,product_category,payment_method -o icicle.html

# parcats — parallel-categories flow: each ribbon is a category combination, sized by how many rows
# share it, so co-occurrence is visible without implying a part-to-whole nesting. viz smart
# auto-picks this over a treemap/sunburst for 3-4 associated many-to-many categoricals
qsv viz parcats sales_sample.csv --cols region,product_category,payment_method -o parcats.html

# splom — scatter-plot matrix: every pairwise scatter of --cols in an N x N grid with shared axes,
# so correlation structure is legible at a glance. Standalone only — viz smart covers the same
# ground with its correlation heatmap plus the strongest-pair scatter and 3D scatter drill-downs
qsv viz splom sales_sample.csv --cols units_sold,revenue,discount_pct,profit_margin_pct -o splom.html

# map — point map on token-free OpenStreetMap tiles; color by magnitude, size by depth
qsv viz map quakes.csv --lat lat --lon lon --color magnitude --size depth_km -o map.html

# map (density) — DensityMap heatmap of the same points, on a light Carto basemap
qsv viz map quakes.csv --lat lat --lon lon --density --style carto-positron -o map_density.html

# geo — offline projection map (no tiles/token); viz smart auto-uses this for global coordinates
qsv viz geo quakes.csv --lat lat --lon lon --color magnitude --projection natural-earth -o geo.html

# geo (animated slider) — animate a point map over a column (scattergeo animates; viz map cannot)
# --slider-cumulative accumulates points as the animation plays, colored per region via --series
qsv viz geo quakes.csv --lat lat --lon lon --slider region --series region --slider-cumulative --projection natural-earth -o geo_animated.html

# choropleth — fill countries by a value, matched by ISO-3 code (also: usa-states, country-names,
# geojson-id; --map for a MapLibre basemap; --geocode to derive codes from lat/lon or place names)
qsv viz choropleth country_stats.csv --locations iso3 --value gdp_usd_tn --color-scale viridis -o choropleth.html

# choropleth (US states) — built-in state geometry on the albers-usa projection, no GeoJSON needed
qsv viz choropleth us_state_stats.csv --locations state --value renewable_electricity_pct \
    --location-mode usa-states -o choropleth_states.html

# choropleth (--map) — filled regions on a MapLibre tile basemap from a custom GeoJSON, matched by
# --feature-id-key; the view auto-centers/zooms to the GeoJSON extent. The id is under the feature's
# properties here, which is the usual real-world shape. --agg mean because density is intensive:
# the default sum would offer a meaningless share-of-total on hover.
qsv viz choropleth northeast_states.csv --locations state --value people_per_sq_mi --agg mean \
    --geojson northeast_states.geojson --feature-id-key properties.STUSAB \
    --map --style carto-positron -o choropleth_map.html

viz smart also adds a choropleth panel on its own whenever it detects lat/lon columns that reverse-geocode to two or more regions — a per-US-state fill when every point resolves to the United States, otherwise a per-country (ISO-3) fill framed to the filled-region geometries via Plotly fitbounds (so the regions are never clipped). This auto-panel needs the geocode feature (included in the prebuilt qsv/qsvpy binaries and any all_features build); a minimal viz-only build shows just the point map. Pairing it with --dictionary infer adds LLM-inferred field labels (see the Data Schematic section):

# US point map + per-US-STATE choropleth, derived purely from the lat/lon columns (no flags)
qsv viz smart us_cities.csv -o us_dashboard.html

# global geo map + per-COUNTRY choropleth, with describegpt-inferred field labels (needs a local LLM)
qsv viz smart world_cities.csv --dictionary infer -o world_dashboard.html

The smart per-country choropleth is reverse-geocoded from coordinates: each of the 1,179 cities is resolved to its sovereign country (ISO-3) and counted, so the densest countries (China, India, the US, Brazil, …) fill darkest. The six-continent bar comes from the dataset's own continent column, whose values follow the plotly.js geo scope continent vocabulary (Oceania, North America, …). world_cities.csv is GeoNames-derived (cities with population over 500,000); elevation_m is real, while avg_annual_temp_c is a rough synthetic proxy (a latitude + elevation-lapse model — no coastal, monsoon, or ocean-current effects), so treat it as illustrative, not measured.

Note: --ohlc-open is spelled out (not --open) because --open already means "open the result in a browser".

Data sources & licensing

These sample datasets exist to demonstrate qsv viz; they are not part of the qsv binary. Where a source requires attribution, the credit below is that attribution — please carry it along if you reuse the file. See THIRD_PARTY_NOTICES.md for the full third-party inventory.

FileSourceLicense
world_cities.csvDerived from GeoNames (cities15000 + countryInfo) by gen_world_cities.py. elevation_m is GeoNames data; avg_annual_temp_c is synthesized by qsv.CC BY 4.0attribution required
allegheny_dog_licenses.csv, allegheny_zip_boundaries.geojsonAllegheny County dog licenses and the county's 125 zip-code boundaries, via the Western Pennsylvania Regional Data CenterSee the WPRDC dataset page
nyc_311.csv, nyc_capital_projects.csv, nyc_neighborhoods.geojsonSamples of NYC Open Data; the GeoJSON is its 188 neighborhood tabulation areasSee the NYC Open Data terms of use
cms_medicare_providers.csvSample of CMS provider dataUS Government work
northeast_states.csv, northeast_states.geojsonBuilt by gen_northeast_states.py from US Census Bureau TIGERweb state boundaries (server-simplified, maxAllowableOffset=0.002) and the Census Vintage 2024 state population estimates. people_per_sq_mi is derived (population ÷ TIGER AREALAND).US Government work — public domain (17 U.S.C. §105); attribution not required, credited for auditability
colombia_departments.geojsonColombia's 33 ADM1 departments from geoBoundaries (William & Mary geoLab) — identified from the file's own shapeGroup/shapeISO/shapeID/shapeType: ADM1 property schemaCC BY 4.0attribution required
japan_prefectures.geojsonJapan's 47 first-level administrative regions (prefectures) from simplemaps' free GIS maps — the file's id/name properties carry simplemaps' own JP01JP47 scheme, and each feature self-declares source: https://simplemaps.comCC BY 4.0attribution required
Synthetic samples (sales_sample.csv, seismic_events.csv, web_flows.csv, …)Generated for these examplesMIT, with qsv