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,157 @@
1
+ """Shared chart title overflow helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any, Literal
7
+
8
+ from dataface.core.render.font_measurement import get_font_measurer
9
+ from mdsvg.fonts import truncate_text_precise, wrap_text_precise
10
+
11
+ TitleOverflowMode = Literal["clip", "truncate", "wrap-two", "wrap"]
12
+ DEFAULT_TITLE_OVERFLOW: TitleOverflowMode = "wrap-two"
13
+
14
+ # Match the outer Vega chart-group translate, e.g. translate(20,53)
15
+ _MAIN_GROUP_X_RE = re.compile(
16
+ r'<g fill="none" stroke-miterlimit="10" transform="translate\(([0-9.]+),'
17
+ )
18
+ # Match the title inner-group translate, e.g. translate(-71.21,-48)
19
+ _TITLE_GROUP_RE = re.compile(
20
+ r'(class="mark-group role-title"><g transform="translate\()(-?[0-9.]+)(,)'
21
+ )
22
+
23
+
24
+ def resolve_title_overflow(title_style: Any | None) -> TitleOverflowMode:
25
+ """Return the effective title overflow mode."""
26
+ raw = getattr(title_style, "overflow", None) if title_style is not None else None
27
+ if raw in {"clip", "truncate", "wrap-two", "wrap"}:
28
+ return raw
29
+ return DEFAULT_TITLE_OVERFLOW
30
+
31
+
32
+ def compute_title_limit(width: Any, padding: Any) -> int | None:
33
+ """Return the usable title width after horizontal padding."""
34
+ if not isinstance(width, (int, float)) or width <= 0:
35
+ return None
36
+
37
+ left = right = 0.0
38
+ if isinstance(padding, dict):
39
+ left = float(padding.get("left", 0) or 0)
40
+ right = float(padding.get("right", 0) or 0)
41
+ elif isinstance(padding, (int, float)):
42
+ left = right = float(padding)
43
+
44
+ limit = int(width - left - right)
45
+ return max(limit, 1) if limit > 0 else None
46
+
47
+
48
+ def prepare_title_text(
49
+ text: str,
50
+ *,
51
+ overflow: TitleOverflowMode,
52
+ limit: int | None,
53
+ font_size: float,
54
+ font_family: str | None = None,
55
+ ) -> str:
56
+ """Return title text with explicit line breaks/clipping applied."""
57
+ if not text or limit is None:
58
+ return text
59
+
60
+ measurer = get_font_measurer(font_family)
61
+ if overflow == "clip":
62
+ return truncate_text_precise(
63
+ text,
64
+ limit,
65
+ font_size,
66
+ measurer,
67
+ ellipsis=False,
68
+ )
69
+ if overflow == "truncate":
70
+ return truncate_text_precise(
71
+ text,
72
+ limit,
73
+ font_size,
74
+ measurer,
75
+ ellipsis=True,
76
+ )
77
+ if overflow == "wrap":
78
+ return "\n".join(
79
+ wrap_text_precise(
80
+ text,
81
+ limit,
82
+ font_size,
83
+ measurer,
84
+ )
85
+ )
86
+ return "\n".join(
87
+ wrap_text_precise(
88
+ text,
89
+ limit,
90
+ font_size,
91
+ measurer,
92
+ max_lines=2,
93
+ ellipsis=True,
94
+ )
95
+ )
96
+
97
+
98
+ def apply_title_overflow_to_spec(
99
+ spec: dict[str, Any],
100
+ title_style: Any | None,
101
+ *,
102
+ title_font_size: float | None = None,
103
+ title_font_family: str | None = None,
104
+ ) -> None:
105
+ """Apply title overflow handling to a Vega-Lite spec in place."""
106
+ title_block = spec.get("title")
107
+ if not isinstance(title_block, dict):
108
+ return
109
+
110
+ limit = compute_title_limit(spec.get("width"), spec.get("padding"))
111
+ if limit is None:
112
+ return
113
+
114
+ title_block["limit"] = limit
115
+ text = title_block.get("text")
116
+ if isinstance(text, str) and text:
117
+ style_font_size = (
118
+ getattr(title_style, "font_size", None) if title_style is not None else None
119
+ )
120
+ effective_font_size = float(
121
+ style_font_size
122
+ or title_font_size
123
+ or spec.get("config", {}).get("title", {}).get("fontSize")
124
+ or 18
125
+ )
126
+ result = prepare_title_text(
127
+ text,
128
+ overflow=resolve_title_overflow(title_style),
129
+ limit=limit,
130
+ font_size=effective_font_size,
131
+ font_family=title_font_family,
132
+ )
133
+ lines = result.split("\n")
134
+ title_block["text"] = lines if len(lines) > 1 else result
135
+
136
+
137
+ def fix_title_alignment(svg: str, padding_left: float) -> str:
138
+ """Move the Vega-rendered title right to ``padding_left`` when leftward drift occurs."""
139
+ main_match = _MAIN_GROUP_X_RE.search(svg)
140
+ if not main_match:
141
+ return svg
142
+
143
+ title_match = _TITLE_GROUP_RE.search(svg)
144
+ if not title_match:
145
+ return svg
146
+
147
+ main_x = float(main_match.group(1))
148
+ title_x = float(title_match.group(2))
149
+ title_svg_x = main_x + title_x
150
+
151
+ if title_svg_x >= padding_left:
152
+ return svg
153
+
154
+ new_title_x = padding_left - main_x
155
+ old = title_match.group(1) + title_match.group(2) + title_match.group(3)
156
+ new = title_match.group(1) + f"{new_title_x:.2f}" + title_match.group(3)
157
+ return svg.replace(old, new, 1)
@@ -0,0 +1,122 @@
1
+ """Shared helpers for Vega-Lite type inference from query result data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime as dt
6
+ import re
7
+ from typing import Any
8
+
9
+ DATE_LIKE_PATTERNS = [
10
+ r"^\d{4}-Q[1-4]$",
11
+ r"^Q[1-4]\s*\d{4}$",
12
+ r"^\d{4}Q[1-4]$",
13
+ r"^\d{4}-\d{2}$",
14
+ r"^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{4}$",
15
+ r"^FY\d{4}$",
16
+ r"^H[12]\s*\d{4}$",
17
+ r"^\d{4}-H[12]$",
18
+ r"^W(?:eek\s*)?\d{1,2}\s*\d{4}$",
19
+ r"^\d{4}-W\d{2}$", # ISO 8601 week: 2024-W01
20
+ r"^\d{2}/\d{4}$", # MM/YYYY: 01/2024
21
+ r"^\d{2}/\d{2}/\d{4}$", # MM/DD/YYYY: 01/15/2024
22
+ r"^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2},?\s+\d{4}$", # Mon DD, YYYY
23
+ r"^\d{4}-\d{2}-\d{2}$", # YYYY-MM-DD: 2024-01-15
24
+ r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$", # ISO 8601 naive timestamp: 2024-01-15T13:30:00
25
+ ]
26
+ DATE_LIKE_REGEXES = [
27
+ re.compile(pattern, re.IGNORECASE) for pattern in DATE_LIKE_PATTERNS
28
+ ]
29
+
30
+
31
+ def is_date_like_string(value: str) -> bool:
32
+ """Return True when a string looks like an ordered date bucket."""
33
+ return any(pattern.match(value) for pattern in DATE_LIKE_REGEXES)
34
+
35
+
36
+ # Year-leading patterns whose lexicographic ascending order equals chronological
37
+ # order. Safe to use as a window-sort field for x-cell sampling.
38
+ # Patterns NOT listed here (e.g. "Jan 2024", "01/2024", "Q1 2024") sort
39
+ # lex-incorrectly and must NOT trigger sampling — wrong cells would be selected.
40
+ # Every entry must also appear in DATE_LIKE_PATTERNS (the module-level assert
41
+ # below enforces this so the two lists cannot silently drift).
42
+ _LEX_SORTABLE_DATE_LIKE_PATTERNS = [
43
+ r"^\d{4}-Q[1-4]$", # 2024-Q1 lex == chron
44
+ r"^\d{4}Q[1-4]$", # 2024Q1 lex == chron
45
+ r"^\d{4}-\d{2}$", # 2024-01 lex == chron
46
+ r"^FY\d{4}$", # FY2024 lex == chron
47
+ r"^\d{4}-H[12]$", # 2024-H1 lex == chron
48
+ r"^\d{4}-W\d{2}$", # 2024-W01 lex == chron (ISO 8601 week)
49
+ r"^\d{4}-\d{2}-\d{2}$", # 2024-01-15 lex == chron
50
+ r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$", # naive ISO timestamp lex == chron (no tz suffix)
51
+ ]
52
+ # Guard against drift: every lex-sortable pattern must be a subset of the
53
+ # full date-like list so a future edit to DATE_LIKE_PATTERNS can't leave
54
+ # is_lex_sortable_date_like silently matching patterns that were tightened.
55
+ # Use RuntimeError (not assert) so the guard survives python -O.
56
+ if not set(_LEX_SORTABLE_DATE_LIKE_PATTERNS) <= set(DATE_LIKE_PATTERNS):
57
+ raise RuntimeError(
58
+ "_LEX_SORTABLE_DATE_LIKE_PATTERNS contains patterns not in DATE_LIKE_PATTERNS; "
59
+ "update or remove the stale entries"
60
+ )
61
+ _LEX_SORTABLE_DATE_LIKE_REGEXES = [
62
+ re.compile(p, re.IGNORECASE) for p in _LEX_SORTABLE_DATE_LIKE_PATTERNS
63
+ ]
64
+
65
+
66
+ def is_lex_sortable_date_like(value: str) -> bool:
67
+ """Return True when the string is a date-like bucket whose lex sort is chronological.
68
+
69
+ Only year-leading ISO-prefix patterns qualify. Month-name, MM/YYYY, and
70
+ quarter-first patterns sort incorrectly under string comparison and must
71
+ not be used to drive the sampling window's ascending sort.
72
+ """
73
+ return any(p.match(value) for p in _LEX_SORTABLE_DATE_LIKE_REGEXES)
74
+
75
+
76
+ def infer_vega_type_from_data(data: list[dict[str, Any]], field: str) -> str:
77
+ """Infer the most appropriate Vega-Lite type for a field."""
78
+ if not data or field not in data[0]:
79
+ return "nominal"
80
+
81
+ sample_values = [
82
+ row.get(field) for row in data[: min(10, len(data))] if field in row
83
+ ]
84
+ if not sample_values:
85
+ return "nominal"
86
+
87
+ all_numeric = True
88
+ all_temporal = True
89
+ all_date_like = True
90
+
91
+ for value in sample_values:
92
+ if value is None:
93
+ continue
94
+ if not isinstance(value, (int, float)):
95
+ all_numeric = False
96
+ if isinstance(value, (dt.date, dt.datetime)):
97
+ # date/datetime objects are always temporal; date_like too
98
+ pass
99
+ elif isinstance(value, str):
100
+ # Require an actual date-like structure: YYYY-MM-DD, YYYY/MM/DD,
101
+ # MM/DD/YYYY, or ISO timestamps. The old check (`count("-") >= 2`)
102
+ # was too broad and misidentified strings like "claude-opus-4-7"
103
+ # (3 hyphens, no digits in date positions) as temporal.
104
+ is_standard_date = bool(
105
+ re.match(r"^\d{4}[-/]\d{2}[-/]\d{2}(T[\d:.].*)?$", value)
106
+ or re.match(r"^\d{2}/\d{2}/\d{4}$", value)
107
+ )
108
+ if not is_standard_date:
109
+ all_temporal = False
110
+ if not is_standard_date and not is_date_like_string(value):
111
+ all_date_like = False
112
+ else:
113
+ all_temporal = False
114
+ all_date_like = False
115
+
116
+ if all_numeric:
117
+ return "quantitative"
118
+ if all_temporal:
119
+ return "temporal"
120
+ if all_date_like:
121
+ return "ordinal"
122
+ return "nominal"
@@ -0,0 +1,99 @@
1
+ """Chart data validation helpers.
2
+
3
+ Keeps data-shape policy separate from mechanical spec assembly.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from collections import Counter
9
+ from typing import Any
10
+
11
+ from dataface.core.compile.chart_resolved import ResolvedChart, effective_color_field
12
+ from dataface.core.errors.codes_render import DF_RENDER_BAR_DUPLICATE_ROWS
13
+ from dataface.core.render.chart.type_inference import infer_vega_type_from_data
14
+ from dataface.core.render.errors import ChartDataError
15
+
16
+
17
+ def _format_duplicate_key(fields: list[str], key: tuple[Any, ...]) -> str:
18
+ parts = [f"{field}={value!r}" for field, value in zip(fields, key, strict=False)]
19
+ return ", ".join(parts)
20
+
21
+
22
+ def _plot_key_fields(chart: ResolvedChart, data: list[dict[str, Any]]) -> list[str]:
23
+ chart_type = chart.chart_type
24
+ color = effective_color_field(chart)
25
+
26
+ if chart_type == "bar":
27
+ x_field = chart.x
28
+ y_field = chart.y if isinstance(chart.y, str) else None
29
+ if not x_field or not y_field:
30
+ return []
31
+
32
+ x_type = infer_vega_type_from_data(data, x_field)
33
+ y_type = infer_vega_type_from_data(data, y_field)
34
+ if x_type in {"nominal", "ordinal"} and y_type == "quantitative":
35
+ fields = [x_field]
36
+ elif y_type in {"nominal", "ordinal"} and x_type == "quantitative":
37
+ fields = [y_field]
38
+ else:
39
+ return []
40
+
41
+ if color:
42
+ fields.append(color)
43
+ return fields
44
+
45
+ if chart_type in {"line", "area"}:
46
+ x_field = chart.x
47
+ y_field = chart.y if isinstance(chart.y, str) else None
48
+ if not x_field or not y_field:
49
+ return []
50
+ if infer_vega_type_from_data(data, y_field) != "quantitative":
51
+ return []
52
+
53
+ fields = [x_field]
54
+ for series_field in (color, chart.shape):
55
+ if series_field:
56
+ fields.append(series_field)
57
+ return fields
58
+
59
+ if chart_type in {"arc", "pie"}:
60
+ return [color] if color else []
61
+
62
+ if chart_type in {"rect", "square", "heatmap"}:
63
+ x_field = chart.x
64
+ y_field = chart.y if isinstance(chart.y, str) else None
65
+ if x_field and y_field:
66
+ return [x_field, y_field]
67
+
68
+ return []
69
+
70
+
71
+ def validate_preaggregated_data(
72
+ chart: ResolvedChart,
73
+ data: list[dict[str, Any]],
74
+ ) -> None:
75
+ """Reject raw detail rows when a chart expects one value per plotted key."""
76
+ if not data:
77
+ return
78
+
79
+ key_fields = _plot_key_fields(chart, data)
80
+ if not key_fields:
81
+ return
82
+
83
+ counts = Counter(tuple(row.get(field) for field in key_fields) for row in data)
84
+ duplicate_keys = [key for key, count in counts.items() if count > 1]
85
+ if not duplicate_keys:
86
+ return
87
+
88
+ duplicate_preview = "; ".join(
89
+ _format_duplicate_key(key_fields, key) for key in duplicate_keys[:3]
90
+ )
91
+ chart_id = chart.id or "unknown"
92
+ field_list = ", ".join(key_fields)
93
+ raise ChartDataError.from_code(
94
+ DF_RENDER_BAR_DUPLICATE_ROWS,
95
+ chart_type=chart.chart_type.title(),
96
+ chart_id=chart_id,
97
+ field_list=field_list,
98
+ duplicate_preview=duplicate_preview,
99
+ )
@@ -0,0 +1,125 @@
1
+ """Public chart rendering entrypoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ if TYPE_CHECKING:
8
+ from dataface.core.compile.models.style.merged import MergedStyle
9
+
10
+ from dataface.core.compile.chart_resolved import ResolvedChart
11
+ from dataface.core.compile.models.chart.compiled import (
12
+ Chart,
13
+ )
14
+ from dataface.core.render.chart.pipeline import resolve_chart
15
+ from dataface.core.render.chart.renderers import RendererRegistry, build_chart_json
16
+ from dataface.core.render.chart.standard_renderer import render_standard_vega_spec
17
+ from dataface.core.render.converters.chart import render_chart_artifact
18
+
19
+ renderer_registry = RendererRegistry()
20
+
21
+
22
+ def generate_vega_lite_spec(
23
+ chart: Chart | ResolvedChart | Any,
24
+ data: list[dict[str, Any]],
25
+ width: float | None = None,
26
+ height: float | None = None,
27
+ theme: str | None = None,
28
+ resolved_style: MergedStyle | None = None,
29
+ ) -> dict[str, Any]:
30
+ """Generate a Vega-Lite spec for charts that render as Vega-Lite."""
31
+ # Synthesise a default at the public-API boundary so callers that don't
32
+ # have a face-level MergedStyle (tests, CLI tools) still get a valid spec.
33
+ # Geo/map chart types require a concrete board_style for stroke/projection
34
+ # reads; this resolves to the compiled default when the caller omits it.
35
+ if resolved_style is None:
36
+ from dataface.core.compile.config import get_config
37
+ from dataface.core.compile.models.style.merged import resolve_style as _resolve
38
+
39
+ resolved_style = _resolve(get_config().style)
40
+ resolved_chart = (
41
+ chart
42
+ if isinstance(chart, ResolvedChart)
43
+ else resolve_chart(chart, data, board_style=resolved_style)
44
+ )
45
+ if resolved_chart.chart_type == "table":
46
+ return {"data": {"values": data}}
47
+ if resolved_chart.renderer_family == "svg":
48
+ # KPI / table / spark_bar / error are custom-SVG; they have no
49
+ # Vega-Lite spec equivalent. Callers asking for a VL spec on these
50
+ # types are almost always confused — fail loudly instead of returning
51
+ # a partial spec.
52
+ raise ValueError(
53
+ f"Chart type '{resolved_chart.chart_type}' does not render to a Vega-Lite spec"
54
+ )
55
+ return render_standard_vega_spec(
56
+ resolved_chart,
57
+ data,
58
+ width=width,
59
+ height=height,
60
+ theme=theme,
61
+ resolved_style=resolved_style,
62
+ )
63
+
64
+
65
+ def render_chart(
66
+ chart: Chart | ResolvedChart | Any,
67
+ data: list[dict[str, Any]],
68
+ format: str = "json",
69
+ width: float | None = None,
70
+ height: float | None = None,
71
+ is_placeholder: bool = False,
72
+ datasets: dict[str, list[dict[str, Any]]] | None = None,
73
+ variables: dict[str, Any] | None = None,
74
+ padding: dict[str, Any] | None = None,
75
+ resolved_style: MergedStyle | None = None,
76
+ face_level: int = 1,
77
+ effective_vega_config: dict[str, Any] | None = None,
78
+ ) -> str:
79
+ """Render a chart to JSON, SVG, PNG, or PDF."""
80
+ resolved_chart = (
81
+ chart
82
+ if isinstance(chart, ResolvedChart)
83
+ else resolve_chart(chart, data, board_style=resolved_style)
84
+ )
85
+
86
+ if format == "json":
87
+ artifact = build_chart_json(resolved_chart, data, width=width, height=height)
88
+ return render_chart_artifact(
89
+ artifact,
90
+ format,
91
+ width=width,
92
+ height=height,
93
+ is_placeholder=is_placeholder,
94
+ resolved_style=resolved_style,
95
+ )
96
+
97
+ render_data = data
98
+ if is_placeholder and not data:
99
+ from dataface.core.render.placeholder import generate_placeholder_data
100
+
101
+ render_data = generate_placeholder_data(
102
+ resolved_chart.chart_type, resolved_chart
103
+ )
104
+
105
+ artifact = renderer_registry.render(
106
+ resolved_chart,
107
+ render_data,
108
+ width=width,
109
+ height=height,
110
+ is_placeholder=is_placeholder,
111
+ datasets=datasets,
112
+ variables=variables,
113
+ padding=padding,
114
+ resolved_style=resolved_style,
115
+ face_level=face_level,
116
+ effective_vega_config=effective_vega_config,
117
+ )
118
+ return render_chart_artifact(
119
+ artifact,
120
+ format,
121
+ width=width,
122
+ height=height,
123
+ is_placeholder=is_placeholder,
124
+ resolved_style=resolved_style,
125
+ )