(raw: unknown)
| 137 | } |
| 138 | |
| 139 | export function normalizeChartSpec(raw: unknown): ChartSpec { |
| 140 | if (!isRecord(raw)) throw new ChartValidationError('chartSpec must be an object') |
| 141 | if (raw.version !== 1) throw new ChartValidationError('chartSpec.version must be 1') |
| 142 | if (typeof raw.type !== 'string' || !CHART_TYPES.has(raw.type as ChartType)) { |
| 143 | throw new ChartValidationError('chartSpec.type must be one of: bar, line, pie, heatmap') |
| 144 | } |
| 145 | if (typeof raw.title !== 'string' || raw.title.trim().length === 0) { |
| 146 | throw new ChartValidationError('chartSpec.title must be a non-empty string') |
| 147 | } |
| 148 | if (!isRecord(raw.encoding)) throw new ChartValidationError('chartSpec.encoding must be an object') |
| 149 | |
| 150 | const spec = raw as unknown as ChartSpec |
| 151 | switch (spec.type) { |
| 152 | case 'bar': |
| 153 | case 'line': |
| 154 | asFieldName(spec.encoding.x, 'encoding.x') |
| 155 | asFieldName(spec.encoding.y, 'encoding.y') |
| 156 | break |
| 157 | case 'pie': |
| 158 | asFieldName(spec.encoding.label, 'encoding.label') |
| 159 | asFieldName(spec.encoding.value, 'encoding.value') |
| 160 | break |
| 161 | case 'heatmap': |
| 162 | asFieldName(spec.encoding.x, 'encoding.x') |
| 163 | asFieldName(spec.encoding.y, 'encoding.y') |
| 164 | asFieldName(spec.encoding.value, 'encoding.value') |
| 165 | break |
| 166 | } |
| 167 | |
| 168 | return { |
| 169 | ...spec, |
| 170 | title: spec.title.trim(), |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | export function inferChartDataset(rows: Record<string, unknown>[], declaredFields?: ChartField[]): ChartDataset { |
| 175 | const declared = new Map((declaredFields ?? []).map((field) => [field.name, field])) |
no test coverage detected