Token导航 LogoToken导航

Agent Skill · 研究检索

create-ixmap(创建 ixmap)

create-ixmap 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

create-ixmap 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描有提醒项,按安装量在研究检索类的 73,675 个 Skill 里排第 58,786。

总安装 404周安装 17GitHub Stars 公开资料未说明下载 141核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:create-ixmap(创建 ixmap)
来源仓库:https://github.com/gjrichter/ixmaps-skills
仓库路径:skills/create-ixmap
安装命令:
npx skills add https://github.com/gjrichter/ixmaps-skills --skill create-ixmap
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/gjrichter/ixmaps-skills --skill create-ixmap

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

用于查找、检索和筛选相关信息,适合根据关键词或任务场景快速定位候选结果。适用于 Codex、Claude、Cursor、Gemini CLI 等宿主环境,支持地理数据可视化与交互映射。通过命令行安装,需结合来源仓库文档核验具体用法,确保 ixmaps.Map 实例正确绑定。

安装前建议确认权限范围,避免触发未授权的联网或文件操作。create-ixmap 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/create-ixmap

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/gjrichter/ixmaps-skills --skill create-ixmap;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

create-ixmap 怎么安装?

在终端执行 npx skills add https://github.com/gjrichter/ixmaps-skills --skill create-ixmap。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

create-ixmap 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

create-ixmap 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

create-ixmap 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

create-ixmap 用的人多吗?

公开数据是累计安装 404 次,近一周安装 17 次,下载 141 次。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

create-ixmap 安全吗?

第三方扫描有提醒项,说明存在需要人工确认的行为。安装前请打开源码看清它读了什么、联网到哪里。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
create-ixmap

创建 ixmap · 本页

404—需要联网未明确有提醒

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Create ixMap Skill

Creates complete HTML files with interactive ixMaps visualizations for geographic data.

⚠️ CRITICAL RULES (Never Skip)

  1. ALWAYS assign ixmaps.Map() to const — discarded instance = silent failure const myMap = ixmaps.Map("map", {...}); // ✅ ixmaps.Map("map", {...}); // ❌ instance lost
  2. ALWAYS include .binding() with geo and value
  3. ALWAYS include showdata: "true" in .style()
  4. ALWAYS include .meta() with tooltip (default: {tooltip: "{{theme.item.chart}}{{theme.item.data}}"})

- Also include name whenever you plan to use changeThemeStyle at runtime (see rule 21)

  1. NEVER use .tooltip() — doesn't exist
  2. NEVER combine CHART and CHOROPLETH in one type string — mutually exclusive
  3. NEVER use |EXACT classification — deprecated; use CATEGORICAL
  4. NEVER use map as variable name — conflicts with internals; use myMap 8a. NEVER use reserved HTML element IDs — ixMaps owns loading-div, tooltip, contextmenu. Using them causes visible artifacts (a white box stuck on the map). Use app-loading or any other non-conflicting name for your own overlays.
  5. NEVER use opacity in .style() — use fillopacity
  6. NEVER use fillcolor — use colorscheme: ["#hex"]
  7. NEVER add .legend("string") unless user explicitly requests it — destroys the default color legend
  8. ALWAYS use CDN https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-flat@master/ixmaps.js

