drawspec

Declarative diagram spec to clean, themeable SVG. Thirteen diagram kinds, no coordinates in the format, writable by an LLM.

View the Project on GitHub nuncaeslupus/drawspec

What the nine kinds could not draw

drawspec exists because 89 study diagrams were written by hand in SVG and a human reviewing them found the same faults over and over — arrow heads with no shaft, lines that miss their box, text outside its box, a different type size in every figure. Those faults are what the nine kinds and the fit engine are for.

This document asks the other question. Not are the drawings correct, but could drawspec have drawn them at all. It is an inventory of the same 89 originals against the nine kinds, and of the gap that inventory showed.

All five gaps are now closed. group, matrix, curve, quadrant and funnel are implemented, each with a reference drawing in docs/reference/ and tests that fail without it. The counts below are the state before that work — they are what the decision was made on, and they are left as they were so the reasoning stays readable. Each section says what it turned into.

Where the originals are. They are the <svg> blocks inline in the opos temario, extracted to originals/ (gitignored — they are study material, not source, and they belong to the other project). originals/index.json carries each one’s title, the reviewer’s note, its element counts, and the kind this document assigns it.


The count

68 of the 89 are drawable by a kind drawspec already has. 21 are not.

Kind Originals Have it?
flow 33 yes
cycle 9 yes
group 6 now yes
stack 5 yes
matrix 5 now yes
timeline 4 yes
tree 4 yes
columns 4 yes
pyramid 4 yes
rings 4 yes
curve 3 now yes
quadrant 2 now yes
funnel 2 now yes
picture 2 out of scope
chart 1 yes
spans 1 no

The assignment is judgement, made by rendering all 89 and looking at them; it is recorded per-diagram in originals/index.json under wants. The long tail is real but it is a tail: flow alone accounts for a third of the corpus, and the nine kinds between them cover more than three quarters of it.


The five kinds that are missing

Ordered by how many originals need them.

1. group — a box that contains boxes (6) — done

The single largest gap, and the least exotic. A Kubernetes control plane holding five components; sector públic holding three peers and a sub-frame holding two more; a medallion architecture holding bronze, silver and gold. The container has its own caption, it nests, and edges cross its boundary to reach what is inside.

This was probably not a kind, and it turned out not to be one. It is a container over the graph kinds: a flow or a tree gains one by naming members, and everything else about the drawing is unchanged.

It also turned out to be mostly already there. The schema had declared groups since v1 and the theme had declared a dashed, unfilled group role since v1 — nothing had ever drawn either. What was missing between them is drawspec.kinds.containers, and three decisions it had to make:

See docs/reference/flow-groups.json for the reference drawing.

Originals: estado__administracion-publica, tic__devops-cicd-contenedores-y-kubernetes, tic__arquitectura-de-datos-dw-lake-lakehouse, tic__practicas-itil-de-entrega-de-servicios, tic__trabajo-documental-sharepoint-y-onedrive, tic__practicas-itil-de-soporte-al-servicio.

2. matrix — rows against columns (5) — done

The shared-responsibility model: three columns (IaaS, PaaS, SaaS), a stack of cells in each, and each cell filled to say who manages it. RAID block layouts. The TCP/IP encapsulation bands, where each layer’s row is segmented and the segments line up down the figure.

What it needed beyond columns: both headings, cells that span, and a fill vocabulary. The vocabulary arrived with the chart marks and is shared with them ([mark] fills), which is why this came second.

The interesting part was what an author is allowed to say. fill is in the schema’s rejection table — appearance is a property of the role, not of the element — and a matrix does not get an exemption just because its whole content is a comparison. So a cell names a group, and the theme turns groups into fills in order of first mention. The author says which cells are the same kind of cell; drawspec decides what that looks like. The test suite caught the first attempt, which had the author naming the fill.

Two more decisions: the matrix’s extent is derived from its cells, so a cell spanning three columns makes a three-column matrix and there is no “reaches outside” error to write — only a negative index is refusable. And a spanning cell is charged to the last row it covers, so a two-row cell asks for the two rows together rather than making each of them as deep as itself.

