dataface 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. d3_format/__init__.py +14 -0
  2. d3_format/errors.py +19 -0
  3. d3_format/format.py +551 -0
  4. d3_format/spec.py +159 -0
  5. dataface/DATAFACE_SYNTAX.md +1135 -0
  6. dataface/__init__.py +93 -0
  7. dataface/_docs_site.py +20 -0
  8. dataface/_install_hint.py +26 -0
  9. dataface/agent_api/__init__.py +79 -0
  10. dataface/agent_api/_init_templates/__init__.py +0 -0
  11. dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
  12. dataface/agent_api/_init_templates/dataface.yml +15 -0
  13. dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
  14. dataface/agent_api/_init_templates/index.md +24 -0
  15. dataface/agent_api/_paths.py +118 -0
  16. dataface/agent_api/_project_agents_md.py +43 -0
  17. dataface/agent_api/_session_store.py +486 -0
  18. dataface/agent_api/_state.py +28 -0
  19. dataface/agent_api/chat.py +221 -0
  20. dataface/agent_api/dashboards.py +257 -0
  21. dataface/agent_api/describe.py +366 -0
  22. dataface/agent_api/describe_query.py +120 -0
  23. dataface/agent_api/docs/__init__.py +25 -0
  24. dataface/agent_api/docs/_loader.py +292 -0
  25. dataface/agent_api/docs/yaml-reference.md +2757 -0
  26. dataface/agent_api/file_refs.py +118 -0
  27. dataface/agent_api/init.py +126 -0
  28. dataface/agent_api/inspect.py +128 -0
  29. dataface/agent_api/mcp_install.py +170 -0
  30. dataface/agent_api/query.py +274 -0
  31. dataface/agent_api/schema.py +658 -0
  32. dataface/agent_api/schema_search.py +284 -0
  33. dataface/agent_api/search.py +270 -0
  34. dataface/agent_api/skill_install.py +141 -0
  35. dataface/agent_api/skill_render.py +90 -0
  36. dataface/agent_api/skills.py +293 -0
  37. dataface/agent_api/surface_aliases.yaml +128 -0
  38. dataface/agent_api/validate.py +175 -0
  39. dataface/agent_api/validate_query.py +84 -0
  40. dataface/ai/__init__.py +39 -0
  41. dataface/ai/agent.py +139 -0
  42. dataface/ai/context.py +45 -0
  43. dataface/ai/events.py +62 -0
  44. dataface/ai/external_mcp.py +610 -0
  45. dataface/ai/generate_sql.py +96 -0
  46. dataface/ai/llm.py +403 -0
  47. dataface/ai/mcp/__init__.py +51 -0
  48. dataface/ai/mcp/server.py +289 -0
  49. dataface/ai/memories.py +85 -0
  50. dataface/ai/prompts.py +177 -0
  51. dataface/ai/schema_context.py +138 -0
  52. dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
  53. dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
  54. dataface/ai/skills/dashboard-build/SKILL.md +212 -0
  55. dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
  56. dataface/ai/skills/dashboard-design/SKILL.md +182 -0
  57. dataface/ai/skills/dashboard-review/SKILL.md +113 -0
  58. dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
  59. dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
  60. dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
  61. dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
  62. dataface/ai/skills/drill-down-link/SKILL.md +112 -0
  63. dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
  64. dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
  65. dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
  66. dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
  67. dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
  68. dataface/ai/skills/kpi-row/SKILL.md +101 -0
  69. dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
  70. dataface/ai/skills/report-design/SKILL.md +184 -0
  71. dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
  72. dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
  73. dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
  74. dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
  75. dataface/ai/skills/time-series-trend/SKILL.md +93 -0
  76. dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
  77. dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
  78. dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
  79. dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
  80. dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
  81. dataface/ai/tool_schemas.py +132 -0
  82. dataface/ai/tools/__init__.py +312 -0
  83. dataface/ai/yaml_utils.py +57 -0
  84. dataface/cli/__init__.py +3 -0
  85. dataface/cli/_console.py +48 -0
  86. dataface/cli/_error_format.py +83 -0
  87. dataface/cli/_extras.py +190 -0
  88. dataface/cli/_json_output.py +8 -0
  89. dataface/cli/_parsing.py +17 -0
  90. dataface/cli/_version_info.py +56 -0
  91. dataface/cli/commands/__init__.py +3 -0
  92. dataface/cli/commands/_agent_input.py +205 -0
  93. dataface/cli/commands/_agent_server.py +115 -0
  94. dataface/cli/commands/chat.py +645 -0
  95. dataface/cli/commands/describe.py +107 -0
  96. dataface/cli/commands/docs.py +131 -0
  97. dataface/cli/commands/extension.py +179 -0
  98. dataface/cli/commands/init.py +240 -0
  99. dataface/cli/commands/inspect.py +94 -0
  100. dataface/cli/commands/mcp_init.py +167 -0
  101. dataface/cli/commands/query.py +386 -0
  102. dataface/cli/commands/render.py +291 -0
  103. dataface/cli/commands/schema.py +411 -0
  104. dataface/cli/commands/search.py +49 -0
  105. dataface/cli/commands/serve.py +114 -0
  106. dataface/cli/commands/skills.py +133 -0
  107. dataface/cli/commands/skills_init.py +161 -0
  108. dataface/cli/commands/validate.py +63 -0
  109. dataface/cli/main.py +1501 -0
  110. dataface/core/__init__.py +75 -0
  111. dataface/core/compile/__init__.py +244 -0
  112. dataface/core/compile/_jinja_helpers.py +78 -0
  113. dataface/core/compile/channel.py +222 -0
  114. dataface/core/compile/chart_focus.py +101 -0
  115. dataface/core/compile/chart_resolved.py +169 -0
  116. dataface/core/compile/chart_type_detection.py +489 -0
  117. dataface/core/compile/chart_update.py +261 -0
  118. dataface/core/compile/colors.py +64 -0
  119. dataface/core/compile/compiler.py +904 -0
  120. dataface/core/compile/config.py +823 -0
  121. dataface/core/compile/custom_chart_types.py +208 -0
  122. dataface/core/compile/data_table_attachment.py +1287 -0
  123. dataface/core/compile/detect.py +110 -0
  124. dataface/core/compile/errors.py +302 -0
  125. dataface/core/compile/filter_injection.py +319 -0
  126. dataface/core/compile/introspection.py +527 -0
  127. dataface/core/compile/jinja.py +511 -0
  128. dataface/core/compile/labels_env.py +52 -0
  129. dataface/core/compile/markdown.py +154 -0
  130. dataface/core/compile/meta.py +388 -0
  131. dataface/core/compile/models/__init__.py +0 -0
  132. dataface/core/compile/models/chart/__init__.py +0 -0
  133. dataface/core/compile/models/chart/authored.py +2137 -0
  134. dataface/core/compile/models/chart/compiled.py +398 -0
  135. dataface/core/compile/models/config.py +347 -0
  136. dataface/core/compile/models/face/__init__.py +0 -0
  137. dataface/core/compile/models/face/authored.py +659 -0
  138. dataface/core/compile/models/face/compiled.py +522 -0
  139. dataface/core/compile/models/factories.py +201 -0
  140. dataface/core/compile/models/markers.py +40 -0
  141. dataface/core/compile/models/palette.py +36 -0
  142. dataface/core/compile/models/primitives.py +415 -0
  143. dataface/core/compile/models/query/__init__.py +0 -0
  144. dataface/core/compile/models/query/authored.py +246 -0
  145. dataface/core/compile/models/query/compiled.py +710 -0
  146. dataface/core/compile/models/refs.py +137 -0
  147. dataface/core/compile/models/source.py +611 -0
  148. dataface/core/compile/models/style/__init__.py +0 -0
  149. dataface/core/compile/models/style/authored.py +481 -0
  150. dataface/core/compile/models/style/compiled.py +3399 -0
  151. dataface/core/compile/models/style/merged.py +1682 -0
  152. dataface/core/compile/models/theme.py +362 -0
  153. dataface/core/compile/models/variable/__init__.py +0 -0
  154. dataface/core/compile/models/variable/authored.py +254 -0
  155. dataface/core/compile/models/vega_lite/__init__.py +0 -0
  156. dataface/core/compile/models/vega_lite/config.py +510 -0
  157. dataface/core/compile/models/vega_lite/contracts.py +171 -0
  158. dataface/core/compile/normalize_charts.py +494 -0
  159. dataface/core/compile/normalize_layout.py +1000 -0
  160. dataface/core/compile/normalize_queries.py +297 -0
  161. dataface/core/compile/normalize_variables.py +489 -0
  162. dataface/core/compile/normalizer.py +543 -0
  163. dataface/core/compile/palette.py +1100 -0
  164. dataface/core/compile/parameterized.py +658 -0
  165. dataface/core/compile/parser.py +228 -0
  166. dataface/core/compile/schema.py +20 -0
  167. dataface/core/compile/schema_renderers/__init__.py +0 -0
  168. dataface/core/compile/schema_renderers/json_schema.py +163 -0
  169. dataface/core/compile/schema_renderers/prompt.py +152 -0
  170. dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
  171. dataface/core/compile/sizing.py +2126 -0
  172. dataface/core/compile/sources.py +518 -0
  173. dataface/core/compile/sql_authoring_lint.py +56 -0
  174. dataface/core/compile/style_cascade.py +471 -0
  175. dataface/core/compile/typography.py +299 -0
  176. dataface/core/compile/validator.py +301 -0
  177. dataface/core/compile/variables.py +53 -0
  178. dataface/core/compile/vega_config.py +98 -0
  179. dataface/core/compile/vega_lite/__init__.py +6 -0
  180. dataface/core/compile/vega_lite/validation.py +95 -0
  181. dataface/core/compile/yaml_error_formatter.py +838 -0
  182. dataface/core/connections.py +38 -0
  183. dataface/core/dashboard.py +358 -0
  184. dataface/core/defaults/default_config.yml +101 -0
  185. dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
  186. dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
  187. dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
  188. dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
  189. dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
  190. dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
  191. dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
  192. dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
  193. dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
  194. dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
  195. dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
  196. dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
  197. dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
  198. dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
  199. dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
  200. dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
  201. dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
  202. dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
  203. dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
  204. dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
  205. dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
  206. dataface/core/defaults/palettes/sequential/amber.yml +22 -0
  207. dataface/core/defaults/palettes/sequential/blue.yml +22 -0
  208. dataface/core/defaults/palettes/sequential/brown.yml +22 -0
  209. dataface/core/defaults/palettes/sequential/gray.yml +22 -0
  210. dataface/core/defaults/palettes/sequential/green.yml +22 -0
  211. dataface/core/defaults/palettes/sequential/purple.yml +22 -0
  212. dataface/core/defaults/palettes/sequential/rust.yml +22 -0
  213. dataface/core/defaults/palettes/sequential/teal.yml +22 -0
  214. dataface/core/defaults/palettes/tone/negative.yml +32 -0
  215. dataface/core/defaults/palettes/tone/positive.yml +22 -0
  216. dataface/core/defaults/palettes/tone/warning.yml +22 -0
  217. dataface/core/defaults/themes/_base.yaml +786 -0
  218. dataface/core/defaults/themes/bi.yaml +16 -0
  219. dataface/core/defaults/themes/carbong100.yaml +41 -0
  220. dataface/core/defaults/themes/cream.yaml +122 -0
  221. dataface/core/defaults/themes/dark.yaml +40 -0
  222. dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
  223. dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
  224. dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
  225. dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
  226. dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
  227. dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
  228. dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
  229. dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
  230. dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
  231. dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
  232. dataface/core/defaults/themes/editorial.yaml +147 -0
  233. dataface/core/defaults/themes/light.yaml +30 -0
  234. dataface/core/defaults/themes/looker.yaml +17 -0
  235. dataface/core/defaults/themes/stark.yaml +134 -0
  236. dataface/core/errors/__init__.py +67 -0
  237. dataface/core/errors/codes_compile.py +56 -0
  238. dataface/core/errors/codes_execute.py +177 -0
  239. dataface/core/errors/codes_render.py +106 -0
  240. dataface/core/errors/codes_unknown.py +15 -0
  241. dataface/core/errors/hints.py +74 -0
  242. dataface/core/errors/registry.py +42 -0
  243. dataface/core/errors/structured.py +92 -0
  244. dataface/core/execute/__init__.py +91 -0
  245. dataface/core/execute/adapters/__init__.py +49 -0
  246. dataface/core/execute/adapters/adapter_registry.py +400 -0
  247. dataface/core/execute/adapters/base.py +245 -0
  248. dataface/core/execute/adapters/csv_adapter.py +239 -0
  249. dataface/core/execute/adapters/dbt_adapter.py +283 -0
  250. dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
  251. dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
  252. dataface/core/execute/adapters/dbt_utils.py +150 -0
  253. dataface/core/execute/adapters/http_adapter.py +224 -0
  254. dataface/core/execute/adapters/metricflow_adapter.py +94 -0
  255. dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
  256. dataface/core/execute/adapters/sql_adapter.py +710 -0
  257. dataface/core/execute/adapters/values_adapter.py +58 -0
  258. dataface/core/execute/batch.py +744 -0
  259. dataface/core/execute/cache_backend.py +135 -0
  260. dataface/core/execute/cache_keys.py +66 -0
  261. dataface/core/execute/dbt_jinja.py +21 -0
  262. dataface/core/execute/dialects/__init__.py +121 -0
  263. dataface/core/execute/dialects/athena.py +75 -0
  264. dataface/core/execute/dialects/base.py +302 -0
  265. dataface/core/execute/dialects/bigquery.py +38 -0
  266. dataface/core/execute/dialects/databricks.py +68 -0
  267. dataface/core/execute/dialects/duckdb.py +35 -0
  268. dataface/core/execute/dialects/mysql.py +68 -0
  269. dataface/core/execute/dialects/postgres.py +39 -0
  270. dataface/core/execute/dialects/redshift.py +12 -0
  271. dataface/core/execute/dialects/snowflake.py +51 -0
  272. dataface/core/execute/dialects/sqlserver.py +92 -0
  273. dataface/core/execute/duckdb_cache.py +712 -0
  274. dataface/core/execute/duckdb_config.py +26 -0
  275. dataface/core/execute/errors.py +213 -0
  276. dataface/core/execute/executor.py +1249 -0
  277. dataface/core/execute/parallel.py +162 -0
  278. dataface/core/execute/setup_sql.py +58 -0
  279. dataface/core/execute/source_registry.py +72 -0
  280. dataface/core/execute/source_resolver.py +255 -0
  281. dataface/core/execute/sql_guard.py +387 -0
  282. dataface/core/execute/sql_literals.py +199 -0
  283. dataface/core/fonts.py +52 -0
  284. dataface/core/inspect/__init__.py +32 -0
  285. dataface/core/inspect/cache_factory.py +98 -0
  286. dataface/core/inspect/db_types.py +162 -0
  287. dataface/core/inspect/dbt_schema.py +96 -0
  288. dataface/core/inspect/defaults.yml +37 -0
  289. dataface/core/inspect/fanout_risk.py +109 -0
  290. dataface/core/inspect/manifest_utils.py +77 -0
  291. dataface/core/inspect/partials/categorical.yml +40 -0
  292. dataface/core/inspect/partials/date.yml +40 -0
  293. dataface/core/inspect/partials/numeric.yml +55 -0
  294. dataface/core/inspect/partition_types.py +38 -0
  295. dataface/core/inspect/query_validator.py +975 -0
  296. dataface/core/inspect/renderer.py +354 -0
  297. dataface/core/inspect/resolver.py +808 -0
  298. dataface/core/inspect/search.py +461 -0
  299. dataface/core/inspect/sources/__init__.py +32 -0
  300. dataface/core/inspect/sources/dbt.py +738 -0
  301. dataface/core/inspect/sources/duckdb_utils.py +66 -0
  302. dataface/core/inspect/templates/__init__.py +1 -0
  303. dataface/core/inspect/templates/categorical_column.yml +196 -0
  304. dataface/core/inspect/templates/charts.yml +109 -0
  305. dataface/core/inspect/templates/date_column.yml +248 -0
  306. dataface/core/inspect/templates/model.yml +138 -0
  307. dataface/core/inspect/templates/numeric_column.yml +261 -0
  308. dataface/core/inspect/templates/quality.yml +80 -0
  309. dataface/core/inspect/templates/string_column.yml +263 -0
  310. dataface/core/project_roots.py +165 -0
  311. dataface/core/render/__init__.py +87 -0
  312. dataface/core/render/board_links.py +176 -0
  313. dataface/core/render/chart/__init__.py +27 -0
  314. dataface/core/render/chart/arc_attached_table.py +251 -0
  315. dataface/core/render/chart/artifacts.py +16 -0
  316. dataface/core/render/chart/callout.py +225 -0
  317. dataface/core/render/chart/decisions.py +358 -0
  318. dataface/core/render/chart/geo.py +700 -0
  319. dataface/core/render/chart/kpi.py +916 -0
  320. dataface/core/render/chart/labels.py +76 -0
  321. dataface/core/render/chart/pipeline.py +818 -0
  322. dataface/core/render/chart/presentation.py +36 -0
  323. dataface/core/render/chart/profile.py +3438 -0
  324. dataface/core/render/chart/render_single.py +347 -0
  325. dataface/core/render/chart/renderers.py +193 -0
  326. dataface/core/render/chart/rendering.py +565 -0
  327. dataface/core/render/chart/serialization.py +90 -0
  328. dataface/core/render/chart/spark.py +496 -0
  329. dataface/core/render/chart/spark_bar.py +370 -0
  330. dataface/core/render/chart/spec_builders.py +154 -0
  331. dataface/core/render/chart/standard_renderer.py +2645 -0
  332. dataface/core/render/chart/table.py +2957 -0
  333. dataface/core/render/chart/table_support.py +1452 -0
  334. dataface/core/render/chart/tick_values.py +66 -0
  335. dataface/core/render/chart/time_unit_detect.py +809 -0
  336. dataface/core/render/chart/title_overflow.py +157 -0
  337. dataface/core/render/chart/type_inference.py +122 -0
  338. dataface/core/render/chart/validation.py +99 -0
  339. dataface/core/render/chart/vega_lite.py +125 -0
  340. dataface/core/render/chart/vega_lite_types.py +268 -0
  341. dataface/core/render/chart/vl_field_maps.py +346 -0
  342. dataface/core/render/chart_interactivity.py +24 -0
  343. dataface/core/render/control_registry.py +287 -0
  344. dataface/core/render/converters/__init__.py +24 -0
  345. dataface/core/render/converters/chart.py +276 -0
  346. dataface/core/render/converters/html.py +98 -0
  347. dataface/core/render/converters/pdf.py +40 -0
  348. dataface/core/render/converters/png.py +41 -0
  349. dataface/core/render/errors.py +144 -0
  350. dataface/core/render/face_api.py +160 -0
  351. dataface/core/render/faces.py +1194 -0
  352. dataface/core/render/font_measurement.py +48 -0
  353. dataface/core/render/font_support.py +197 -0
  354. dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
  355. dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
  356. dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
  357. dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
  358. dataface/core/render/fonts/InterVariable.ttf +0 -0
  359. dataface/core/render/fonts/InterVariable.woff2 +0 -0
  360. dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
  361. dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
  362. dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
  363. dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
  364. dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
  365. dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
  366. dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
  367. dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
  368. dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
  369. dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
  370. dataface/core/render/fonts/_emoji_font_face.css +43 -0
  371. dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
  372. dataface/core/render/format_utils.py +329 -0
  373. dataface/core/render/geo_defaults.yml +28 -0
  374. dataface/core/render/json_format.py +146 -0
  375. dataface/core/render/layout_sizing.py +865 -0
  376. dataface/core/render/layouts.py +541 -0
  377. dataface/core/render/markdown_defaults.yml +16 -0
  378. dataface/core/render/missing_vars_prompt.py +79 -0
  379. dataface/core/render/placeholder.py +389 -0
  380. dataface/core/render/render_result.py +14 -0
  381. dataface/core/render/renderer.py +467 -0
  382. dataface/core/render/script_embedding.py +16 -0
  383. dataface/core/render/svg_utils.py +212 -0
  384. dataface/core/render/template_loader.py +69 -0
  385. dataface/core/render/templates/controls/_styles.css +606 -0
  386. dataface/core/render/templates/controls/checkbox.html +16 -0
  387. dataface/core/render/templates/controls/date.html +16 -0
  388. dataface/core/render/templates/controls/number.html +19 -0
  389. dataface/core/render/templates/controls/readonly.html +9 -0
  390. dataface/core/render/templates/controls/select.html +21 -0
  391. dataface/core/render/templates/controls/slider.html +22 -0
  392. dataface/core/render/templates/controls/text.html +16 -0
  393. dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
  394. dataface/core/render/templates/scripts/variables.js +976 -0
  395. dataface/core/render/templates/svg/grid_pattern.svg +3 -0
  396. dataface/core/render/templates/svg/styles.css +51 -0
  397. dataface/core/render/terminal.py +311 -0
  398. dataface/core/render/terminal_charts.py +563 -0
  399. dataface/core/render/terminal_defaults.yml +2 -0
  400. dataface/core/render/terminal_layouts.py +299 -0
  401. dataface/core/render/terminal_text.py +31 -0
  402. dataface/core/render/text/__init__.py +1 -0
  403. dataface/core/render/text/case.py +113 -0
  404. dataface/core/render/text_format.py +129 -0
  405. dataface/core/render/utils.py +106 -0
  406. dataface/core/render/variable_controls.py +946 -0
  407. dataface/core/render/variable_input_refinement.py +140 -0
  408. dataface/core/render/warnings/__init__.py +15 -0
  409. dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
  410. dataface/core/render/warnings/base.py +44 -0
  411. dataface/core/render/warnings/fanout_risk.py +15 -0
  412. dataface/core/render/warnings/from_query_diagnostic.py +56 -0
  413. dataface/core/render/warnings/missing_join_predicate.py +13 -0
  414. dataface/core/render/warnings/query_parse_error.py +14 -0
  415. dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
  416. dataface/core/render/warnings/reaggregation.py +14 -0
  417. dataface/core/render/warnings/registry.py +45 -0
  418. dataface/core/render/warnings/suppression.py +46 -0
  419. dataface/core/render/warnings/temporal_single_point.py +63 -0
  420. dataface/core/render/warnings/unreferenced_chart.py +15 -0
  421. dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
  422. dataface/core/render/yaml_format.py +167 -0
  423. dataface/core/resolve_face.py +195 -0
  424. dataface/core/schema/__init__.py +0 -0
  425. dataface/core/schema/guidance.py +151 -0
  426. dataface/core/scoped_paths.py +59 -0
  427. dataface/core/serve/__init__.py +14 -0
  428. dataface/core/serve/bootstrap.py +39 -0
  429. dataface/core/serve/embedded.py +57 -0
  430. dataface/core/serve/port.py +129 -0
  431. dataface/core/serve/server.py +938 -0
  432. dataface/core/serve/templates/__init__.py +0 -0
  433. dataface/core/serve/templates/directory.yml +6 -0
  434. dataface/core/serve/templates/error.html.j2 +217 -0
  435. dataface/core/utils.py +121 -0
  436. dataface/core/validate.py +64 -0
  437. dataface/integrations/__init__.py +0 -0
  438. dataface/integrations/highlighting.py +351 -0
  439. dataface/integrations/markdown.py +537 -0
  440. dataface/py.typed +0 -0
  441. dataface-0.1.2.dist-info/METADATA +375 -0
  442. dataface-0.1.2.dist-info/RECORD +455 -0
  443. dataface-0.1.2.dist-info/WHEEL +4 -0
  444. dataface-0.1.2.dist-info/entry_points.txt +2 -0
  445. dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
  446. mdsvg/__init__.py +168 -0
  447. mdsvg/fonts.py +656 -0
  448. mdsvg/images.py +299 -0
  449. mdsvg/parser.py +629 -0
  450. mdsvg/playground.py +284 -0
  451. mdsvg/py.typed +2 -0
  452. mdsvg/renderer.py +1623 -0
  453. mdsvg/style.py +355 -0
  454. mdsvg/types.py +200 -0
  455. mdsvg/utils.py +86 -0