- data.js (https://cdn.jsdelivr.net/gh/gjrichter/data.js@master/data.js) is already loaded by ixmaps — Data.* functions are available inside query: and process: callbacks without any extra <script> tag - Only include the data.js CDN explicitly when you need Data.* functions *outside* ixmaps theme realization (e.g. pre-processing data in your own <script> block before defining layers)

  1. NEVER use info from ixmaps.ca or ixmaps.com — only github.com/gjrichter/ixmaps-flat
  2. ONE .data() per layer — never chain two .data() calls on the same layer
  3. SAME LAYER NAME for all layers sharing geometry — #1 cause of silent failures:

- ✅ myMap.layer("regions").type("FEATURE") → myMap.layer("regions").type("CHOROPLETH") - ❌ myMap.layer("regions").type("FEATURE") → myMap.layer("flows").type("CHOROPLETH") — silently broken

  1. NO FEATURE on overlay layers — base layer gets FEATURE; choropleth/chart overlays do not:

- ✅ myMap.layer("x").type("FEATURE") → myMap.layer("x").type("CHOROPLETH|CATEGORICAL") - ❌ myMap.layer("x").type("FEATURE") → myMap.layer("x").type("FEATURE|CHOROPLETH|CATEGORICAL")

  1. objectscaling: "dynamic" requires normalSizeScale — set to map scale denominator: zoom 4→30M · 5→15M · 6→8M · 8→2M · 10→500k · 12→100k
  2. lookup goes in .binding(), not in .data()
  3. values: for CATEGORICAL must be strings — ixMaps bug: numeric values silently ignored
  4. To make a fill invisible use colorscheme: ["none"] — NOT fillopacity: 0 (causes errors)
  5. FEATURE layer: colorscheme controls line color — for FEATURE type layers, colorscheme sets the line/stroke color, NOT the fill. linecolor is overridden by colorscheme. Always drive line color via colorscheme. colorscheme: "none" silences lines entirely (renders invisible).
  6. changeThemeStyle requires name in .meta() — it finds themes by name, NOT by the string in myMap.layer("name"). Without name, calls silently have no effect: .meta({name: "punti", tooltip: "..."}) // ✅ — changeThemeStyle("punti",...) will work.meta({tooltip: "..."}) // ❌ — theme is invisible to changeThemeStyle
  7. hideTheme/showTheme also resolve themes by name in .meta() — same rule as changeThemeStyle. Once name is set, use ixmaps.hideTheme(name) / ixmaps.showTheme(name) for layer visibility. CSS injection ([id*=":name:"] {display: none!important}) remains a reliable fallback if hideTheme behaves unexpectedly for a given layer type.

Choosing Visualization Type

Is your data...

├─ Points (lat/lon)?
│  ├─ Just locations?                    → CHART|DOT
│  ├─ Colored by category (legend-selectable)? → CHART|BUBBLE|CATEGORICAL  ⚠️ NOT DOT|CATEGORICAL
│  ├─ Sized by value?                    → CHART|BUBBLE|SIZE|VALUES
│  ├─ Density heatmap (circles)?         → CHART|BUBBLE|SIZE|AGGREGATE  + gridwidth:"5px"
│  ├─ Density heatmap (squares)?         → CHART|SYMBOL|GRIDSIZE|AGGREGATE|RECT|SUM|DOPACITY|VALUES  + symbols:["square"] + gridwidth:"80px"
│  ├─ Sparklines per grid cell?          → CHART|SYMBOL|PLOT|LINES  (see Sparklines below)
│  ├─ Flows origin→destination?          → CHART|VECTOR|BEZIER|POINTER
│  ├─ Multi-value per point?             → CHART|SYMBOL|SEQUENCE  (|STAR for 5+ categories)
│  └─ Stacked/grouped bars per location? → CHART|BAR|STACKED  (add |SIZE|GRID|BOX|VALUES for full display)
│     gridx:N in .style() = values per bar group (gridx:2 → 2 segments per bar; gridx:3 → 3 separate bars)
│
└─ Polygons (GeoJSON/TopoJSON)?
   ├─ Boundaries only?                   → FEATURE
   ├─ Colored by data (geometry+data)?   → FEATURE|CHOROPLETH  (|QUANTILE | |EQUIDISTANT | |CATEGORICAL)
   └─ Data joined to pre-loaded geometry?→ CHOROPLETH only — NEVER FEATURE|CHOROPLETH

Key type modifiers:

  • |GLOW — glow effect on any CHART type
  • |DOPACITYMAX — dynamic opacity (high values prominent); add alpha: "field" to .binding()
  • |DOPACITYMINMAX — dynamic opacity (extremes prominent)
  • |CATEGORICAL — discrete category coloring; values: array in style maps to colorscheme in order
  • |SILENT — excludes layer from legend, statistics and suppresses tooltips on its items
  • |NOLEGEND — excludes layer from legend only (tooltips still work)
  • |NOOUTLIER — removes extreme outliers from classification calculations
  • |ZEROISNOTVALUE — suppresses rendering where value ≤ 0 (useful for sparse/incomplete time series)
  • |NOSCALE — disables dynamic zoom scaling; flows/symbols stay constant size regardless of zoom
  • |GRADIENT — gradient color along flow lines (origin color → destination color); use with CHART|VECTOR|BEZIER — gradient must be defined via linecolor: ["#from","#to"] array, NOT colorscheme
  • |CLIPTOGEOBOUNDS — clips chart rendering to the containing polygon boundary
  • |DOMINANT|PERCENTOFMEAN — colors by which of multiple piped fields is above-mean dominant; useful for showing "winner" category per region
  • |DTEXT — makes VALUES-generated text labels on CHOROPLETH themes properly sized (always pair with |VALUES on choropleth layers that show value labels)
  • |SMOOTH — smoothing interpolation on sparkline curves
  • |SORT / |SORT|DOWN — sort sparkline categories ascending / descending
  • |TEXTLEGEND — renders category labels directly on chart symbols instead of in the legend box
  • |TEXTONLY — text labels only, no chart symbol (combine with CHART|LABEL|VALUES|FIXSIZE|NOLEGEND)

Aggregation modifiers (replace the value of each cell with the aggregate):

ModifierComputes
SUMSum of all values in cell
COUNTCount of rows in cell
MEANArithmetic mean
MINMinimum value
MAXMaximum value

Classification methods (used with CHOROPLETH and CHART):

MethodDescription
EQUIDISTANTEqual-width intervals across the data range
QUANTILEEqual-count intervals — each class has the same number of features
HEADTAILHead/tail breaks — iteratively splits at the mean; best for heavy-tailed distributions
NATURALJenks natural breaks — minimises within-class variance
LOGLogarithmic intervals — useful when values span several orders of magnitude

VECTOR sub-modifiers:

  • |DASH — animated flowing dashes along flow direction (combine freely with BEZIER|POINTER|FADEIN)
  • |GRADIENT — gradient color from origin to destination along each flow line

Deprecated modifiers — do NOT use:

  • SIZEP1 → use SIZE + sizepow: 1 in .style() instead
  • EXACT → use CATEGORICAL instead
For full type-string reference and all modifiers → API_REFERENCE.md § Visualization Types

Workflow

  1. Parse the user's request: data source, visualization goal, styling preferences
  2. Ask if key info is missing (data format? geographic scope?)
  3. Choose template:

- template-points.html — CSV/JSON with lat/lon - template-geojson.html — GeoJSON/TopoJSON - template-multi-layer.html — multiple layers with join - template.html — general purpose

  1. Write the HTML file
  2. Validate before writing:

- const myMap = ixmaps.Map(...) — instance stored - .binding() has geo + value - .style() has showdata: "true" - .meta() present with tooltip - If objectscaling:"dynamic" → normalSizeScale set - Start with scale: 1 — let user request size adjustments

  1. Confirm file created; explain what it shows; offer to enhance

Defaults

SettingDefault
filenameixmap.html
mapType"VT_TONER_LITE" ← always use unless user asks otherwise
center{lat: 42.5, lng: 12.5} (Italy)
zoom6
colorscheme["#0066cc"]
basemapopacity0.6
flushChartDraw1000000
flushPaintShape*(not set)* — set to 1000000 when rendering large polygon datasets (municipalities, communes) to avoid rendering hangs
zoomAnimationtrue — smooth zoom transitions; set false to disable
toolstrue

Valid basemaps (case-sensitive): "VT_TONER_LITE" · "white" · "CartoDB - Dark matter" · "CartoDB - Positron" · "Stamen Terrain" · "OpenStreetMap - Osmarenderer" ❌ NOT: "OpenStreetMap" · "OSM" · "CartoDB Positron" → See MAP_TYPES_GUIDE.md for full list


Map Init Pattern

⚠️ Scrollbar pitfall — never use width: 100vw; height: 100vh on the map <div>. When a scrollbar appears, vw/vh exceed the viewport and trigger a feedback loop. Always use: ``css html, body { width: 100%; height: 100%; overflow: hidden; } #map { width: 100%; height: 100%; } ``
const myMap = ixmaps.Map("map", {
    mapType: "VT_TONER_LITE",
    mode:    "info",
    legend:  "closed",   // or "open"
    tools:   true
})
.view({ center: { lat: 42.5, lng: 12.5 }, zoom: 6 })
.options({
    objectscaling:   "dynamic",
    normalSizeScale: "8000000",   // match to zoom (zoom6≈8M, zoom12≈100k)
    basemapopacity:  0.6,
    flushChartDraw:  1000000
});

Lambert projection (Eurostat style)

var myMap = ixmaps.Map("map", {
  mapType:       "white",        // blank tile layer
  mapProjection: "lambert",      // Lambert Azimuthal Equal-Area — EPSG:3035
  mode:          "pan",
  legend:        "closed",
  tools:         false
})
.view([53.4, 16.9], 3.7)        // ⚠️ ARRAY syntax [lat, lng], zoom — object {center,zoom} does NOT work with projections
.options({ basemapopacity: 0, flushChartDraw: 1000000 });
  • Set sea/background color via CSS on the #map div: background: #c6daea
  • basemapopacity: 0 hides tile layer entirely
  • Add a graticule layer before countries (see Graticule below)

Graticule (world grid lines)

(function() {
  var step = 10, features = [];
  for (var lon = -180; lon <= 180; lon += step) {
    var coords = [];
    for (var lat = -90; lat <= 90; lat += 2) coords.push([lon, lat]);
    features.push({ type:"Feature", geometry:{ type:"LineString", coordinates:coords }, properties:{} });
  }
  for (var lat = -80; lat <= 80; lat += step) {
    var coords = [];
    for (var lon = -180; lon <= 180; lon += 2) coords.push([lon, lat]);
    features.push({ type:"Feature", geometry:{ type:"LineString", coordinates:coords }, properties:{} });
  }
  myMap.layer("graticule")
    .data({ obj: { type:"FeatureCollection", features:features }, type: "geojson" })
    .binding({ geo: "geometry" })
    .type("FEATURE|SILENT")
    .style({ colorscheme: "#7aaabb", linewidth: 0.6, fillopacity: 0 })
    .define();
})();

Intermediate points every 2° ensure smooth curves in Lambert projection. Define graticule before the countries layer so it renders underneath.


Layer chain (order matters):

myMap.layer("name")
    .data({ url: "…", type: "csv" })   // OR obj: myArray
    .binding({ geo: "lat|lon", value: "fieldname", title: "label" })
    .filter('WHERE field == "value"')   // optional; use AND/OR not && /||
    .type("CHART|BUBBLE|SIZE|VALUES")
    .style({ colorscheme: ["#0066cc"], fillopacity: 0.7, showdata: "true" })
    .meta({ tooltip: "{{label}}: {{fieldname}}" })
    .title("Legend label")
    .define();
Full .options() / .style() property reference → API_REFERENCE.md § Map Constructor and § Style Properties

Tooltip Mustache Reference

Tooltips in .meta({tooltip: "..."}) use {{…}} placeholders. Two prefixes control formatting:

SyntaxBehaviour
{{fieldname}}ixmaps-formatted value — may apply number formatting, units, rounding
{{raw.fieldname}}Raw unformatted value — bypasses all ixmaps formatting; use this when you want pre-formatted strings (e.g. "1.234.567" from .toLocaleString()) or exact string values
{{theme.item.chart}}Renders the built-in chart SVG/HTML for this item
{{theme.item.data}}Renders the built-in data table for this item

raw. is the escape hatch — whenever ixmaps mangles a value (reformats numbers, truncates strings, adds units), use {{raw.field}} to get the original data value unchanged.

For fields not in the primary value binding, list them in datafields in .style() to make them available:

.style({ datafields: ["field1", "field2"], showdata: "true" })
.meta({ tooltip: "{{raw.field1}} — {{field2}}" })

Geometry Sources

geo: "geometry" for GeoJSON point data — works correctly with all CHART types. ixmaps extracts full-precision coordinates directly from Point.coordinates[lon,lat]. The .type() call (CHART|DOT, CHART|BUBBLE, etc.) controls the renderer — NOT the geo binding. Use geo: "geometry" when source GeoJSON has Point geometry (preferred over property lat/lon fields which may be truncated). Only use geo: "lat|lon" when the data has separate lat/lon columns (CSV, non-geometry JSON).

World countries (GISCO — preferred over world-atlas)

.data({ url: "https://gisco-services.ec.europa.eu/distribution/v2/countries/topojson/CNTR_RG_60M_2020_4326.json", type: "topojson" })
.binding({ geo: "geometry", id: "CNTR_ID", title: "NAME_ENGL" })
// ⚠️ Join field is CNTR_ID (ISO-2) — NOT CNTR_CODE

Scales: 60M (default/world) · 20M · 10M · 3M · 1M (country zoom)

Germany municipalities (LAU 2021)

.data({ url: "https://cdn.jsdelivr.net/gh/gjrichter/geo@028b3fe/lau/germany_lau_2021_4326.topojson", type: "topojson" })
.binding({ geo: "geometry", id: "LAU_ID", title: "LAU_NAME" })
// LAU_ID = 8-digit AGS · LAU_NAME = name · POP_DENS_2021 = density (useful for alpha/DOPACITYMAX)

NUTS1 Germany

.data({ url: "https://gisco-services.ec.europa.eu/distribution/v2/nuts/topojson/NUTS_RG_60M_2021_4326_LEVL_1.json", type: "topojson" })
.filter('WHERE CNTR_CODE == "DE"')
.binding({ geo: "geometry", id: "NUTS_ID", title: "NUTS_NAME" })
// NUTS_ID examples: "DE1", "DEA"  (CNTR_CODE works for NUTS, unlike country data which uses CNTR_ID)

Italy geometry sources (gjrichter/geo)

Municipalities (comuni) — ISTAT, ~8 000 polygons, 500m simplified:

.data({ url: "https://raw.githubusercontent.com/gjrichter/geo/main/italy/boundaries/italy_istat_municipalities_4326_500m.topojson", type: "topojson" })
.binding({ geo: "geometry", id: "com_istat_code", title: "name" })
// Join field: com_istat_code (numeric) — matches cod_istat in ISTAT/ondata CSVs
// Useful properties: com_istat_code, name, prov_istat_code, reg_istat_code (cod_reg for region filter)

⚠️ Use flushPaintShape: 1000000 in .options() when rendering all 8 000 polygons to avoid hangs.

⚠️ Local file:// URLs are blocked by browser CORS — always use CDN or inline obj: Full geometry sources list → API_REFERENCE.md § Data Configuration

Multi-Layer Join Pattern

When joining external data to geometry (e.g. TopoJSON + CSV statistics):

// Step 1 — FEATURE base (geometry + id field for join)
myMap.layer("regions")
    .data({ url: "regions.topojson", type: "topojson" })
    .binding({ geo: "geometry", id: "reg_code", title: "reg_name" })
    .type("FEATURE")
    .style({ colorscheme: ["#ccc"], fillopacity: 0.1, linecolor: "#666", linewidth: 0.5, showdata: "true" })
    .define();

// Step 2 — CHOROPLETH overlay (SAME layer name, NO FEATURE, lookup joins to id)
myMap.layer("regions")
    .data({ url: "data.csv", type: "csv" })
    .binding({ lookup: "csv_code_col", value: "metric" })
    .type("CHOROPLETH|QUANTILE")
    .style({ colorscheme: ["#eee", "#00468b"], fillopacity: 0.75, showdata: "true" })
    .meta({ tooltip: "{{reg_name}}: {{metric}}" })
    .define();

Critical: id values in geometry must match lookup values in CSV exactly (case-sensitive). Always inspect both sources to confirm field names before writing the join.


Sparklines (CHART|SYMBOL|PLOT|LINES)

Two distinct patterns depending on data shape:

Pattern A — single column, year as category (raw events)

.binding({ geo: "lat|lon", value: "year" })   // year field = categorical x-axis
.type("CHART|SYMBOL|PLOT|LINES|AREA|FADE|LASTARROW|NOCLIP|GRIDSIZE|CATEGORICAL|AGGREGATE|RECT|SUM|FIXSIZE")
.style({
  gridwidth: "100px", normalsizevalue: "30", markersize: 2,
  colorscheme: ["#00e5ff"], fillopacity: 0.5,
  values: ["2020","2021","2022","2023"],  // ordered x-axis categories (also controls sort)
  showdata: "true"
})
// CATEGORICAL+AGGREGATE+RECT+SUM = aggregation semantics (NOT style)
// AREA|FADE|LASTARROW|FIXSIZE = visual style only
// FIXSIZE: all sparks same size; normalsizevalue controls chart scale (larger = smaller sparks)
// markersize: controls LASTARROW arrow head size (default ~8; use 1–3 for smaller arrows)
// ⚠️ normalsizevalue does NOT control arrow size — use markersize for that
// LASTARROW = arrow marker on last point  |  LASTPOP = dot/pop marker on last point (use one or the other)
// MAX/MIN/MEAN/COUNT/SUM = aggregation modifiers — compute cell aggregate value; NOT sparkline visual markers

BOX|GRID and XAXIS — only add on explicit user request:

  • Default (no BOX|GRID): sparkline appears as a lightweight curve/arrow on the map; xaxis/chart still visible in tooltip via {{theme.item.chart}}
  • BOX|GRID|XAXIS + label:[]+xaxis:[] in style: renders grid boxes + x-axis labels ON the map — heavier, less performant; use only when user wants to see the grid/axes directly on the map
  • BOX alone (without GRID): adds a background box; can be combined with TITLE or BOTTOMTITLE for chart titles; scale-dependent via boxupper/boxlower and titleupper/titlelower style params
// Only when user explicitly wants grid + axis labels on map:
.type("...NOCLIP|BOX|GRID|GRIDSIZE|XAXIS|CATEGORICAL|AGGREGATE|RECT|SUM|FIXSIZE")
.style({
  values: ["2020","2021","2022","2023"],
  label:  ["2020","2021","2022","2023"],
  xaxis:  ["2020","2021","2022","2023"],
})

Pattern B — multiple pre-aggregated columns

.binding({ geo: "lat|lon", value: "val2020|val2021|val2022|val2023" })  // chain columns
.type("CHART|SYMBOL|PLOT|LINES|AREA|FADE|LASTARROW|NOCLIP|GRIDSIZE|FIXSIZE")
// No CATEGORICAL or SUM — data already aggregated
Full sparkline reference, FIXSIZE/normalsizevalue details, point-anchored variant → API_REFERENCE.md § CHART|SYMBOL|PLOT|LINES

Animated / Timeseries Maps

Method A — myMap.layer(theme, "direct") (preferred)

// ixmaps.layer() (global) builds theme WITHOUT adding to map
// myMap.layer(theme, "direct") = smart upsert: add on first call, replace on subsequent
function showYear(year) {
    const theme = ixmaps.layer("countries")
        .data({ obj: yearData[year], type: "json" })
        .binding({ geo: "lat|lon", value: "metric" })
        .type("CHART|BUBBLE|SIZE|VALUES")
        .style({ colorscheme: ["#0066cc"], fillopacity: 0.7, showdata: "true" })
        .meta({ name: "myTheme", tooltip: "{{label}}: {{metric}}" })
        .define();           // returns theme object, does NOT add to map
    myMap.layer(theme, "direct");   // smart upsert — no tracking needed
}
showYear("2023");

Method B — explicit addTheme / replaceTheme

let activeTheme = null;
let mapInstance = null;
myMap.then(map => { mapInstance = map; showYear("2023"); });

function showYear(year) {
    if (!mapInstance) return;
    const theme = ixmaps.layer("countries")
        .data({ obj: yearData[year], type: "json" })
        .binding({ geo: "lat|lon", value: "metric" })
        .type("CHART|BUBBLE|SIZE|VALUES")
        .style({ colorscheme: ["#0066cc"], fillopacity: 0.7, showdata: "true" })
        .meta({ name: "myTheme", tooltip: "{{label}}: {{metric}}" })
        .define();
    if (activeTheme) mapInstance.replaceTheme("myTheme", theme, "direct");
    else             mapInstance.addTheme("myTheme", theme, "direct");
    activeTheme = theme;
}

Key: replaceTheme avoids flicker vs remove+add. Theme name in .meta() is the upsert key.

Time slider (timefield in .binding()), setThemeTimeFrame() → API_REFERENCE.md § Time Slider

Key Style Properties (quick ref)

PropertyNotes
colorschemeArray of hex colors. ["100","tableau"] for auto-palette
fillopacity0–1. NEVER use opacity
linecolor / linewidthNEVER strokecolor / strokewidth; linecolor accepts a single string or an array ["#c1","#c2"] — array form required for `VECTOR\GRADIENT`
scaleUniform size multiplier (start at 1)
normalsizevalueData value that maps to "normal" display size. Higher = SMALLER bubbles — a larger reference value means most real data values fall below it, so bubbles render smaller. E.g. "1000" → smaller bubbles than "300".
gridwidthGrid cell size for aggregate layers (e.g. "5px")
rangecentervalueDiverging center; requires EVEN number of colors
rangesExplicit class breaks (n+1 values for n colors)
valuesCategory list for CATEGORICAL (must be strings)
alignChart anchor: "left" "right" "top" "bottom" "above" "below"
sizepowPower curve for size scaling — 2 = quadratic, exaggerates contrast between small and large values
rotationRotate chart symbol in degrees (e.g. 35 for a tilted arrow)
rangescaleScale factor applied after range computation
aggregationfieldField used as aggregation key when AGGREGATE is set
titlefieldField used as chart title label (overrides binding title)
datafieldsArray of extra fields carried through to tooltip: ["field1","field2"] — access as {{raw.field1}}
textscaleScale factor for label text rendered on the chart
boxupper / boxlowerScale-dependent box visibility threshold, e.g. "1:250000" — box shown only when map scale ≤ 1:250k
valuesupper / valueslowerScale-dependent value label visibility threshold
valuedecimalsDecimal places for rendered value labels
minvaluesizeMinimum pixel size below which no chart symbol is drawn
unitsString appended to rendered value labels, e.g. "%", "€", "km"
sizefieldData column that drives symbol SIZE independently from the value (color) field — use with CATEGORICAL to combine category color + numeric size on one layer
dopacitypowPower curve exponent for DOPACITY opacity mapping (default ≈ 1; 2 = quadratic, exaggerates contrast)
dopacityscaleMultiplier applied after opacity calculation — stretches the opacity range
gridwidthpxGrid cell width in pixels; supports "factor" mode in changeThemeStyle for runtime zoom-scaling

Trees (street-level) sizing baseline with |GLOW:

  • Use this as a reliable starting point for urban tree inventories (diameter in cm):

- objectscaling: "dynamic" - normalSizeScale: "5000" (street-detail zoom reference) - normalsizevalue: "220" (higher value keeps bubbles controlled) - scale: 0.32 (reduce apparent size added by |GLOW)

  • Rule of thumb: with |GLOW, start with a smaller scale than non-glow bubbles.
Complete style properties, dynamic opacity, diverging scales, categorical color binding → API_REFERENCE.md § Style Properties

Runtime Controls (Filters & Layer Toggles)

Use these patterns when you need interactive UI controls (checkboxes, dropdowns) that modify the map after it's loaded.

Filtering data across all layers — changeThemeStyle

changeThemeStyle(themeName, styleString, mode) modifies a live layer property and triggers a re-render. For aggregate layers (grid counts, sparklines) it also re-aggregates — cells recount correctly with only the filtered rows.

Mode values:

ModeBehaviour
"set"Replace property with the given value (default)
"remove"Delete the property entirely
"factor"Multiply the current numeric value by the given factor (e.g. "gridwidthpx:1.1" → 10% larger)
`"set\silent"`Set value WITHOUT triggering a redraw (use for low-priority zoom tweaks)

Prerequisites:

  1. Every layer that should respond must have name in its .meta() (see Rule 21)
  2. Must call via the Promise API — myMap.then(map =>...) — NOT the fluent chain
function applyFilter(activeValues) {
  // activeValues = array of selected values, e.g. ["M", "F"]
  const szFilter = (activeValues.length === totalCount)
    ? null   // all selected → remove filter
    : 'WHERE fieldName in (' + activeValues.join(',') + ')';

  myMap.then(function(map) {
    ['layerNameA', 'layerNameB', 'layerNameC'].forEach(function(id) {
      if (szFilter) {
        map.changeThemeStyle(id, 'filter:' + szFilter, 'set');
      } else {
        map.changeThemeStyle(id, 'filter', 'remove');
      }
    });
  });
}
⚠️ ixmaps.map().changeThemeStyle() returns {szMap: null} and silently does nothing — that form cannot find the live map instance.

Region selector with zoom navigation

Use a <select> dropdown to filter all theme layers to a single geographic region and pan/zoom to it. The map needs to be declared as var myMap (not const) in outer scope so both buildMap() and changeRegion() can access it.

Key facts:

  • Filter uses single-value equality: WHERE field = value
  • Empty <option value=""> is the "show all" sentinel — triggers filter removal
  • Navigation uses myMap.view() called outside .then() — it is safe to call on the fluent chain after init
  • myMap.view() only pans/zooms; it does not reset layers

REGION_VIEWS lookup table:

const REGION_VIEWS = {
    "":  { lat: 42.5, lng: 12.5, zoom: 6 },   // full extent
    "1": { lat: 44.9, lng:  7.9, zoom: 8 },
    // ... one entry per region code
};

changeRegion function:

var myMap;   // outer scope — shared by buildMap() and changeRegion()

function changeRegion(code) {
    var THEME_NAMES = ["themeA", "themeB", "themeC"];  // all named themes that should filter
    var filterStr = code ? "WHERE regionField = " + code : null;
    var v = REGION_VIEWS[code] || REGION_VIEWS[""];

    myMap.then(function(m) {
        THEME_NAMES.forEach(function(name) {
            if (filterStr) {
                m.changeThemeStyle(name, "filter:" + filterStr, "set");
            } else {
                m.changeThemeStyle(name, "filter", "remove");
            }
        });
    });

    myMap.view({ center: { lat: v.lat, lng: v.lng }, zoom: v.zoom });
}

function buildMap() {
    myMap = ixmaps.Map("map", { ... });
    // ...layers...
}

Overlay selector UI — centered over the map, no background bar, map interaction passes through the wrapper:

<!-- CSS -->
#region-bar {
    position: absolute;
    top: 20px; left: 50%; transform: translateX(-50%);
    z-index: 1001;
    display: flex; align-items: center; gap: 8px;
    pointer-events: none;        /* wrapper is click-through */
}
#region-bar label {
    color: #333; font-size: 0.78rem;
    pointer-events: none;
}
#region-select {
    background: rgba(20,20,20,0.72);
    border: 1px solid rgba(255,255,255,0.22);
    border-radius: 6px; color: #f0f0f0;
    padding: 5px 10px; cursor: pointer;
    pointer-events: all;         /* select itself is interactive */
}
#region-select option { background: #1e1e1e; color: #f0f0f0; }