The reviewer’s note on the original was the warning to design against: “the hatching is too strong and client is hard to read”. Every pattern is drawn faint; the reference drawing has body text over both fills.

See docs/reference/matrix-responsibility.json.

Originals: tic__arquitecturas-de-seguridad-en-la-nube, tic__computacion-hibrida-iaas-paas-saas, tic__sistemas-san-y-raid, tic__tcp-ip-v4-y-mpls, tic__itil-v4-conceptos-y-cadena-de-valor.

3. curve — a shape that is not data (3) — done

The Gartner hype cycle, the EVM S-curves, the sprint burn-down. These look like charts and are not: there are no numbers behind the hype cycle, only a named shape with five labelled waypoints on it. The burn-down has two series where one is a straight ideal and the other is the real line, each labelled where it ends.

It is its own kind rather than a chart option, and the reason is the one thing these three have in common: there are no numbers. So there are no ticks — drawing them would invite a reader to measure a shape somebody sketched — and a waypoint carries a name where a data point would carry a value.

Two decisions:

A curve’s name is drawn where it ends, and the names go down before the waypoint labels so a label at the end of a curve never lands on that curve’s own name.

See docs/reference/curve-hype.json and docs/reference/curve-burndown.json.

Originals: tic__prospeccion-de-soluciones-tic, tic__pmbok-rendimiento-riesgos-y-cierre, barcelona__scrum-bit.

4. quadrant — two named axes, items placed in the plane (2) — done

The Thomas-Kilmann grid: cooperation against assertiveness, five named positions. Tuckman’s model on a different pair of axes. Small, self-contained, and nothing in the nine comes close — chart plots series against scales, not labelled points against named directions.

It lives in the chart family and reuses its axis furniture unchanged, which is what made it small. Three decisions of its own:

It also turned up a real bug in the chart: _segment_meets_box sampled a segment at a fixed twenty-four points, which is fine for a chart’s short segments and steps clean over a label when the segment is a midline running the height of the plot. Sampling is now bounded by a spacing smaller than a label box.

Originals: tic__pmbok-liderazgo-equipo-y-recursos__0 and __1. See docs/reference/quadrant-conflict.json.

5. funnel — the pyramid lying down (2) — done

An innovation funnel with three gates; a sales funnel from lead to account. A tapering band divided into stages, captioned at both ends, with the dividers drawn differently from the outline.

It was as close to pyramid as it looked: same trapezoid, same constant progression, turned a quarter turn. What changes is which way the shape gives when the text does not fit. A pyramid narrows to its apex, so its base is what the labels buy; a funnel narrows to its mouth, so what they buy is its depth — and unlike the pyramid it fills the canvas width, because a funnel that did not would be a wedge in a corner.

The gates are drawn in the theme’s weak edge role rather than as the shared sides of separate polygons. A gate is a threshold, not a wall: the band is continuous and something passes through it, which is what a stage boundary in a funnel means and what a solid line would deny.

See docs/reference/funnel-innovation.json.

Originals: tic__gestion-de-la-innovacion-sgi__1, tic__soluciones-crm.

And one that may not deserve a kind

spans — an axis of instants with nested labelled brackets over it (RPO, RTO, WRT, MTD in the disaster-recovery diagram). One original. It is a real shape and nothing draws it, but one is one; it may be better served by irregular timeline spacing plus a bracket annotation than by a kind of its own.

Out of scope, and worth saying so

Two originals are pictures: overlapping Wi-Fi cells, and one parcel of land drawn vector against raster. They are illustrations of a physical thing, not diagrams of a structure. drawspec should not try to draw them — a declarative document whose author has no coordinates is the wrong tool for a picture, and pretending otherwise is how a diagram language acquires an escape hatch that eats it.


Features the covered ones still want

The 68 are drawable, but several want something the kind does not have yet. These are cheaper than kinds and some are nearly free:

