Architecture
This document describes the technical architecture of EChartify.
Data Flow
┌─────────────────────────────────────────────────────────────────┐
│ TypeScript-First Data Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ AI Assistant / User │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Chart IR │ ◄── Non-polymorphic JSON │
│ │ (JSON) │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ TypeScript │ ──► │ JSON Schema │ ──► │ Go Types │ │
│ │ Zod Schemas │ │ (Generated) │ │ (Secondary) │ │
│ │ (SOURCE) │ └──────┬──────┘ └─────────────┘ │
│ └──────┬──────┘ │ │
│ │ ▼ │
│ │ ┌─────────────┐ │
│ │ │ Backend │ │
│ │ │ Validation │ │
│ │ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Compiler │ │
│ │ (TypeScript)│ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ ECharts │ │
│ │ Option │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Design Principles
- TypeScript/Zod schemas are the source of truth - Canonical IR definition in TypeScript
- Non-polymorphic IR - Single shape for all chart types
- Compiler handles complexity - Polymorphism resolved at compile time
- Layered architecture - Separate data, geometry, encoding, style
- Generated artifacts - JSON Schema and Go types derived from Zod
Why Non-Polymorphic?
Apache ECharts uses a polymorphic option object:
// ECharts: Shape changes based on type
{
series: [
{ type: "line", data: [...], smooth: true },
{ type: "pie", data: [...], radius: "50%" } // Different structure!
]
}
This causes problems:
- AI generation errors - LLMs mix up fields between chart types
- Schema complexity - JSON Schema requires
oneOf/anyOfdiscriminators - Type degradation - Go types become
interface{}for union types
EChartify solves this with uniform structure:
{
"marks": [
{ "id": "line", "geometry": "line", "encode": { "x": "a", "y": "b" }, "smooth": true },
{ "id": "pie", "geometry": "pie", "encode": { "value": "v", "name": "n" } }
]
}
All marks have the same shape. The geometry field is an enum, not a discriminator that changes structure.
Compilation Strategy
The compiler transforms IR → ECharts options:
Mapping Rules
| IR Concept | ECharts Equivalent |
|---|---|
ChartIR.title |
option.title.text |
ChartIR.datasets |
option.dataset |
ChartIR.marks |
option.series |
ChartIR.axes |
option.xAxis / option.yAxis |
Mark.geometry |
series.type |
Mark.encode |
series.encode |
Mark.style |
series.itemStyle / series.lineStyle |
Geometry → Series Type
| IR Geometry | ECharts Type | Notes |
|---|---|---|
line |
line |
|
bar |
bar |
|
pie |
pie |
Uses value/name encoding |
scatter |
scatter |
|
area |
line |
With areaStyle |
Data Type Coercion
All dataset values are stored as strings for schema compliance:
{
"columns": [
{ "name": "month", "type": "string" },
{ "name": "sales", "type": "number" }
],
"rows": [
["Jan", "1000"],
["Feb", "1500"]
]
}
The compiler parses string values to numbers based on column type metadata.
Project Structure
echartify/
├── ts/ # TypeScript (SOURCE OF TRUTH)
│ ├── src/
│ │ ├── schema/ # Zod schemas (canonical)
│ │ │ ├── chartir.ts
│ │ │ ├── dataset.ts
│ │ │ ├── mark.ts
│ │ │ └── ...
│ │ ├── compiler/ # IR → ECharts compiler
│ │ │ └── compile.ts
│ │ └── index.ts
│ ├── scripts/
│ │ └── generate-schema.ts # JSON Schema generator
│ └── tests/ # 53 tests
│
├── schema/ # Generated JSON Schema
│ ├── schema.go # Go embed
│ └── chartir.schema.json
│
├── chartir/ # Go IR types (secondary)
│ ├── chartir.go
│ ├── dataset.go
│ └── ...
│
└── examples/ # Example IR documents
Schema Generation
JSON Schema is generated from Zod using zod-to-json-schema:
This produces schema/chartir.schema.json which:
- Passes
schemalint lint --profile scalefor static type compatibility - Contains no
oneOf/anyOfdiscriminators - Can be used for validation in any language
Validation Strategy
Frontend (TypeScript)
import { chartIRSchema, compile } from "@grokify/echartify";
const result = chartIRSchema.safeParse(jsonData);
if (result.success) {
const option = compile(result.data);
}
Backend (Go)
import "github.com/grokify/echartify/chartir"
var ir chartir.ChartIR
json.Unmarshal(data, &ir)
// Use embedded schema for additional validation if needed
Dependencies
TypeScript
| Package | Purpose |
|---|---|
zod |
Runtime validation schemas |
zod-to-json-schema |
JSON Schema generation |
echarts |
Type definitions |
Go
| Package | Purpose |
|---|---|
github.com/invopop/jsonschema |
Schema generation (optional) |
Test Coverage
| Component | Tests | Coverage |
|---|---|---|
| TypeScript Schema | 19 | Enum values, validation, helpers |
| TypeScript Compiler | 22 | Geometry mapping, style compilation |
| TypeScript Examples | 12 | All example files |
| Go Types | 8 | JSON marshaling, enum coverage |
| Go Schema | 2 | Schema embedding |
| Total | 63 |
Future Considerations
- Multi-axis support - Charts with multiple Y axes
- Theme integration - Theme references vs inline styles
- Additional geometries - Radar, heatmap, treemap
- Go compiler - Full compilation in Go for backend rendering