<!-- HTML (inside the 1024px container div, above the map div) -->
<div id="region-bar">
    <label for="region-select">Regione:</label>
    <select id="region-select" onchange="changeRegion(this.value)">
        <option value="">— Tutta Italia —</option>
        <option value="1">Piemonte</option>
        <!-- ... -->
    </select>
</div>
⚠️ Always include <option value=""> as the first option — it is the "show all" state that triggers filter remove. Presetting a region on load via selected means removing the initial .filter() from layer definitions; conversely, if a region is pre-filtered in .filter(), set selected on the matching option so the UI and the data stay in sync.

Toggling layer visibility — hideTheme / showTheme

hideTheme and showTheme resolve themes by name in .meta() — just like changeThemeStyle. Once name is set on every layer, the standard calls work:

ixmaps.hideTheme("grid");       // hides layer named "grid"
ixmaps.showTheme("grid");       // shows it again
// Usage: <input type="checkbox" onchange="this.checked ? ixmaps.showTheme('grid') : ixmaps.hideTheme('grid')">

Initially hidden layer — add visible: false to .style() — do NOT call hideTheme from myMap.then():

myMap.layer("danno")
  .binding({ ... })
  .type("CHART|BUBBLE|CATEGORICAL|GLOW")
  .style({
    colorscheme: [...],
    values:      [...],
    visible:     false    // ✅ layer starts hidden; toggle via showTheme/hideTheme at runtime
  })
  .define();