@@ -0,0 +1,700 @@
1
+ """Geographic and map chart Vega-Lite spec generators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from dataface.core.compile.chart_resolved import ResolvedChart, effective_color_field
8
+ from dataface.core.compile.models.style.merged import MergedStyle, resolve_mark
9
+ from dataface.core.compile.models.vega_lite.contracts import Projection
10
+ from dataface.core.render.chart.presentation import to_plain_dict
11
+ from dataface.core.render.chart.spec_builders import (
12
+ new_chart_spec,
13
+ set_chart_dimensions,
14
+ set_chart_title,
15
+ tooltip_entry,
16
+ )
17
+ from dataface.core.render.chart.type_inference import infer_vega_type_from_data
18
+ from dataface.core.render.format_utils import resolve_format
19
+ from dataface.core.render.text.case import format_display_text
20
+ from dataface.core.render.utils import normalize_data_types, slug_to_text
21
+
22
+
23
+ def _add_data_join(
24
+ spec: dict[str, Any],
25
+ data: list[dict[str, Any]],
26
+ geo_key: str,
27
+ lookup_field: str | None,
28
+ value_field: str | None,
29
+ handle_missing: bool = False,
30
+ default_value: Any | None = None,
31
+ ) -> dict[str, Any]:
32
+ """Add lookup transform to join user data to geographic features.
33
+
34
+ This function creates a Vega-Lite lookup transform that joins user data
35
+ to geographic features (states, countries, etc.) based on matching keys.
36
+
37
+ The geo data (from URL) becomes the primary data source, and user data
38
+ is joined via lookup. This ensures all geographic regions are displayed,
39
+ with unmatched regions showing null values.
40
+
41
+ Args:
42
+ spec: Vega-Lite spec to modify (will be modified in place)
43
+ data: User data to join to geographic features
44
+ geo_key: Key field in geographic data (e.g., "id" in TopoJSON)
45
+ lookup_field: Key field in user data to match against geo_key
46
+ value_field: Field in user data to use for encoding (color, etc.)
47
+ handle_missing: If True, add handling for missing/unmatched data
48
+ default_value: Default value for unmatched geographic features
49
+
50
+ Returns:
51
+ Modified Vega-Lite spec with lookup transform added
52
+
53
+ Example:
54
+ >>> spec = {"data": {"url": "us-10m.json"}}
55
+ >>> data = [{"state_code": "06", "population": 39500000}]
56
+ >>> result = _add_data_join(spec, data, "id", "state_code", "population")
57
+ >>> result["transform"][0]["lookup"]
58
+ 'id'
59
+
60
+ """
61
+ # Return spec unchanged if no data or no lookup field
62
+ if not data or not lookup_field:
63
+ return spec
64
+
65
+ # Get all fields from data to include in lookup
66
+ data_fields = list(data[0].keys()) if data else []
67
+
68
+ # Create lookup transform
69
+ lookup_transform: dict[str, Any] = {
70
+ "lookup": geo_key,
71
+ "from": {
72
+ "data": {"values": data},
73
+ "key": lookup_field,
74
+ "fields": data_fields,
75
+ },
76
+ }
77
+
78
+ # Initialize transforms list if not present
79
+ if "transform" not in spec:
80
+ spec["transform"] = []
81
+
82
+ # Add the lookup transform
83
+ spec["transform"].insert(0, lookup_transform)
84
+
85
+ # Handle missing/unmatched data if requested
86
+ if handle_missing and default_value is not None and value_field:
87
+ # Add a calculate transform to replace nulls with default value
88
+ # This ensures unmatched regions show with the default value
89
+ calculate_transform = {
90
+ "calculate": f"datum.{value_field} === null ? {default_value} : datum.{value_field}",
91
+ "as": f"{value_field}_filled",
92
+ }
93
+ spec["transform"].append(calculate_transform)
94
+
95
+ return spec
96
+
97
+
98
+ # ============================================================================
99
+ # GEOGRAPHIC DATA SOURCES
100
+ # ============================================================================
101
+ # Built-in geographic data sources from Vega Datasets CDN
102
+
103
+ GEO_SOURCES: dict[str, dict[str, Any]] = {
104
+ # US boundaries
105
+ "us-states": {
106
+ "url": "https://vega.github.io/vega-datasets/data/us-10m.json",
107
+ "format": {"type": "topojson", "feature": "states"},
108
+ "projection": "albersUsa",
109
+ "key": "id",
110
+ },
111
+ "us-counties": {
112
+ "url": "https://vega.github.io/vega-datasets/data/us-10m.json",
113
+ "format": {"type": "topojson", "feature": "counties"},
114
+ "projection": "albersUsa",
115
+ "key": "id",
116
+ },
117
+ # World boundaries
118
+ "world-countries": {
119
+ "url": "https://vega.github.io/vega-datasets/data/world-110m.json",
120
+ "format": {"type": "topojson", "feature": "countries"},
121
+ "projection": "equalEarth",
122
+ "key": "id",
123
+ },
124
+ "world-50m": {
125
+ "url": "https://vega.github.io/vega-datasets/data/world-50m.json",
126
+ "format": {"type": "topojson", "feature": "countries"},
127
+ "projection": "equalEarth",
128
+ "key": "id",
129
+ },
130
+ # City neighborhoods - San Francisco
131
+ "sf-neighborhoods": {
132
+ "url": "https://data.sfgov.org/api/geospatial/p5b7-5n3h?method=export&format=GeoJSON",
133
+ "format": {"type": "json", "property": "features"},
134
+ "projection": {
135
+ "type": "mercator",
136
+ "center": [-122.4194, 37.7749],
137
+ "scale": 80000,
138
+ },
139
+ "key": "nhood",
140
+ },
141
+ # City neighborhoods - New York City
142
+ "nyc-neighborhoods": {
143
+ "url": "https://data.cityofnewyork.us/api/geospatial/cpf4-rkhq?method=export&format=GeoJSON",
144
+ "format": {"type": "json", "property": "features"},
145
+ "projection": {
146
+ "type": "mercator",
147
+ "center": [-73.9857, 40.7484],
148
+ "scale": 40000,
149
+ },
150
+ "key": "ntaname",
151
+ },
152
+ # City neighborhoods - Chicago
153
+ "chicago-neighborhoods": {
154
+ "url": "https://data.cityofchicago.org/api/geospatial/bbvz-uum9?method=export&format=GeoJSON",
155
+ "format": {"type": "json", "property": "features"},
156
+ "projection": {
157
+ "type": "mercator",
158
+ "center": [-87.6298, 41.8781],
159
+ "scale": 50000,
160
+ },
161
+ "key": "pri_neigh",
162
+ },
163
+ # City neighborhoods - Los Angeles
164
+ "la-neighborhoods": {
165
+ "url": "https://data.lacity.org/api/geospatial/r3ta-u4d6?method=export&format=GeoJSON",
166
+ "format": {"type": "json", "property": "features"},
167
+ "projection": {
168
+ "type": "mercator",
169
+ "center": [-118.2437, 34.0522],
170
+ "scale": 35000,
171
+ },
172
+ "key": "name",
173
+ },
174
+ }
175
+
176
+ # Supported projections with their default settings
177
+ PROJECTIONS: dict[str, dict[str, Any]] = {
178
+ "mercator": {"type": "mercator"},
179
+ "albersUsa": {"type": "albersUsa"},
180
+ "equalEarth": {"type": "equalEarth"},
181
+ "naturalEarth1": {"type": "naturalEarth1"},
182
+ "orthographic": {"type": "orthographic"},
183
+ "stereographic": {"type": "stereographic"},
184
+ "albers": {"type": "albers"},
185
+ "conicEqualArea": {"type": "conicEqualArea"},
186
+ "equirectangular": {"type": "equirectangular"},
187
+ }
188
+
189
+
190
+ def _resolve_geo_source(
191
+ geo_source: str | None,
192
+ geo_feature: str | None = None,
193
+ ) -> tuple[str, dict[str, Any], str | dict[str, Any]]:
194
+ """Resolve a geo source name or URL to URL, format, and default projection."""
195
+ if geo_source in GEO_SOURCES:
196
+ geo_info = GEO_SOURCES[geo_source]
197
+ resolved_feature = geo_feature or geo_info["format"].get("feature")
198
+ geo_format = dict(geo_info["format"])
199
+ if resolved_feature:
200
+ geo_format["feature"] = resolved_feature
201
+ return geo_info["url"], geo_format, geo_info["projection"]
202
+
203
+ return (
204
+ geo_source or "",
205
+ {"type": "topojson", "feature": geo_feature or "features"},
206
+ "mercator",
207
+ )
208
+
209
+
210
+ def _resolve_projection(
211
+ projection_config: str | Projection | None,
212
+ default_projection: str | dict[str, Any],
213
+ fallback: str,
214
+ ) -> dict[str, Any]:
215
+ """Resolve the explicit or default projection config."""
216
+ if isinstance(projection_config, str):
217
+ return PROJECTIONS.get(projection_config, {"type": projection_config})
218
+ if isinstance(projection_config, Projection):
219
+ return to_plain_dict(projection_config)
220
+ if isinstance(default_projection, dict):
221
+ return default_projection
222
+ return PROJECTIONS.get(default_projection, {"type": fallback})
223
+
224
+
225
+ def _build_point_position_encoding(
226
+ lat_field: str | None,
227
+ lng_field: str | None,
228
+ ) -> dict[str, Any]:
229
+ """Build latitude and longitude encodings for point maps."""
230
+ encoding: dict[str, Any] = {}
231
+ if lng_field:
232
+ encoding["longitude"] = {"field": lng_field, "type": "quantitative"}
233
+ if lat_field:
234
+ encoding["latitude"] = {"field": lat_field, "type": "quantitative"}
235
+ return encoding
236
+
237
+
238
+ def _build_point_tooltip_fields(
239
+ data: list[dict[str, Any]],
240
+ *,
241
+ lat_field: str | None = None,
242
+ lng_field: str | None = None,
243
+ size_field: str | None = None,
244
+ color_field: str | None = None,
245
+ max_fields: int = 6,
246
+ tooltip_format: str,
247
+ ) -> list[dict[str, Any]]:
248
+ """Build tooltip fields for point and bubble maps."""
249
+ tooltip_fields: list[dict[str, Any]] = []
250
+
251
+ if lat_field:
252
+ tooltip_fields.append(
253
+ tooltip_entry(lat_field, "quantitative", title="Latitude"),
254
+ )
255
+ if lng_field:
256
+ tooltip_fields.append(
257
+ tooltip_entry(lng_field, "quantitative", title="Longitude"),
258
+ )
259
+ if size_field:
260
+ tooltip_fields.append(
261
+ tooltip_entry(
262
+ size_field,
263
+ "quantitative",
264
+ title=slug_to_text(size_field),
265
+ format=tooltip_format,
266
+ ),
267
+ )
268
+ if color_field:
269
+ tooltip_fields.append(
270
+ tooltip_entry(
271
+ color_field,
272
+ infer_vega_type_from_data(data, color_field),
273
+ title=slug_to_text(color_field),
274
+ ),
275
+ )
276
+
277
+ if data:
278
+ for field in data[0]:
279
+ if field in {lat_field, lng_field, size_field, color_field}:
280
+ continue
281
+ tooltip_fields.append(
282
+ tooltip_entry(
283
+ field,
284
+ infer_vega_type_from_data(data, field),
285
+ title=slug_to_text(field),
286
+ ),
287
+ )
288
+ if len(tooltip_fields) >= max_fields:
289
+ break
290
+
291
+ return tooltip_fields
292
+
293
+
294
+ def _generate_map_spec(
295
+ chart: ResolvedChart,
296
+ data: list[dict[str, Any]],
297
+ chart_type: str,
298
+ width: float | None = None,
299
+ height: float | None = None,
300
+ *,
301
+ board_style: MergedStyle,
302
+ ) -> dict[str, Any]:
303
+ """Generate geographic map specification (map, geoshape, etc.).
304
+
305
+ Creates Vega-Lite specs for geographic visualizations including:
306
+ - Filled-region maps (colored by data)
307
+ - Base geoshape maps (outline only)
308
+
309
+ Uses the _add_data_join() helper for consistent data joining across
310
+ all map types.
311
+
312
+ Args:
313
+ chart: Chart definition with geo configuration
314
+ data: List of data points to join to geographic features
315
+ chart_type: Type of map ('map', 'geoshape')
316
+ width: Optional explicit width in pixels
317
+ height: Optional explicit height in pixels
318
+
319
+ Returns:
320
+ Vega-Lite specification for geographic visualization
321
+
322
+ """
323
+ data = normalize_data_types(data)
324
+ map_config = chart.resolved_style.geoshape
325
+
326
+ # Get geo configuration from chart
327
+ geo_config = chart.geo
328
+ geo_source = chart.geo_source
329
+
330
+ # If geo_config is a dict, extract source
331
+ if isinstance(geo_config, dict):
332
+ geo_source = geo_config.get("source", geo_source)
333
+ geo_key = geo_config.get("key", "id")
334
+ geo_feature = geo_config.get("feature", None)
335
+ else:
336
+ geo_key = "id"
337
+ geo_feature = None
338
+
339
+ geo_url, geo_format, default_projection = _resolve_geo_source(
340
+ geo_source,
341
+ geo_feature,
342
+ )
343
+ projection = _resolve_projection(chart.projection, default_projection, "mercator")
344
+
345
+ color_field = effective_color_field(chart)
346
+ value_field: str | None = chart.value or color_field
347
+ lookup_field = chart.lookup
348
+
349
+ # Resolve the geoshape mark (global tier + optional family override).
350
+ charts = chart.resolved_style
351
+ geoshape_mark = resolve_mark(
352
+ charts.marks.geoshape,
353
+ map_config.marks.geoshape,
354
+ )
355
+
356
+ # Build the spec
357
+ spec = new_chart_spec(
358
+ data,
359
+ mark={
360
+ "type": "geoshape",
361
+ "stroke": geoshape_mark.stroke.color if geoshape_mark.stroke else None,
362
+ "strokeWidth": geoshape_mark.stroke.width if geoshape_mark.stroke else None,
363
+ "tooltip": True,
364
+ },
365
+ )
366
+ spec["data"] = {"url": geo_url, "format": geo_format}
367
+ spec["projection"] = projection
368
+ set_chart_dimensions(spec, width, height)
369
+
370
+ # If we have data to join, use the _add_data_join helper
371
+ if data and lookup_field and value_field:
372
+ # Use the data join helper for consistent joining logic
373
+ spec = _add_data_join(
374
+ spec=spec,
375
+ data=data,
376
+ geo_key=geo_key,
377
+ lookup_field=lookup_field,
378
+ value_field=value_field,
379
+ handle_missing=True, # Handle missing data gracefully
380
+ )
381
+
382
+ # Color scheme: chart-local style.color_scheme is pre-merged into
383
+ # map_config.color_scheme by build_resolved_style.
384
+ spec["encoding"]["color"] = {
385
+ "field": value_field,
386
+ "type": "quantitative",
387
+ "title": format_display_text(
388
+ value_field, from_slug=True, font=chart.resolved_style.legend.title.font
389
+ ),
390
+ "scale": {"scheme": map_config.color_scheme},
391
+ }
392
+
393
+ # Build tooltip from data fields
394
+ data_fields = list(data[0].keys()) if data else []
395
+ tooltip_fields: list[dict[str, Any]] = []
396
+ if lookup_field in data_fields:
397
+ tooltip_fields.append(
398
+ tooltip_entry(
399
+ lookup_field,
400
+ "nominal",
401
+ title=slug_to_text(lookup_field),
402
+ ),
403
+ )
404
+ if value_field and value_field != lookup_field:
405
+ tooltip_fields.append(
406
+ tooltip_entry(
407
+ value_field,
408
+ "quantitative",
409
+ title=slug_to_text(value_field),
410
+ format=resolve_format(
411
+ chart.resolved_style.tooltip.format,
412
+ chart.resolved_style.formats,
413
+ ),
414
+ ),
415
+ )
416
+ if tooltip_fields:
417
+ spec["encoding"]["tooltip"] = tooltip_fields
418
+ else:
419
+ # No data - render the base map with a neutral fill. Prefer the
420
+ # theme's header background if set; otherwise fall back to the
421
+ # theme background (universal default has no header fill).
422
+ _ms = board_style
423
+ spec["mark"]["fill"] = _ms.charts.table.header.background or _ms.background
424
+
425
+ set_chart_title(spec, chart.title, chart.subtitle, style=chart.resolved_style)
426
+
427
+ return spec
428
+
429
+
430
+ def _generate_point_map_spec(
431
+ chart: ResolvedChart,
432
+ data: list[dict[str, Any]],
433
+ chart_type: str,
434
+ width: float | None = None,
435
+ height: float | None = None,
436
+ *,
437
+ board_style: MergedStyle,
438
+ ) -> dict[str, Any]:
439
+ """Generate point/bubble map specification with markers at lat/lng coordinates.
440
+
441
+ Creates Vega-Lite specs for geographic point visualizations including:
442
+ - Point maps (markers at coordinates)
443
+ - Bubble maps (sized markers based on data values)
444
+
445
+ Optionally layers points over a background geoshape (state/country outlines).
446
+
447
+ Args:
448
+ chart: Chart definition with lat/lng configuration
449
+ data: List of data points with latitude/longitude fields
450
+ chart_type: Type of map ('point_map', 'bubble_map')
451
+ width: Optional explicit width in pixels
452
+ height: Optional explicit height in pixels
453
+
454
+ Returns:
455
+ Vega-Lite specification for point/bubble map
456
+
457
+ """
458
+ data = normalize_data_types(data)
459
+
460
+ # Get lat/lng field names
461
+ lat_field = chart.latitude
462
+ lng_field = chart.longitude
463
+
464
+ # Fallback to common field names if not specified
465
+ if not lat_field:
466
+ for field in ["latitude", "lat", "Latitude", "Lat", "y"]:
467
+ if data and field in data[0]:
468
+ lat_field = field
469
+ break
470
+ if not lng_field:
471
+ for field in ["longitude", "lng", "lon", "Longitude", "Lng", "Lon", "x"]:
472
+ if data and field in data[0]:
473
+ lng_field = field
474
+ break
475
+
476
+ size_field = chart.size
477
+ color_field = effective_color_field(chart)
478
+
479
+ # Get basemap (tile-layer) configuration
480
+ background_config: dict[str, Any] | None = chart.basemap
481
+ geo_source = chart.geo_source
482
+ _scatter = chart.resolved_style.scatter
483
+ point_mark = resolve_mark(
484
+ chart.resolved_style.marks.point,
485
+ _scatter.marks.point,
486
+ )
487
+
488
+ # Get projection configuration
489
+ # User-provided projection takes precedence, otherwise use geo_source default
490
+ default_projection: str | dict[str, Any] = "albersUsa"
491
+ if geo_source and geo_source in GEO_SOURCES:
492
+ default_projection = GEO_SOURCES[geo_source].get("projection", "albersUsa")
493
+ projection = _resolve_projection(chart.projection, default_projection, "albersUsa")
494
+
495
+ # Determine if we need a layered spec (background + points)
496
+ has_background = background_config or geo_source
497
+
498
+ if has_background:
499
+ # Create layered spec with background geoshape and points
500
+ return _generate_layered_point_map_spec(
501
+ chart,
502
+ data,
503
+ lat_field,
504
+ lng_field,
505
+ size_field,
506
+ color_field,
507
+ projection,
508
+ background_config,
509
+ geo_source,
510
+ width,
511
+ height,
512
+ board_style=board_style,
513
+ )
514
+
515
+ # Simple point map without background
516
+ mark_type = "circle"
517
+
518
+ mark_dict: dict[str, Any] = {"type": mark_type, "tooltip": True}
519
+ if point_mark.opacity is not None:
520
+ mark_dict["opacity"] = point_mark.opacity
521
+ spec = new_chart_spec(data, mark=mark_dict)
522
+ spec["projection"] = projection
523
+ spec["encoding"].update(_build_point_position_encoding(lat_field, lng_field))
524
+ set_chart_dimensions(spec, width, height)
525
+
526
+ # Size encoding for bubble maps
527
+ if size_field:
528
+ spec["encoding"]["size"] = {
529
+ "field": size_field,
530
+ "type": "quantitative",
531
+ "title": format_display_text(
532
+ size_field, from_slug=True, font=chart.resolved_style.legend.title.font
533
+ ),
534
+ "scale": {"range": [50, 1000]},
535
+ }
536
+ else:
537
+ spec["mark"]["size"] = point_mark.size
538
+
539
+ # Color encoding
540
+ if color_field:
541
+ # Infer type from data
542
+ color_type = infer_vega_type_from_data(data, color_field)
543
+ spec["encoding"]["color"] = {
544
+ "field": color_field,
545
+ "type": color_type,
546
+ "title": format_display_text(
547
+ color_field, from_slug=True, font=chart.resolved_style.legend.title.font
548
+ ),
549
+ }
550
+
551
+ tooltip_fields = _build_point_tooltip_fields(
552
+ data,
553
+ lat_field=lat_field,
554
+ lng_field=lng_field,
555
+ size_field=size_field,
556
+ color_field=color_field,
557
+ tooltip_format=resolve_format(
558
+ chart.resolved_style.tooltip.format, chart.resolved_style.formats
559
+ ),
560
+ )
561
+ if tooltip_fields:
562
+ spec["encoding"]["tooltip"] = tooltip_fields
563
+
564
+ set_chart_title(spec, chart.title, chart.subtitle, style=chart.resolved_style)
565
+
566
+ return spec
567
+
568
+
569
+ def _generate_layered_point_map_spec(
570
+ chart: ResolvedChart,
571
+ data: list[dict[str, Any]],
572
+ lat_field: str | None,
573
+ lng_field: str | None,
574
+ size_field: str | None,
575
+ color_field: str | None,
576
+ projection: dict[str, Any],
577
+ background_config: dict[str, Any] | None,
578
+ geo_source: str | None,
579
+ width: float | None = None,
580
+ height: float | None = None,
581
+ *,
582
+ board_style: MergedStyle,
583
+ ) -> dict[str, Any]:
584
+ """Generate layered point map with background geoshape.
585
+
586
+ Creates a Vega-Lite layered spec with:
587
+ - Layer 1: Background geoshape (state/country outlines)
588
+ - Layer 2: Point/circle markers at lat/lng coordinates
589
+
590
+ Args:
591
+ chart: Chart definition
592
+ data: Point data with lat/lng
593
+ lat_field: Latitude field name
594
+ lng_field: Longitude field name
595
+ size_field: Optional size encoding field
596
+ color_field: Optional color encoding field
597
+ projection: Projection configuration
598
+ background_config: Background layer configuration
599
+ geo_source: Built-in geo source name
600
+ width: Optional width
601
+ height: Optional height
602
+
603
+ Returns:
604
+ Layered Vega-Lite specification
605
+
606
+ """
607
+ _ms = board_style
608
+ _scatter = chart.resolved_style.scatter
609
+ resolved_point = resolve_mark(
610
+ chart.resolved_style.marks.point,
611
+ _scatter.marks.point,
612
+ )
613
+
614
+ # Determine background geo source. For the fill, fall back to the theme
615
+ # background when the theme doesn't define a header fill (universal
616
+ # default is "header rule only").
617
+ _bg_fill_default = _ms.charts.table.header.background or _ms.background
618
+ if isinstance(background_config, dict):
619
+ bg_source = background_config.get("source", geo_source)
620
+ bg_fill = background_config.get("fill", _bg_fill_default)
621
+ bg_stroke = background_config.get("stroke", _ms.border.color)
622
+ else:
623
+ bg_source = geo_source
624
+ bg_fill = _bg_fill_default
625
+ bg_stroke = _ms.border.color
626
+
627
+ geo_url, geo_format, _ = _resolve_geo_source(bg_source)
628
+ spec = new_chart_spec(data, layer=True)
629
+ spec["projection"] = projection
630
+ set_chart_dimensions(spec, width, height)
631
+
632
+ # Layer 1: Background geoshape
633
+ background_layer: dict[str, Any] = {
634
+ "data": {
635
+ "url": geo_url,
636
+ "format": geo_format,
637
+ },
638
+ "mark": {
639
+ "type": "geoshape",
640
+ "fill": bg_fill,
641
+ "stroke": bg_stroke,
642
+ "strokeWidth": 0.5,
643
+ },
644
+ }
645
+ spec["layer"].append(background_layer)
646
+
647
+ # Layer 2: Points
648
+ mark_type = "circle"
649
+ point_mark: dict[str, Any] = {"type": mark_type, "tooltip": True}
650
+ if resolved_point.opacity is not None:
651
+ point_mark["opacity"] = resolved_point.opacity
652
+ point_layer: dict[str, Any] = {
653
+ "data": {"values": data},
654
+ "mark": point_mark,
655
+ "encoding": _build_point_position_encoding(lat_field, lng_field),
656
+ }
657
+
658
+ # Size encoding
659
+ point_size = resolved_point.size
660
+ if size_field:
661
+ point_layer["encoding"]["size"] = {
662
+ "field": size_field,
663
+ "type": "quantitative",
664
+ "title": format_display_text(
665
+ size_field, from_slug=True, font=chart.resolved_style.legend.title.font
666
+ ),
667
+ "scale": {"range": [50, 1000]},
668
+ }
669
+ else:
670
+ # Use point size from config (slightly larger for map overlays)
671
+ point_layer["mark"]["size"] = int(point_size * 1.33)
672
+
673
+ # Color encoding
674
+ if color_field:
675
+ color_type = infer_vega_type_from_data(data, color_field)
676
+ point_layer["encoding"]["color"] = {
677
+ "field": color_field,
678
+ "type": color_type,
679
+ "title": format_display_text(
680
+ color_field, from_slug=True, font=chart.resolved_style.legend.title.font
681
+ ),
682
+ }
683
+ else:
684
+ point_layer["mark"]["color"] = chart.resolved_style.palette[0]
685
+
686
+ tooltip_fields = _build_point_tooltip_fields(
687
+ data,
688
+ max_fields=6,
689
+ tooltip_format=resolve_format(
690
+ chart.resolved_style.tooltip.format, chart.resolved_style.formats
691
+ ),
692
+ )
693
+ if tooltip_fields:
694
+ point_layer["encoding"]["tooltip"] = tooltip_fields
695
+
696
+ spec["layer"].append(point_layer)
697
+
698
+ set_chart_title(spec, chart.title, chart.subtitle, style=chart.resolved_style)
699
+
700
+ return spec