Want Where it shows Kind
Emphasis on a node or an edge done — the strong edge role, beside the emphasis node role flow
A label inside a bar the five stars of open data, each in its own column columns
A caption under the axis “depth of change in the organisation” timeline
Irregular spacing done — give every entry an at timeline
A dashed series, a label at the end of a line ideal against real chart
Bars, areas, stacked marks done — mark: "bar" / "area", and stack chart
A fork and a join components in series against in parallel flow
Straight edges where the mesh is the message spine-leaf, every leaf to every spine flow

That last one is worth its own sentence, because it contradicts a rule. The review’s rule 5.3 is “right angles, not diagonals”, and it is right: a diagonal arriving at a box at a closed angle looks wrong, and orthogonal routing is why drawspec’s flow charts read cleanly. But in the spine-leaf diagram the twelve straight diagonals are the content — “every leaf connects to every spine” is legible as a lattice and illegible as twelve orthogonal routes threading a comb. A rule that good still has an exception, and the exception should be a declared edge style rather than a defeat.


What this suggests doing first

  1. group. Done — see above.
  2. Chart marks — done. The library question was the real content of this item, so here is the answer and its reasons.

    Keep hand-rolling. Not because writing marks is easy, but because nothing an established library emits can pass through this pipeline. drawspec’s emitter is the only styling point and it enforces the embedding contract: no <style> element, namespaced ids, only theme-declared colours, no embedded or referenced font, and a role on every primitive. matplotlib, plotly and pygal each emit their own complete SVG with their own font handling and their own style block — wrapping one means either abandoning the Scene seam for charts, or post-processing foreign SVG, which is more work than the marks and fails in more ways.

    What a library really sells is statistical plotting, interactivity and thirty chart types. drawspec wants none of those. What it wanted was a mark abstraction so that the next mark is additive rather than a special case, and that is what this is: mark: "line" | "bar" | "area", plus stack.

    The one thing worth borrowing was the fill vocabulary, and it is now in the theme as [mark] fills — an ordered sequence of patterns, distinct by construction, that a mark takes by its own index. Which is exactly what matrix needs next.

  3. matrix. Done — and the greyscale rule turned out to be the easy half. The hard half was keeping the author out of the appearance.

All five are done — group, matrix, curve, quadrant, funnel — along with spans’ cousin still open and the two pictures still declined. The count at the top of this document is the state before that work; what is left is the features table above and the one limitation below.

One limitation found on the way and worth writing down: a chart series whose highest and lowest points sit exactly on the plot’s edges can never label them — above the top point is outside the plot, and below it crosses the curve — so the all-or-nothing rule drops that series’ labels entirely. The fix is a vertical margin on the scale, the way quadrant already has one. It is not done here because it changes every existing chart drawing, and those have been reviewed.


Found later, by drawing the corpus rather than sorting it

This document asked could drawspec draw them at all, and answered by kind. It could not have found what follows, because the answer there was yes and the drawing still came out wrong.

An ascending scale — closed by rise

Migrating the corpus for real turned up two originals that draw a scale by making each box taller than the last:

Original Geometry Drawn as What the document itself says
The five stars of open data heights 30·60·90·120·150 columns each star adds a condition to the one before it
Degrees of digital transformation heights 60·75·100, under an axis labelled depth of change timeline each degree changes more than the last

columns gave five boxes of one height; timeline gave 51.7 · 51.7 · 66.55, and the third only because its label took an extra line.

Nothing caught it. Not the word check — every word was present. Not the collision check — nothing crossed anything. Not the clipping check — everything was on the canvas. Those three answer is the text there and readable, and here the text was fine and the geometry was the message. The only thing that found it was putting the old drawing beside the new one and looking.

That is the general lesson, and it is worth more than the field: a kind that can hold every word of an original is not thereby the right kind for it. The inventory below sorts by what a kind can hold. It cannot sort by what a kind says.

rise closes both, and the second one is worth the correction. This paragraph first said an ascending timeline was not the same field — a timeline’s boxes sit on an axis, so what rises there is a label above a tick and not a column from a baseline. Writing the code showed that was a distinction without a difference: a timeline’s labels already all end at the axis, so the axis is the common baseline and the ascent is the same arithmetic. One helper serves both, and the only thing each kind decides is where its floor is.