// ❌ WRONG: myMap.then(function() { ixmaps.hideTheme('danno'); });  — unreliable timing

CSS injection fallback — if hideTheme behaves unexpectedly for a layer type, inject/remove a style rule instead:

function toggleLayer(id, show) {
  const styleId = 'hide-' + id;
  if (!show) {
    if (!document.getElementById(styleId)) {
      const s = document.createElement('style');
      s.id = styleId;
      s.textContent = '[id*=":' + id + ':"] { display: none !important; }';
      document.head.appendChild(s);
    }
  } else {
    document.getElementById(styleId)?.remove();
  }
}

The category filter and layer-visibility toggle work independently and can be freely combined.

Isolating categorical classes — markThemeClass / unmarkThemeClass

Use these to isolate one or more categorical classes in a CATEGORICAL layer. Marked classes stay visible; all others are hidden. When zero classes are marked, every class is shown again — no reset call needed.

// Mark (isolate) class at index n — index = position in the `values:` array (0-based)
ixmaps.markThemeClass("themeName", n);

// Remove the isolation for class n
ixmaps.unmarkThemeClass("themeName", n);

Clickable legend pattern — toggle isolation on click, track state in a Set:

const markedClasses = new Set();

function toggleClass(classIdx) {
    if (markedClasses.has(classIdx)) {
        markedClasses.delete(classIdx);
        ixmaps.unmarkThemeClass("myLayer", classIdx);
    } else {
        markedClasses.add(classIdx);
        ixmaps.markThemeClass("myLayer", classIdx);
    }
    // update legend UI: dim items not in markedClasses (only when set is non-empty)
    document.querySelectorAll(".leg-item").forEach(el => {
        const c = parseInt(el.dataset.class, 10);
        el.classList.toggle("off", markedClasses.size > 0 && !markedClasses.has(c));
    });
}
// HTML: <div class="leg-item" data-class="0" onclick="toggleClass(0)">…</div>
Multiple classes can be marked simultaneously — all marked classes show together. The themeName must match name in .meta() (same rule as changeThemeStyle).

Reacting to zoom / pan — .on() events

Use .on(events, handler) to subscribe to view events. Multiple space-separated events are accepted in one call.

View events
EventFires when
zoomendZoom level changed
moveendMap panned without zoom change
viewchange (alias zoompan)Any zoom or pan

Handler receives {nZoom, zoomChanged, panChanged, szMap}.

Typical zoom-adaptive pattern — debounce to avoid firing on every intermediate step:

var _zoomTimer = null;
myMap.on("zoomend moveend", function() {
    clearTimeout(_zoomTimer);
    _zoomTimer = setTimeout(function() {
        var z = ixmaps.getZoom();   // global, no .then() needed
        myMap.then(function(m) {
            m.setBasemapOpacity(Math.max(0, Math.min(0.8, (z - 9) / 3)), "absolute");
            m.changeThemeStyle("layerName", "minvaluesize:" + (z > 10 ? 1 : 15), "set");
        });
    }, 400);
});
Item (feature) events
EventFires whenHandler receives
mouseover / itemoverPointer enters a feature{szId, id, theme, szMap}
mouseout / itemoutPointer leaves a featuresame
click / itemclickFeature clickedsame

szId = full compound id "themeId::itemKey" · id = item key only · theme = layer id

Lifecycle events
EventFires when
ready / mapreadySVG engine fully loaded
layerdraw / drawthemeA layer finishes drawing
layeradd / newthemeA layer is created
layerremove / removethemeA layer is removed
myMap
  .on("ready",     function()  { hideSpinner(); })
  .on("layerdraw", function(e) { console.log("drawn:", e.id); })
  .on("click",     function(e) { showDetail(e.id); })
  .on("mouseover", function(e) { highlight(e.id); })
  .on("mouseout",  function()  { clearHighlight(); });

Inside handlers — call ixmaps.getZoom() / ixmaps.getCenter() directly (no Promise); use myMap.then(m =>...) only when you need to call m.changeThemeStyle() or m.setBasemapOpacity().

getBounds() note — returns a flat 4-element array [swLat, swLng, neLat, neLng], NOT a Leaflet LatLngBounds object. Always guard: if (!bounds || bounds.length!== 4) return;

Legacy hook — ixmaps.htmlgui_onZoomAndPan — still works; prefer .on() for new code:

ixmaps.htmlgui_onZoomAndPan = function() {
  myMap.then(function(m) { updateLegend(m.getBounds()); });
};

When another handler already owns htmlgui_onZoomAndPan, wrap it to call _prev first instead of overwriting.