The inventory

Every original, the kind that would draw it, and the reviewer’s note where there is one. * marks a kind drawspec does not have.

A struck-through kind is one this survey got wrong and the redraw corrected. Fourteen of the eighty-nine moved — one in six. The survey was made by counting shapes, and a diagram’s shapes are not always what it is saying: three boxes in a row were called columns and turned out to be a stack, a chart and a matrix; six activities joined by chords were called a matrix and are a graph; a grid of customer-against-provider was called a matrix and is a proportion. The lesson is the one the reviewer taught on the shared-responsibility model: read what the original is saying before choosing the kind. group is not counted as a move — it was never a kind, only flow carrying a groups field.

A struck-through kind is one this survey got wrong and the redraw corrected — the survey was made by counting shapes, and a diagram’s shapes are not always what it is saying. The comparison page shows both, so a reclassification is visible rather than quietly applied.

Original Wants Reviewer’s note
tic__pmbok-enfoques-y-ciclo-de-vida chart  
cataluna__estatut-autonomia-catalunya columns stack  
estado__funcion-publica-y-trebep columns chart Margen inferior en cajas. “máxim 6 ANYS” está dentro del recuadro. Sin contexto no lo entiendo mucho, no sé si cada caja superior encaja con una inferior?
tic__datos-abiertos-y-reutilizacion columns Sólo la última queda con las estrellas encima del título. A lo mejor podrían ir todas así.
tic__dominios-de-datos-data-mesh columns matrix Las cajas no están alineadas. Las de Domini sobresalen por la derecha.
barcelona__scrum-bit *curve Es un poco feo, pero no sé muy bien por qué. Lo de ideal y real está en sitios extraños.
tic__pmbok-rendimiento-riesgos-y-cierre *curve Es un gráfico muy raro, los textos se solapan, hay flechas con sólo punta, no hay texto para el eje vertical… Revisar entero.
tic__prospeccion-de-soluciones-tic *curve Puntos fuera de la línea y texto atravesando líneas. El texto “productivity\nPlateau of” queda cortado y creo que el texto esperado sería “Plateau of productivity”. No quedan muy claros los ejes.
barcelona__estrategia-municipal-de-algoritmos-y-datos cycle flow “3. Contractació” (y todos en general) deberían ir centrados verticalmente?
estado__haciendas-locales-y-presupuestos cycle Fatal en cuanto a márgenes y flechas y texto que no cabe. Revisar completo.
tic__automatizacion-orquestacion-y-rpa cycle Centrado verticalmente. Sólo hay una flecha con cabeza, no sé si es lo esperado.
tic__certificados-digitales-y-eidas cycle flow Márgenes y no queda claro dónde van “Signant” y “Verificador”
tic__gestion-de-incidentes-de-ciberseguridad cycle Las flechas están todas mal. Como parece ser un ciclo, entiendo que tratan de simular un círculo, pero no aciertan. La frase “si hi ha indicis nous” queda cortada y no se ve entera, además de atravesar la línea.
tic__gestion-de-la-innovacion-sgi__0 cycle matrix Flechas sin palo.
tic__gestion-del-riesgo-en-itil-v4 cycle Flechas con palo demasiado corto.
tic__gestion-financiera-de-los-servicios cycle Si trata de ser un ciclo, OK, pero las flechas cruzan líneas y las curvas son extrañas. Valorar ángulos de 90 grados.
tic__medicion-y-mejora-continua cycle Las flechas no acaban en la caja.
barcelona__municipio-y-regimen-especial-de-barcelona flow Creo que las frases deberían empezar en mayúscula. Además, estar centradas verticalmente. Y “Presideix la Commissió…” no cabe en la caja.
barcelona__prevencion-de-riesgos-y-lengua-catalana flow stack Puede que esté hecho a propósito, pero las flechas no tienen palo. Preferiría que lo tuvieran o bien que sean otro tipo de flechas.
estado__administracion-electronica flow  
estado__administracion-general-del-estado flow El texto de “Subdelegat del Govern” tiene interlineado irregular y no tiene margen inferior suficiente.
estado__buen-gobierno flow “Reclamació…” está debajo de todo y hay flechas que cruzan otras cajas. Si hace falta, haz el esquema vertical con flechas hacia abajo.
estado__comunidades-autonomas flow Margen inferior en “Competències…”.
estado__cortes-defensor-y-tribunal-de-cuentas flow Mismo problema de líneas atravesando. Mejor esquema en vertical. Margen inferior en “Veto s’aixeca…”.
estado__el-gobierno flow Flechas sin palo, Investit debería ir centrado verticalmente. Márgenes inferiores y texto que se sale.
estado__poder-judicial flow  
tic__administracion-electronica-y-servicios-digitales flow Triángulo raro en “Accés al contingut?”. Texto que se solapa en “No, en 10 dies naturals”.
tic__analitica-de-datos-y-business-intelligence flow  
tic__arquitectura-de-microservicios flow No sé de dónde debe salir la caja de “API Manager”
tic__arquitecturas-de-seguridad-on-premise flow Una flecha sin palo.
tic__catalogo-de-servicios-y-sla flow Líneas curvas extrañas, tratemos de hacerlas con ángulos y líneas rectas.
tic__centros-de-proceso-de-datos flow “Servidors…” quedan con el texto muy justo.
tic__copias-de-seguridad-3-2-1-y-ransomware flow  
tic__gestion-centralizada-de-endpoints flow  
tic__gestion-de-capacidad-y-disponibilidad flow  
tic__gestion-de-datos-en-la-administracion flow El subrayado de NIF (si es que hace falta) está muy pegado al texto.
tic__gestion-de-identidades flow Flechas curvas mejor con ángulos rectos. El texto de “1” se solapa.
tic__gestion-de-la-demanda-tic flow Flechas sin palo.
tic__low-code-y-no-code flow  
tic__planificacion-estrategica-tic flow  
tic__plataforma-de-observabilidad flow La letra es muy pequeña.
tic__pmbok-gestion-del-trabajo flow  
tic__pmbok-planificacion-integrada flow Líneas que no tocan cajas. Entiendo que “C” no está en negrita a propósito.
tic__pmbok-valor-y-adaptacion flow Texto muy pequeño.
tic__rgpd flow  
tic__sap-basis-y-s4hana flow “transport” queda tapado por la flecha las dos veces. Una de las flechas no tiene cabeza.
tic__sap-fi-y-ea-ps flow  
tic__sap-hcm flow Textos en cursiva se cruzan con flechas y cajas.
tic__soluciones-erp flow  
tic__trabajo-colaborativo-microsoft-365 flow Aquí por ejemplo has usado líneas con ángulos rectos. Me gusta. Pero a diferencia del resto de los esquemas, las puntas de flecha no tocan las cajas de destino.
tic__gestion-de-la-innovacion-sgi__1 *funnel Flechas sin palo.
tic__soluciones-crm *funnel Pirámide de forma rara. Texto “Compte i contacte” se solapa con las líneas (no cabe).
estado__administracion-publica group tree Margen inferior e interlineado en b).
tic__arquitectura-de-datos-dw-lake-lakehouse *group “Ingesta” no cabe. “format” debería ser “Format”? Líneas entre las tres cajas principales con distinta longitud.
tic__devops-cicd-contenedores-y-kubernetes *group Comandos en monospace?
tic__practicas-itil-de-entrega-de-servicios *group La línea que sale de “Disseny del servei” va a “Transició del servei”, mientras que la que llega a “Operació del servei” no viene de “Transició del servei” sino de un item interno, “Revisió posterior”. Sólo comprobar si es correcto. La caja de debajo (“CMDB…”) debería estar centrada con “Transició del servei o con todo el gráfico? Ahora no acaba de estar centrada del todo con nada.
tic__practicas-itil-de-soporte-al-servicio *group Yexto muy pequeño. Alguna flecha con poco palo. La palabra “excepció” no se sabe si es de la línea entre “Service Desk” y “Petició de servei” o de la línea entre “Esdeveniment” y “Incident” (o de las dos).
tic__trabajo-documental-sharepoint-y-onedrive *group  
tic__arquitecturas-de-seguridad-en-la-nube matrix chart Las líneas de rayado son demasiado fuertes y cuesta leer “client”. Además, hay como unos separadores entre las tres cajas que no se respetan ni por las líneas generales ni por el rayado.
tic__computacion-hibrida-iaas-paas-saas *matrix Mismo problema que antes en cuanto al rayado y las cajas separadas. A lo mejor “Gestiona el client” y “Gestiona el proveïdor” deberían ir al revés.
tic__itil-v4-conceptos-y-cadena-de-valor matrix flow No acabo de entender este esquema: las líneas cruzan cajas, hay dos posibles flechas sin palo, el texto se sale de las cajas y es muy grande y en negrita (o con sombra, no sé).
tic__sistemas-san-y-raid *matrix  
tic__tcp-ip-v4-y-mpls *matrix  
tic__redes-wifi *picture Todos los AP quedan cruzados con el punto. La imagen queda cortada por abajo. El texto “solapament 15-20 %” no sé si está donde toca. Entiendo que el dibujo con los solapamientos sí es correcto.
tic__servicios-digitales-geograficos *picture Bello. Sólo el borde derecho del “Ràster” no ha quedado lo suficientemente gordo (o se sale de la imagen).
barcelona__plan-de-igualdad-y-proteccion-de-datos pyramid Irónicamente, cuanto más corto es el espacio, más largo es el texto. Si no cabe, puedes poner el 1 en dos líneas.
estado__fuentes-del-derecho-publico pyramid  
tic__pruebas-de-software pyramid Pirámide de forma extraña.
tic__servicio-de-atencion-al-usuario pyramid Pirámide de forma rara, texto pequeño. Revisar si el texto es correcto.
tic__pmbok-liderazgo-equipo-y-recursos__0 quadrant flow Puntas de flecha raras.
tic__pmbok-liderazgo-equipo-y-recursos__1 *quadrant “Suavitzar / acomodar” atraviesa línea, y “Retirar / eludir” casi también.
tic__aplicaciones-nativas-en-la-nube rings Los textos de Cloud, Clúster y Contenidor deberían ir más abajo. Concretamente, Contenidor casi toca la línea.
tic__gestion-documental-y-archivo-electronico rings  
tic__gobernanza-en-itil-v4 rings Textos que cruzan líneas. Posible falta de un círculo concéntrico.
tic__inteligencia-artificial-riesgos-y-etica rings Todos los textos atraviesan su propia línea.Valorar otro tipo de esquema o letra más pequeña, porque los textos son muy largos.
tic__drp-y-bcp *spans  
estado__constitucion-espanola stack A lo mejor un poco más de margen vertical.
tic__brm-y-gestion-de-expectativas stack Flechas un poco raras, no sé qué significan.
tic__calidad-del-software stack columns  
tic__nube-soberana-y-administracion-publica stack flow  
tic__sap-trm stack Flecha sin palo, márgenes inferiores escasos, entre las dos últimas cajas no hay texto…
estado__acceso-al-empleo-publico timeline Es raro partir una frase por cualquier lado y poner una parte con letra más pequeña. En el esquema siguiente tiene sentido, pero no aquí. Todo el texto debería ir arriba. No sé si a), b), c) y d) son necesarios, a no ser que vengan así en el texto.
estado__acto-administrativo timeline  
tic__pmbok-interesadas-y-comunicacion timeline  
tic__transformacion-digital timeline  
barcelona__carta-municipal tree Margen inferior en “Comissió de Govern”.
tic__despliegue-de-aplicaciones-sccm tree Las líneas no tocan las cajas.
tic__esquema-nacional-de-seguridad ~~tree pyramid~~ pyramid Parece como si las líneas no llegaran a las cajas inferiores.
tic__internet-arquitectura-protocolos-dns tree El texto “13 conjunts…” toca líneas y cajas, no queda muy claro dónde debe ir.