Live legend pattern — update sidebar counts from inline data on every pan/zoom:

myMap.on("viewchange", function() {
  myMap.then(function(m) { updateLegend(m.getBounds()); });
});
// Also fire once on load:
myMap.then(function(m) { updateLegend(m.getBounds()); });

function updateLegend(bounds) {
  if (!bounds || bounds.length !== 4) return;
  const [swLat, swLng, neLat, neLng] = bounds;
  const counts = {};
  for (const t of DATA) {
    if (t.lat < swLat || t.lat > neLat || t.lon < swLng || t.lon > neLng) continue;
    counts[t.category] = (counts[t.category] || 0) + 1;
  }
  // update DOM legend elements with new counts
}

Persisting the map view in the browser URL

Storing lat/lng/zoom in URL params lets users bookmark or share the exact view. Use ixmaps.getCenter() and ixmaps.getZoom() (global, no Promise needed) to read state, and history.replaceState to update silently.

Important: if another handler (e.g. a data provider) already owns htmlgui_onZoomAndPan, use a wrapper that calls _prev first — never overwrite blindly.

/* ── 1. Read initial view from URL (before map init) ── */
var _urlParams = new URLSearchParams(window.location.search);
var _initLat   = parseFloat(_urlParams.get("lat"))  || 46.8;   // default fallback
var _initLng   = parseFloat(_urlParams.get("lng"))  || 2.3;
var _initZoom  = parseFloat(_urlParams.get("zoom")) || 6;

const myMap = ixmaps.Map("map", { ... })
    .view({ center: { lat: _initLat, lng: _initLng }, zoom: _initZoom })
    ...

/* ── 2. Write current view back to URL (debounced) ── */
var _urlUpdateTimer = null;

function updateUrlFromView() {
    try {
        var c = ixmaps.getCenter();
        var z = ixmaps.getZoom();
        if (!c || z == null) { return; }
        var params = new URLSearchParams(window.location.search);
        params.set("lat",  c.lat.toFixed(6));
        params.set("lng",  c.lng.toFixed(6));
        params.set("zoom", z.toFixed(4));
        history.replaceState(null, "", "?" + params.toString());
    } catch(e) {}
}

/* ── 3. Wrap the existing htmlgui_onZoomAndPan (don't replace it) ── */
function hookUrlUpdate() {
    var _prev = ixmaps.htmlgui_onZoomAndPan;   // save whatever is already there
    ixmaps.htmlgui_onZoomAndPan = function(nZoom) {
        try { if (_prev) { _prev.call(this, nZoom); } } catch(e) {}
        clearTimeout(_urlUpdateTimer);
        _urlUpdateTimer = setTimeout(updateUrlFromView, 400);
    };
}

/* ── 4. Install after map is ready; setTimeout fallback for edge cases ── */
myMap.then(function() { hookUrlUpdate(); updateUrlFromView(); });
setTimeout(function()  { hookUrlUpdate(); updateUrlFromView(); }, 1000);
ixmaps.getCenter() / ixmaps.getZoom() are global — call them directly, no myMap.then() needed. Shareable URL format: map.html?lat=48.856900&lng=2.347800&zoom=14.0000

Special Patterns (quick ref)

Categorical color binding (pin specific colors to values):

.type("CHART|BUBBLE|CATEGORICAL")
.style({ colorscheme: ["#4fc3f7","#ffb300","#ef5350"], values: ["C","F","R"], showdata: "true" })
⚠️ Always use values with CATEGORICAL — without it, ixMaps assigns colors by order of first occurrence in the dataset, not by category name. This means color assignments change depending on data order and are unpredictable. values is the only reliable way to pin a specific color to a specific category.

CATEGORICAL + bubble size from a numeric field (color by category AND size by value — single layer):

.binding({ geo: "lat|lon", value: "categoryField", title: "label", size: "numericField" })
.type("CHART|BUBBLE|CATEGORICAL|GLOW")
.style({ colorscheme: ["#4fc3f7","#ffb300","#ef5350"], values: ["C","F","R"], normalsizevalue: "80", showdata: "true" })
Add size: "numericField" to .binding() to drive bubble radius from a numeric column independently from the category value field. This avoids needing a separate SIZE|VALUES layer when you want both category color and numeric sizing.

Urban trees preset (species color + diameter size + GLOW):

.binding({ geo: "lat|lon", value: "SPECIE", size: "DIAMETRO", title: "LUOGO" })
.type("CHART|BUBBLE|CATEGORICAL|GLOW")
.style({
  colorscheme: [...],
  values: [...],               // species list, fixed order
  normalsizevalue: "220",
  scale: 0.32,
  fillopacity: 0.8,
  showdata: "true"
})
// map options: objectscaling:"dynamic", normalSizeScale:"5000"

Dynamic opacity from a field:

.type("CHART|BUBBLE|SIZE|DOPACITYMAX")
.binding({ geo: "lat|lon", value: "count", alpha: "density" })

Glow effect: add |GLOW to any CHART type

Flows with animated dashes: CHART|VECTOR|BEZIER|POINTER|DASH

CHART|USER — custom draw functions (pinnacleChart, arrowChart):

Requires three extra CDN scripts (load order relative to ixmaps.js does not matter):

<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-flat@master/usercharts/d3/chart.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-flat@master/usercharts/d3/arrow_chart.js"></script>

The userdraw style property names the draw function ("pinnacleChart" or "arrowChart"). Key type modifiers used with USER charts:

ModifierRole
DIFFERENCEcomputes value[1] − value[0] from a `"a\b"` binding
NONEGATIVErender flag — suppresses drawing the chart symbol where the computed value ≤ 0 (data row is still processed; only rendering is skipped)
RELOCATErelocates the chart symbol to the geometry centroid
BOXadds a background box behind the label
BOTTOMTITLEplaces title below the chart symbol
NOLEGENDexcludes this layer from the map legend

Split-winner pattern — two layers from one dataset, no pre-filtering needed:

// Layer A — shows only communes where Sì wins (voti_si − voti_no > 0)
myMap.layer("comuni")
    .data({ url: DATA_URL, type: "csv" })
    .binding({ lookup: "cod_istat", value: "voti_no|voti_si", title: "desc_com" })
    .type("CHART|USER|3D|DIFFERENCE|AGGREGATE|RECT|RELOCATE|SUM|VALUES|NONEGATIVE|BOX|BOTTOMTITLE|NOLEGEND")
    .style({
        name:             "chart_si",
        userdraw:         "pinnacleChart",
        colorscheme:      SI_COLORS,
        sizepow:          2,
        normalsizevalue:  1000000,
        aggregationfield: "desc_com",
        titlefield:       "desc_com",
        datafields:       ["desc_com","desc_prov","margin_f"],
        showdata:         "true"
    })
    .meta({ name: "chart_si", tooltip: "{{desc_com}}<br>voti in più Sì: {{raw.margin_f}}" })
    .define();

// Layer B — shows only communes where No wins: swap binding order, NONEGATIVE drops the rest
myMap.layer("comuni")
    .data({ url: DATA_URL, type: "csv" })
    .binding({ lookup: "cod_istat", value: "voti_si|voti_no", title: "desc_com" })  // ← swapped
    .type("CHART|USER|3D|DIFFERENCE|HEADTAIL|AGGREGATE|RECT|RELOCATE|SUM|VALUES|NONEGATIVE|NOLEGEND")
    .style({ name: "chart_no", userdraw: "pinnacleChart", /* ... */ showdata: "true" })
    .meta({ name: "chart_no", tooltip: "..." })
    .define();
Binding order determines sign: "a|b" → b − a. With NONEGATIVE, only locations where the result > 0 get a chart drawn. Swapping a and b between two layers gives "A wins" vs "B wins" without any data pre-processing. {{raw.fieldname}} in tooltip accesses fields listed in datafields — useful for pre-formatted strings (e.g. "12.345" from .toLocaleString()).

Invisible point anchor layer — load centroid geometry without rendering anything:

// Required when CHART|USER layers need to snap to precise urban centroids
// For POINT geometry, fillopacity:0 alone still renders a dot — scale:0 suppresses it completely
myMap.layer("centroids")
    .data({ url: CENTROIDS_URL, type: "geojson" })
    .binding({ geo: "geometry", id: "PRO_COM", title: "PRO_COM" })
    .type("FEATURE|NOLEGEND")
    .style({
        colorscheme: ["none"],
        scale:       0,         // ← required for point geometry
        fillopacity: 0,
        linecolor:   "none",
        linewidth:   0,
        showdata:    "true"
    })
    .define();
Diverging scales, density patterns, road-tracing, SEQUENCE charts → API_REFERENCE.md § Special Cases Complete working examples → EXAMPLES.md Data preprocessing (data.js) → DATA_JS_GUIDE.md Symbols/icons → SYMBOLS_GUIDE.md Troubleshooting → TROUBLESHOOTING.md

Facet Sidebar (filter panel updated on zoom/pan)

A facet sidebar lets users filter the map by clicking category values or dragging range sliders. It auto-updates on every zoom, pan, and filter change. This pattern requires three CDN plugins:

<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-flat@master/plugins/format.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-flat@master/plugins/facet.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-flat@master/plugins/show_facets.js"></script>

Sidebar HTML structure

Use the canonical template below. The counter-chips pattern (🌳 N totali / 👁 N in vista) gives users immediate feedback on dataset size and current view. Adapt the emoji and label text to the subject matter.

<!-- Sidebar — place outside #map_div, position with CSS (right panel, overlay, etc.) -->
<div id="sidebar_div" style="
    position:absolute; top:0; right:0; width:320px; height:100%;
    display:flex; flex-direction:column;
    background:rgba(248,247,242,0.97); box-shadow:-2px 0 8px rgba(0,0,0,0.08);
    font-family:sans-serif; z-index:900;">

  <!-- Title block -->
  <div style="padding:1.2em 1.2em 0.6em">
    <div style="font-size:1.3em; font-weight:700">🌳 Map Title</div>
    <div style="font-size:0.85em; color:#888; margin-top:0.2em">Subtitle / data source line</div>
  </div>

  <!-- Counter chips: total + in-view -->
  <div style="padding:0 1.2em 0.8em; display:flex; gap:0.5em; flex-wrap:wrap; border-bottom:1px solid #e0dfd8">
    <span id="count-chip-total" style="
        background:#fff; border:1.5px solid #ccc; border-radius:2em;
        padding:0.3em 0.9em; font-size:0.9em; white-space:nowrap">
      🌳 <b id="count-total">—</b> totali
    </span>
    <span id="count-chip-visible" style="
        background:#fff; border:1.5px solid #ccc; border-radius:2em;
        padding:0.3em 0.9em; font-size:0.9em; white-space:nowrap">
      👁 <b id="count-visible">—</b> in vista
    </span>
  </div>

  <!-- Active-filter banner (hidden until a filter is applied) -->
  <div id="filter-div" style="display:none; padding:0.4em 1.2em; background:#fff3cd; font-size:0.82em">
    <b>Filtro attivo:</b> <span id="filter" style="font-style:italic"></span>
    <button onclick="clearFilter()" style="
        float:right; background:none; border:none; cursor:pointer;
        font-size:1em; color:#666">✕</button>
  </div>

  <!-- Data source credit -->
  <div style="padding:0.4em 1.2em; font-size:0.78em; color:#999; border-bottom:1px solid #e0dfd8">
    Dati: <a href="#" target="_blank" style="color:#888">Source Name</a> (CC-BY)
  </div>

  <!-- Scrollable facet area -->
  <div style="overflow-y:auto; flex:1; padding:0 0.4em">
    <div id="show-facets-div"></div>
  </div>
</div>

Set the total count once after data loads (not on every draw):

document.getElementById("count-total").textContent = DATA.length;

ixmaps.statistics — the facet engine hook

Override ixmaps.statistics to compute and render facets. It is called by ixmaps after every draw:

ixmaps.statistics = function (szId) {
    var themeObj = ixmaps.getThemeObj(szId);
    if (!themeObj) return;

    var lastFilter = themeObj.szFilter || "";

    // Fields to facet — order determines sidebar order.
    // NONUMERIC flag: suppresses numeric range sliders for fields that
    // have many unique numbers but are better treated as categories.
    // Fields with >N unique values auto-render as text-search inputs.
    ixmaps.data.fShowFacetValues = false;
    var szFieldsA = [
        "CATEGORY_FIELD",   // categorical — picks up theme colors if it's the value field
        "HEIGHT_CLASS",     // ordinal text
        "STREET_NAME",      // high-cardinality → auto text-search input
        "YEAR"              // numeric range slider (omit NONUMERIC to allow)
    ];

    var facetsA = ixmaps.data.getFacets(
        lastFilter, "user_legend", szFieldsA, szId, "map", "NONUMERIC"
    );

    if (facetsA && facetsA.length) {
        ixmaps.data.showFacets(lastFilter, "show-facets-div", facetsA);
    }

    // update visible-count chip — use your inline DATA array, not theme.indexA
    myMap.then(function(m) {
        var bounds = m.getBounds();
        if (!bounds || bounds.length !== 4) return;
        var swLat = bounds[0], swLng = bounds[1], neLat = bounds[2], neLng = bounds[3];
        var vis = 0;
        DATA.forEach(function(d) {
            if (d.lat >= swLat && d.lat <= neLat && d.lon >= swLng && d.lon <= neLng) vis++;
        });
        var el = document.getElementById("count-visible");
        if (el) el.textContent = vis;
    });
};

React to layer draw — map.on("layerdraw")

Use the event API (preferred over the legacy htmlgui_onDrawTheme hook):

myMap.on("layerdraw", function(e) {
    var themeObj = ixmaps.getThemeObj(e.id);

    // skip helper/invisible layers
    if (!themeObj) return;
    if (themeObj.szFlag && themeObj.szFlag.match(/NOLEGEND/)) return;
    if (!themeObj.fVisible) return;

    ixmaps.statistics(e.id);

    // show/hide active-filter banner
    if (themeObj.szFilter) {
        document.getElementById("filter").innerHTML = themeObj.szFilter;
        document.getElementById("filter-div").style.display = "";
    } else {
        document.getElementById("filter-div").style.display = "none";
    }
});
Legacy hook ixmaps.htmlgui_onDrawTheme — still works but is old-style. Prefer map.on("layerdraw") for new code. If an existing plugin already uses htmlgui_onDrawTheme, wrap it (var _prev = ixmaps.htmlgui_onDrawTheme; ixmaps.htmlgui_onDrawTheme = function(szId){...; _prev && _prev(szId);}) rather than overwriting.

Clear all facet filters

function clearFilter() {
    ixmaps.data.facetsFilterA = [];
    myMap.then(function(m) {
        m.changeThemeStyle("yourThemeName", "filter", "remove");
    });
}

Facet button style overrides

show_facets.js generates .btn-primary buttons and .badge count labels. Override them to match your design:

#show-facets-div .btn-primary {
    background-color: #fff;
    color: #334;
    border: none;
    border-bottom: solid rgba(128,128,128,0.25) 1px;
    border-radius: 0;
}
#show-facets-div .btn-primary:hover,
#show-facets-div .btn-primary:focus {
    background-color: #f0efe8;
    color: #112;
    outline: none; box-shadow: none;
}
#show-facets-div .badge {
    background: transparent;
    color: #778;
    font-size: 13px;
    font-weight: 400;
}

NONUMERIC flag

Pass "NONUMERIC" as the last argument to getFacets to suppress range-slider facets for fields that happen to contain numbers but are really categories (e.g. year codes, ID numbers). Without this flag, any numeric field will render as a histogram + dual-handle slider.

Category field gets theme colors automatically

If one of the facet fields matches the theme's value binding field, show_facets.js automatically colors each facet button with the corresponding theme color. No extra config needed — just include the field name.

theme.szFilter vs themeObj.szFilter

lastFilter = themeObj.szFilter || "" — always read the filter from the theme object, not a local variable. The facet engine updates it internally; reading it fresh on each draw ensures facets reflect the current filter state.


Overlay Indicator Layer (small dot on top of main bubble)

Use a second layer over the main bubbles to show a per-item status flag — e.g. failure risk class, alert state, certification level — without changing the primary color scheme.

Pattern

  1. Add a constant _dot field to source data so the size binding has a numeric value: DATA.forEach(function(d) {d._dot = 50;});
  2. Filter to only the items worth showing (e.g. only elevated/extreme risk, skip negligible): var riskData = DATA.filter(function(d) {return d.RISK && d.RISK.match(/^(HIGH|EXTREME)/);});
  3. Define the overlay layer using CATEGORICAL|NOLEGEND piped into the type string, scale for size, and _dot for the size binding: var indicatorTheme = ixmaps.layer("risk_dots").data({obj: riskData, type: "json"}).binding({geo: "lat|lon", value: "RISK", title: "NAME", size: "_dot"}).type("CHART|BUBBLE|CATEGORICAL|NOLEGEND").style({colorscheme: ["#ff9800", "#d32f2f"], values: ["HIGH", "EXTREME"], normalsizevalue: "1000", // same as main layer scale: 0.1, // 10% of main bubble size → small indicator dot fillopacity: 1.0, strokewidth: "0", showdata: "true", align: "bottom" // anchor dot to bottom of main bubble}).meta({name: "risk_dots"}).title("Risk indicator").define(); myMap.layer(indicatorTheme, "direct");

Key rules

RuleWhy
NOLEGEND must be piped into the type string: `"CHARTBUBBLE
Use scale: 0.1 rather than a very large normalsizevaluescale is a clean multiplier applied after size calculation; normalsizevalue only works cleanly when the size field has a known typical range
Keep normalsizevalue the same as the main layerMakes the dot size proportional to the main bubble for the same item — a bigger tree gets a bigger dot
Add _dot constant before filteringDATA.forEach(d => d._dot = 50) on the source array means every filtered subset inherits the field
Filter to only meaningful statesEmpty dots for the "all-clear" state (A/negligible) add clutter without information

Skip the indicator layer in layerdraw

The draw handler must skip NOLEGEND layers to avoid running ixmaps.statistics on the indicator layer (which would show the risk categories as the main facets). The guard is already in the recommended pattern above:

if (themeObj.szFlag && themeObj.szFlag.match(/NOLEGEND/)) return;

CSS Conflicts with External Frameworks (Bootstrap etc.)

Never load Bootstrap 3 (or similar CSS frameworks) alongside ixmaps. Bootstrap 3's .hidden {display:none!important} rule silently breaks ixmaps UI elements — toolbar buttons, tooltip, and context menu all become invisible because:

  • ixmaps creates elements with class="hidden" and controls visibility via element.style.display = "flex/block/inline"
  • Bootstrap's !important on .hidden beats inline styles — ixmaps can never win
  • The failure is silent: no JS errors, elements just stay invisible

Root fix: standalone facet CSS

Instead of Bootstrap, include ~35 lines of standalone CSS that covers only what show_facets.js generates:

/* ── Standalone facet CSS (replaces Bootstrap 3) ── */
.list-group { padding-left: 0; margin-bottom: 20px; list-style: none; }
.facet, .facet-active { margin-bottom: 0; }

/* CRITICAL: must use display:table, NOT flexbox.
   show_facets.js renders a colored proportion bar as a <div> immediately
   after each <button> inside .input-group. With display:table, they stack
   vertically (each becomes a table row). With display:flex, the bar becomes
   a horizontal sibling and disappears entirely. */
.input-group { position: relative; display: table; border-collapse: separate; width: 100%; }
.input-group .form-control { display: table-cell; width: 100%; }
.input-group-btn { display: table-cell; white-space: nowrap; width: 1%; vertical-align: middle; }
.form-control {
  display: block; width: 100%;
  padding: 4px 8px; font-size: 14px; line-height: 1.43;
  color: #555; background: #fff;
  border: 1px solid #ccc; border-radius: 4px;
}
.form-control:focus { outline: none; border-color: #66afe9; }

.btn {
  display: inline-block; padding: 5px 10px;
  font-size: 14px; font-weight: 400; line-height: 1.43;
  text-align: center; white-space: nowrap; vertical-align: middle;
  cursor: pointer; border: 1px solid transparent; border-radius: 4px;
  background: none; font-family: inherit;
}
.btn-block  { display: block; width: 100%; }
.btn-primary { color: #fff; background: #337ab7; border-color: #2e6da4; }
.btn-default { color: #333; background: #fff; border-color: #ccc; }
.btn-default:hover { background: #e6e6e6; border-color: #adadad; }
.badge {
  display: inline-block; min-width: 10px; padding: 3px 7px;
  font-size: 12px; font-weight: 700; line-height: 1;
  color: #fff; text-align: center; white-space: nowrap;
  vertical-align: baseline; background: #777; border-radius: 10px;
}
.pull-right { float: right !important; }

Tooltip and context menu fix

ixmaps creates #tooltip and #contextmenu with class="hidden visibility-hidden;" (note: literal semicolon in the class attribute). Always add this safety fix in myMap.then():

myMap.then(function() {
    setTimeout(function() {
        ["tooltip","contextmenu"].forEach(function(id) {
            var el = document.getElementById(id);
            if (el) {
                el.classList.remove("hidden");
                el.classList.remove("visibility-hidden");
                el.style.display = "none";
            }
        });
    }, 500);
});

Fallback: CSS attribute-selector workaround

If Bootstrap cannot be removed (e.g. it is required by other page content), use higher-specificity rules to override the conflict. Specificity (0,2,0) beats Bootstrap's (0,1,0):

.hidden[style*="display: flex"],   .hidden[style*="display:flex"]   { display: flex   !important; }
.hidden[style*="display: block"],  .hidden[style*="display:block"]  { display: block  !important; }
.hidden[style*="display: inline"], .hidden[style*="display:inline"] { display: inline !important; }
Note: CDN-loaded Bootstrap stylesheets are CORS-blocked, so JS-based patching of cssRules does not work. The CSS or JS approaches above are the only reliable fixes.

平台分布

Codex

37.2%
按下载量换算52

Claude

29.53%
按下载量换算42

Cursor

20.31%
按下载量换算29

Gemini CLI

9.68%
按下载量换算14

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills