Saltar al contenido principal

El generador pattern

Se usa cuando una señal tiene una forma que no se puede describir con una fórmula — demanda por hora, una onda demográfica, la captura de pantalla de un gráfico bursátil, cualquier línea «serpenteante». Usted dibuja la figura, y su forma se estira a lo largo de todas las filas: el eje horizontal es el número de fila (de la primera a la última) y el eje vertical es el valor. El dibujo puede ser diminuto; las filas pueden contarse por millones.

Nada se pre-renderiza a un archivo. La figura se lee cuando arranca la corrida y cada fila calcula su propio punto sobre ella, así que un millón de filas no cuesta más memoria que diez.

Las salidas de ejemplo de abajo son ilustrativas y pueden variar un poco según la versión del núcleo; lo que se garantiza es la forma.

Por qué dibujar una forma en vez de elegir una distribución

Las distribuciones ya hechas (normal, poisson, …) tienen una forma fija: una campana, una cola que decae. Una señal real muchas veces necesita una forma que no está en esa lista: dos jorobas donde la de la derecha es más alta, una meseta con un pico al final, demanda que sube hacia el mediodía y cae de noche. De normal no se sacan «dos jorobas de distinta altura». pattern quita ese límite: usted dibuja la silueta y ella se estira sobre las filas tal cual.

Cómo se activa

Cada <gen> con type="pattern" toma exactamente una fuente de forma. La normal es un archivo: dibuje en cualquier editor o tome la captura de un gráfico, y entrégueselo:

<!-- un dibujo hecho por usted: SVG o PNG -->
<gen type="pattern" src="chart.svg" y_range="0..40"/>

Para una forma lo bastante simple como para teclearla, el mismo generador acepta puntos en línea: una sola línea con points, o una banda con upper / lower:

<gen type="pattern" points="0,5 20,40 40,8 60,45 80,15 100,5" y_range="0..40"/>
<gen type="pattern" upper="0,20 50,40 100,20" lower="0,5 50,10 100,5" y_range="0..40"/>
AtributoQué define
srcUn archivo SVG o PNG — la forma habitual de dar la figura
pointsPares x,y tecleados en el config en vez de un archivo: x a lo ancho, y la altura
upper / lowerDos curvas de borde tecleadas en el config — un corredor
modesignal (por omisión) — una trayectoria; density — una distribución
y_rangeObligatorio. min..max — en qué se convierten el piso y el techo del lienzo del dibujo
fitbajo..alto — dónde cae un dibujo leído de src (abajo)
interplinear (por omisión) / smooth / step — el comportamiento de la línea entre puntos
spreadDispersa cada fila en ±N, en unidades de y_range (por omisión 0 — línea exacta)
ink_threshold0..1 — qué tan oscuro debe ser un píxel del PNG para contar como tinta (por omisión 0.5)
decimalsDígitos después del punto decimal (por omisión 0)

src — la figura es el config

Se leen dos formatos, y los dos terminan siendo internamente lo mismo: un contorno vectorial.

  • SVG — se lee por su geometría real: <path>, <polyline>, <line>, <polygon>, <rect> (esquinas redondeadas incluidas), <circle>, <ellipse>. Las curvas de Bézier y los arcos se recorren tal como están dibujados, y el transform de una figura o de cualquier <g> que la contenga se aplica. Dibuje en Illustrator, Figma, Inkscape o a mano — exporte y listo. Una sola regla distingue las figuras: al leer una línea única, los primitivos (rect, circle, ellipse) cuentan como mobiliario — el marco y el fondo de un gráfico exportado — y todo lo realmente dibujado los supera; en una banda cuenta cada figura, porque el dibujo es la región.
  • PNG con fondo transparente — los píxeles dibujados son la forma y la transparencia es espacio vacío. Así se ve una captura recortada a un gráfico, o un trazo de pincel exportado desde cualquier editor de mapa de bits. Un PNG que en cambio tiene fondo sólido cae en la regla de respaldo: los píxeles oscuros son tinta, los claros son fondo, y ink_threshold mueve el umbral.

En un SVG el eje y crece hacia abajo, tal como lo guarda cualquier programa de dibujo. TDC lo voltea, así que «más arriba en la pantalla» siempre significa «valor más grande».

Un ejemplo completo, de punta a punta

Este es un archivo real — 240×140 píxeles, un trazo sobre fondo transparente y nada más. Sin ejes, sin etiquetas, sin marco: todo lo opaco del archivo se lee como parte del dibujo.

line-input.png — un solo trazo ondulado sobre transparencia
line-input.png — el archivo completo. La tarjeta blanca está aquí nada más para que se vea; el archivo en sí no tiene fondo.
<gen type="pattern" src="line-input.png" y_range="0..100"/>

Córralo con 40 filas y grafique el resultado contra el dibujo. Los puntos verdes son los valores generados; la línea naranja es la figura:

Los valores generados caen exactamente sobre la curva dibujada
Cada fila cae exactamente sobre el trazo — una línea sola no carga ninguna aleatoriedad.
  • la línea dibujada en el archivo
  • valores generados (40 filas)

La figura mide 240 px de ancho y la corrida tiene 40 filas, pero ninguno de los dos números tiene que coincidir con el otro: pida 40 filas o 4 000 000 y el mismo dibujo se estira sobre ellas.

Lo mismo, desde un archivo vectorial

Un SVG no se aproxima. Este trae una sola curva de Bézier cúbica dentro de un <g> que voltea el eje y — las dos cosas que un programa de dibujo emite todo el tiempo:

curve-input.svg — una curva de Bézier cúbica dentro de un transform que voltea el eje
curve-input.svg — un path, un transform.
Los valores generados siguen la Bézier con exactitud, con el transform aplicado
La curva se recorre tal como está dibujada: la Bézier se camina, el transform se aplica, el eje y se voltea de regreso.
  • la Bézier del archivo, con su transform aplicado
  • valores generados (40 filas)

El dibujo se lee columna por columna

TDC nunca adivina si su figura es «una línea» o «una banda». Mide, y la medición responde la pregunta sola.

Para cada columna del dibujo toma dos lecturas: de arriba hacia abajo hasta la primera marca que encuentra, y de abajo hacia arriba hasta la primera marca que encuentra.

Dos lecturas por columna: si se juntan en un píxel el valor es exacto, si quedan separadas hay banda
Las mismas dos lecturas, sobre una línea sola y sobre una banda. No se inspecciona nada más.
  • Alas lecturas se juntan en un píxel — el valor es exacto
  • Blas lecturas quedan separadas — el valor es aleatorio entre ellas
  • las lecturas mismas: de arriba hacia abajo y de abajo hacia arriba
  • Las dos lecturas caen en el mismo lugar → esa columna es un punto exacto del gráfico. Cada fila que caiga ahí recibe ese único valor, sin ninguna aleatoriedad.
  • Las lecturas caen separadas → esa columna es una banda. Una fila que caiga ahí recibe un valor aleatorio entre los dos bordes, fijado por el seed.

Por eso una figura con dos trazos es un corredor, y cada fila toma un valor aleatorio entre ellos. Aquí está el archivo de entrada y 300 filas generadas a partir de él:

tunnel-input.png — dos trazos que forman una banda
tunnel-input.png — dos trazos, nada más.
300 valores generados dispersos dentro de la banda dibujada
Todos los valores caen dentro de la banda. Donde la banda es ancha la dispersión es ancha; donde se angosta, también se angostan los datos.
  • la banda dibujada en el archivo
  • valores generados (300 filas)
  • Auna columna: el valor se toma entre sus bordes

Dos consecuencias que vale la pena conocer:

  • Una misma figura puede ser las dos cosas. Una línea que va sola y luego se bifurca en dos ramas da valores exactos mientras es una sola y aleatorios en cuanto se abre — sin ningún atributo, sin elegir modo.
  • Todo lo cerrado es un corredor. Dibuje un coche, una hoja, una mancha: tiene un borde superior y un borde inferior, así que se lee como una banda entre ellos. No es una figura retórica — aquí hay un coche:
car-input.png — una silueta de coche rellena
car-input.png — una silueta, pintada de un solo color.
Los valores generados llenan la silueta del coche
Los valores llenan la carrocería entre su borde superior y su borde inferior — y donde el dibujo es una sola línea (el suelo antes y después del coche), se colapsan en un único valor exacto.
  • el coche dibujado
  • valores generados (300 filas)
  • Auna rueda baja el borde inferior de la carrocería
  • Bel suelo antes y después del coche es una sola línea, así que los valores son exactos

Aquí hay una línea que va plana y luego se bifurca: una rama hacia arriba, otra hacia abajo:

split-input.png — una línea que a la mitad se bifurca en dos ramas
split-input.png — un solo trazo que se vuelve dos.
Valores exactos antes de la bifurcación, valores aleatorios dentro de la banda después
Antes de la bifurcación cada fila recibe el mismo valor exacto; después, uno aleatorio dentro de la banda que se abre.
  • las líneas del archivo
  • valores generados (60 filas)
  • Aaquí la línea se bifurca
  • Bantes de la bifurcación — el mismo valor exacto siempre
  • Cdespués — aleatorio dentro de la banda que se abre

La misma corrida con dos semillas distintas lo muestra sin rodeos: la primera mitad es idéntica, la segunda no:

./run split.tdc (11 filas, dos semillas)
seed A:  50  50  50  50  50  50  44  47  66  18  45
seed B:  50  50  50  50  50  51  57  61  60  81  35

Un corredor escrito a mano: dos atributos, nunca una sola cadena

Un corredor dibujado en un archivo no necesita ningún atributo: dos trazos en la imagen ya se leen como dos bordes. Escrito a mano necesita dos atributos, upper y lower, con una curva cada uno:

<gen type="pattern" upper="0,80 100,80" lower="0,20 100,20" y_range="0..100"/>

El atajo tentador —un solo points con un separador entre las dos líneas— no existe, y un ; se rechaza en lugar de ignorarse:

tdcv2 check corridor.tdc
error[TDC285]: pattern: ";" does not separate two lines in points= — every number is read as one curve. For a band, draw the two edges separately: upper="0,80 100,80" lower="0,20 100,20".

La misma comprobación atrapa un dibujo sin ancho: todos los puntos en la misma x.

tdcv2 check flat.tdc
error[TDC285]: pattern: every point sits at x=50, so the drawing has no width and a card has nothing to read across. Give the points at least two different x coordinates.

Ambos los reporta check además de la corrida: points, upper y lower se leen antes de que exista una sola fila, con el mismo código que usa la corrida.

y_range — la escala, y es obligatoria

Un dibujo no trae escala propia. La misma curva sale de una herramienta en coordenadas 0..100, de otra en 0..480, de una tercera en 0..10002345345, y ninguno de esos números significa nada hasta que usted diga cuál es el eje. y_range="min..max" es esa declaración, y por eso el atributo es obligatorio y no opcional: sin él toda respuesta sería una conjetura sobre los ajustes de exportación de alguien más.

Lo que se escala es el lienzo, no la tinta:

de dónde viene el dibujopiso del lienzotecho del lienzo
un PNGla base de la imagenel tope de la imagen
un archivo vectorial (SVG)el punto más bajo del propio dibujoel punto más alto del propio dibujo
points / upper / lower0, crece hacia abajo si dibujó más abajo100, crece hacia arriba si dibujó más arriba

Para un dibujo escrito a mano el lienzo es un tablero porcentual: 0..100 por omisión, y sólo crece para contener lo que usted dibujó fuera de él — nunca se encoge. Eso es lo que hace que una línea plana en 50 sea el medio del rango que pida. Medida contra la tinta, esa misma línea sería lo más alto que hay, y por tanto el techo.

Las tres filas difieren porque las tres entradas traen cosas distintas. Un PNG mide exactamente lo que dice medir, así que su marco es un lienzo real y el espacio en blanco sobre un trazo baja de verdad sus valores. Un archivo vectorial no trae un marco en el que valga la pena confiar: casi todos los editores recortan el viewBox al dibujo al exportar, y uno recortado no se distingue de uno ajustado a propósito. Por eso se mide el dibujo mismo, y colocarlo es tarea de fit.

<gen type="pattern" points="0,0 50,100 100,0" y_range="0..40" decimals="1"/>
<gen type="pattern" points="0,0 50,100 100,0" y_range="-2..2" decimals="1"/>
./run yrange.tdc (el mismo triángulo, 11 filas)
0..40    0.0   8.0   16.0   24.0   32.0   40.0   32.0   24.0   16.0   8.0   0.0
-2..2   -2.0  -1.2   -0.4    0.4    1.2    2.0    1.2    0.4   -0.4  -1.2  -2.0

Nada sale del rango, sin importar en qué coordenadas haya dibujado. Combínelo con decimals cuando necesite valores continuos en vez de enteros.

El mismo triángulo bajo tres ajustes distintos de y_range
Un dibujo, tres escalas. La forma es idéntica; lo único que cambia son los números del eje.
  • el mismo triángulo dibujado
  • valores generados (25 filas)

y_range siempre significa el rango de los valores que se obtienen. En mode="density" el eje de los valores es el ancho del dibujo y no su altura, así que ahí el rango se estira sobre la figura de izquierda a derecha — el atributo conserva su significado aunque cambie el eje.

fit — dónde cae un dibujo venido de un archivo

Un dibujo trae una forma. Trae una posición sólo si trae también un tablero — y un archivo exportado desde el editor de otra persona no lo trae. Ni las unidades, ni el origen, ni siquiera hacia dónde está arriba. Así que sólo hay dos cosas suyas que se pueden medir: su punto más bajo y su punto más alto.

fit="bajo..alto" dice en qué se convierten esos dos.

<gen type="pattern" src="foreign.svg" y_range="200..300" fit="255..297"/>

El punto más bajo del dibujo sale como 255, el más alto como 297, y nada intermedio puede salirse de esa banda, en las coordenadas que fuera dibujado el archivo — 0..1000 o -5..3e6. La forma sobrevive; la colocación es la que usted declaró.

Omita fit y el dibujo llena el y_range: lo más bajo al piso, lo más alto al techo. Eso es una convención declarada, no una conjetura: no inventa información.

lienzo (lo que se mapea)sin fitcon fit
archivo vectorialel dibujo mismollena el y_rangesus extremos caen en la banda
un PNGel marco de la imagenel marco llena el y_rangeel marco cae en la banda

Con mode="density" sigue el mismo eje que y_range. Allí el eje de valores es el ancho del dibujo, no su altura, así que fit coloca su borde izquierdo y su borde derecho en vez de su punto más bajo y el más alto. fit es simplemente un y_range más estrecho para un dibujo leído de src: cambia de eje exactamente donde y_range cambia de eje, y nunca por separado.

Junto a points / upper / lower el atributo se rechaza (TDC300). Un punto escrito a mano ya es un porcentaje del tablero 0..100 — 80 significa el 80% de y_range —, así que no queda nada que colocar, y escribir ambos sería un config diciendo dos cosas a la vez.

Un corredor dibujado en un archivo lo necesita

Dos trazos en un archivo son un corredor, y una banda medida contra sí misma es siempre la banda entera. Por eso, sin fit, un SVG con banda llena el y_range — correcto según la regla de arriba, y rara vez lo que se quería. fit lleva en el config exactamente lo que la forma escrita a mano lleva en sus números:

<gen type="pattern" upper="0,80 100,80" lower="0,60 100,60" y_range="0..100" decimals="1"/>
<gen type="pattern" src="band.svg" y_range="0..100" fit="60..80" decimals="1"/>
./run fit.tdc (el mismo corredor, escrito y dibujado, seed="fit", 8 filas)
escrito   62.3  62.5  73.4  76.0  71.7  75.0  61.3  64.3
dibujado  62.3  62.5  73.4  76.0  71.7  75.0  61.3  64.3

Dígito por dígito, y lo mismo en las cinco implementaciones: el par es un fixture compartido.

Más alto en la imagen significa mayor en valor

SVG cuenta la y hacia abajo: el tope del archivo es y=0. El motor la invierte, así que lo que se ve más alto en su editor sale mayor en los datos. Nunca hay que escribirlo; conviene saberlo cuando un dibujo sale al revés y usted va a mirar el archivo.

decimals — dígitos después del punto

decimals define cuántos dígitos fraccionarios conserva cada valor (por omisión 0, es decir, enteros). Échele mano cuando la curva alimenta un precio, una temperatura o cualquier magnitud medida donde redondear a enteros aplanaría la forma.

<gen type="pattern" points="0,0 50,100 100,0" y_range="-1..1" decimals="2"/>
./run decimals.tdc (11 filas)
-1.00   -0.60   -0.20   0.20   0.60   1.00   0.60   0.20   -0.20   -0.60   -1.00

La fila del medio cae en la cima y lee 1.00, porque ahí es donde su línea cruza el dibujo. Más abajo, Estiramiento desarrolla esa regla completa.

Estiramiento — el dibujo casi nunca tiene tantos puntos como filas hay

La fila i de count lee el dibujo en t = i / (count − 1), y su valor es donde su propia línea vertical cruza la línea que usted dibujó. Diez filas son diez cruces, y puede comprobarlas sobre la figura con una regla. No hay una segunda regla debajo.

Más detalle dibujado que filas → obtiene los cruces, no un resumen. Un zigzag de cinco dientes exprimido en cinco filas reporta los cinco lugares donde caen esas filas, y cada uno es un punto real del zigzag:

<gen type="pattern" points="0,0 10,100 20,0 30,100 40,0 50,100 60,0 70,100 80,0 90,100 100,0"
y_range="0..100"/>
./run saw.tdc (5 dientes en 5 filas)
0   50   100   50   0

Amplíe la misma corrida a 40 filas y los dientes se notan, porque ahora las filas van lo bastante juntas como para caer sobre ellos: 0 26 51 77 97 72 46 21 5 31 56 82 …

No dar con un pico que queda entre dos filas no es una medición perdida: es lo que significa pedir cinco filas. Dibuje con más detalle, o pida más filas: con un millón de filas el mismo dibujo se despliega entero, con todos sus picos. Lo que gana la regla es que usted puede mirar su propio dibujo de antemano y decir qué va a salir.

Un zigzag leído con 300 filas y con 6 filas
El mismo zigzag. Con filas de sobra se nota cada diente; con siete filas, cada una lee el lugar donde su propia línea cruza el dibujo, y los dientes intermedios simplemente no están entre las siete mediciones pedidas.
  • el zigzag dibujado
  • valores generados
  • A300 filas — los dientes se notan
  • B7 filas — siete cruces, y el resto cae entre ellos

Más filas que puntos dibujados → interpolación, y ahí es donde entra interp.

interp — cómo se comporta la línea entre dos puntos dibujados

Entre dos puntos dibujados lejos uno del otro pueden caber miles de filas. Librado a su suerte, un segmento recto sube exactamente lo mismo en cada fila: matemáticamente correcto y visiblemente sintético. interp elige el comportamiento:

ValorQué hace
linear (por omisión)Segmentos rectos — ritmo constante entre dos puntos
smoothUna curva por los puntos que entra y sale suave, sin pasarse nunca
stepSostiene el valor de cada punto hasta el siguiente — una escalera

smooth usa una cúbica monótona: redondea las esquinas y varía el ritmo, pero nunca puede llevar la línea por encima ni por debajo de los valores que usted realmente dibujó, así que de un recodo no sale ningún pico fantasma. El mismo dibujo de tres puntos sobre 11 filas:

./run kink.tdc (11 filas, tres modos)
linear:  0   8  16  24  32  40  52  64  76  88  100
smooth:  0   8  15  23  31  40  50  62  75  88  100
step:    0   0   0   0   0  40  40  40  40  40  100

Fíjese en cómo termina step. Una escalera sostiene el valor de cada punto en la banda a su derecha, y el último punto no tiene banda — ahí se acaba el dibujo. Por eso el último punto se lee en el único lugar que toda corrida visita: el borde derecho mismo, donde lo cruza la línea de la última fila. Los tres modos coinciden ahí, en lo último que usted dibujó.

En un dibujo con esquinas de verdad los tres se distinguen sin duda — la línea punteada es lo que se dibujó, los puntos son lo que salió:

linear sigue la polilínea, smooth redondea las esquinas, step sostiene cada valor
linear va exactamente sobre la polilínea; smooth redondea cada esquina sin salirse jamás del rango dibujado; step sostiene cada punto hasta el siguiente.
  • lo que se dibujó: una polilínea por cinco puntos
  • valores generados (41 filas)

Mire el tamaño de los pasos: linear repite 8, 8, 8, 8 — perfectamente predecible. smooth va 8, 7, 8, 8, 9, 10, 12, 13 — sale del punto con suavidad, acelera y llega con suavidad.

spread — convertir una línea en un túnel

spread="N" dispersa cada fila en ±N, en las unidades de su y_range. Por defecto 0: la línea es exacta y 100% predecible. Póngale un valor y la línea dibujada se vuelve el centro de una banda — así no hace falta dibujar los dos bordes de un corredor nada más para tener algo de bamboleo.

Sigue la escala que usted declaró: con y_range="0..100" un spread de 1 es un punto de ruido; con y_range="0..1" habría que escribir spread="0.001" para el mismo efecto relativo.

<gen type="pattern" src="ramp.svg" y_range="0..100" spread="5"/>
./run ramp.tdc (11 filas)
spread 0, seed A:   0  10  20  30  40  50  60  70  80  90  100
spread 5, seed A:   5  13  16  29  37  52  57  69  83  86   99
spread 5, seed B:   5  11  21  26  45  53  64  73  82  94   99

El mismo trazo único de line-input.png, esta vez con spread="6":

La línea dibujada con una banda de ±6 alrededor y los valores generados dispersos adentro
Una línea dibujada, y un túnel que usted nunca dibujó. La forma queda intacta; cada fila simplemente se posa en algún lugar dentro de ±6 de ella.
  • la línea dibujada — el centro
  • el corredor de ±6 que usted nunca dibujó
  • valores generados (60 filas)

La tendencia queda intacta; cada fila simplemente se posa en algún lugar dentro de ±5 de ella. Igual que con el corredor, la dispersión es determinista: la misma semilla la reproduce exactamente. spread también funciona sobre un dibujo que ya es una banda: la ensancha N de cada lado.

ink_threshold — el umbral oscuro/claro para PNG

Cuando un PNG tiene fondo sólido, la tinta se decide por oscuridad. ink_threshold es el umbral en una escala 0..1 (por omisión 0.5): un píxel cuenta como tinta cuando es igual de oscuro o más que el umbral. Así que súbalo hacia 1 para recoger bordes tenues y suavizados y el gris claro, y bájelo hacia 0 para quedarse solo con los trazos casi negros. No tiene efecto en un PNG dibujado sobre transparencia, donde el canal alfa ya dice qué está dibujado.

<gen type="pattern" src="faint-curve.png" ink_threshold="0.8" y_range="0..100"/>

Este archivo tiene dos trazos sobre un lienzo blanco — uno negro y uno gris claro — y el umbral decide si el gris existe:

threshold-input.png — un trazo negro y un trazo gris sobre blanco
threshold-input.png — dos trazos sobre un lienzo opaco.
Con 0.5 solo se lee el trazo negro; con 0.8 se leen los dos y aparece un corredor
Con el umbral por omisión el trazo gris es fondo y la lectura es una sola línea exacta. Suba el umbral y el gris también cuenta como tinta — ahora hay dos bordes, así que los valores salen aleatorios entre ellos.
  • el trazo negro
  • el trazo gris claro
  • valores generados (60 filas)

La señal es una trayectoria, no un histograma

El valor de una fila es la altura de la curva en su posición dentro del orden. Así que los números salen a lo largo de la curva, como el rastro de una pluma: una trayectoria, no «un montón de valores». Si tomara esos mismos números y, ignorando su orden, armara un histograma (con qué frecuencia aparece cada valor), no reproduciría la forma de la curva. Este es el histograma real de un dibujo de dos jorobas sobre 300 filas:

./run humps.tdc --count 300 | histogram
0  | ################################## 29
3  | ######################################## 34
7  | ######################################## 34
10 | #################################### 31
13 | ######################### 21
17 | ########################## 22
20 | ################################# 28
23 | ########################## 22
27 | ######################### 21
30 | ################################## 29
33 | ################### 16
37 | ############### 13

Ni rastro de las dos jorobas — el histograma es casi plano. Eso no es un error: la señal pasa por cada altura más o menos con la misma frecuencia, así que el montón de sus valores no se parece al dibujo. Cuando lo que quiere es que el dibujo defina la frecuencia, esa es la otra lectura: mode="density", más abajo.

mode — las dos preguntas que se le pueden hacer a un dibujo

La misma figura responde dos preguntas distintas, y mode elige cuál:

ModoLa preguntaQué sale
signal (por omisión)¿qué valor recibe esta fila?0, 20, 40, 70, 90, 100, 90, 70, 40, 20, 0 — camina a lo largo de la línea, en orden
density¿con qué frecuencia aparece este valor?un montón de números agrupados alrededor de la joroba dibujada, en orden aleatorio

En density los ejes intercambian significado: el eje horizontal es el valor, y la altura de la curva es con qué frecuencia ocurre ese valor. Dibuje una joroba en el medio y la mayoría de los números caerá en el medio; dibuje dos jorobas de distinta altura y la más alta se lleva proporcionalmente más. Es «dibuje su propia probabilidad» en lugar de elegir normal / poisson de una lista.

<!-- un triángulo parado entre x=25 y x=75 -->
<gen type="pattern" points="0,0 25,0 50,100 75,0 100,0" y_range="0..100" mode="density"/>
./run density.tdc --count 6000 | histogram
  0- 9 |
10-19 |
20-29 | ## 85
30-39 | ########################### 901
40-49 | ########################################################## 1911
50-59 | ############################################################ 1955
60-69 | ############################## 978
70-79 | ##### 170
80-89 |
90-99 |

Los valores mismos salen revueltos — 60 57 36 49 48 40 67 53 … — porque una distribución no tiene orden. Y fuera de 25..75 no aparece nada: ahí el dibujo es plano, y un tramo plano significa «nunca».

Lo mismo, pero con una figura en vez de puntos tecleados. Aquí hay una joroba dibujada como forma rellena:

hump-input.png — una joroba rellena con forma de campana
hump-input.png — una joroba pintada con pincel, guardada sobre transparencia.
<gen type="pattern" src="hump-input.png" y_range="0..100" mode="density"/>

Genere 6000 filas a partir de ella y cuente con qué frecuencia salió cada valor:

Arriba la joroba dibujada, abajo el histograma de los valores generados, alineados sobre el mismo eje de valores
Arriba: lo que se dibujó. Abajo: con qué frecuencia salió realmente cada valor. El eje horizontal es el mismo en ambos — y es el valor, no el número de fila.
  • Alo que está dibujado en el archivo
  • Bhistograma de 6000 valores generados
  • Cla altura del dibujo aquí = con qué frecuencia sale ese valor

En eso consiste toda la idea: la silueta que usted pinta se vuelve el histograma de los datos. En ningún lado se eligió una fórmula.

Y la altura es proporcional, no solo posicional: dibuje una joroba del doble de alto que la otra y se lleva alrededor del doble de los valores:

Dos jorobas de distinta altura, y el histograma correspondiente
Dos jorobas, la derecha del doble de alto. El histograma de abajo reproduce ambas, en proporción.
  • Alo que está dibujado: dos jorobas de distinta altura
  • Bhistograma de 6000 valores generados
  • Cla joroba izquierda
  • Dla derecha, del doble de alto — y con el doble de valores

Cosas que vale la pena saber sobre density:

  • El cero es el piso de la figura. Para un PNG es el borde inferior de la imagen; para un SVG o para puntos en el config es el punto más bajo del dibujo. Donde la curva esté en su punto más bajo, ese valor no ocurre nunca — así que baje su curva hasta la base en los extremos del rango que quiere.
  • De una banda se toma el borde superior. Si el dibujo es un corredor, es el contorno lo que da forma a la distribución.
  • interp sigue aplicando, y mueve peso de verdad. smooth da una distribución redondeada en vez de una poligonal — pero si el dibujo es un GRÁFICO DE BARRAS, una proporción declarada por barra, use interp="step". Es el único modo que mantiene un valor a lo ancho de toda la barra; linear y smooth pasan una línea POR el punto, y el pico se derrama sobre sus vecinos. Medido con diez barras y 200 000 filas, con un pico declarado del 40%: step dejó las dos barras centrales en 28.4% y 27.9%, smooth las concentró en 41.5% y 10.0%. Los mismos puntos, proporciones distintas — la elección no es cosmética.
  • spread se rechaza aquí. El dibujo ya es la dispersión, así que combinar las dos cosas es un error y no una función.
  • Un dibujo plano significa «me da igual» y da una dispersión uniforme sobre el rango, en vez de un error.
  • Determinista y transmisible por streaming, como todo lo demás: la misma semilla reproduce el mismo montón, con cualquier cantidad de filas.

Detalles

  • Determinista: donde el dibujo es una línea sola, el valor de una fila es simplemente la altura ahí (nada de aleatoriedad); donde es una banda — o donde está puesto spread — la dispersión queda fijada por el seed. Misma semilla y mismo config → mismo resultado.
  • Cualquier tamaño, cualquier motor: cada fila se calcula a partir de su propio número, así que la memoria no crece. Un millón de filas a partir de un dibujo chico va bien en los motores de streaming.
  • El archivo se lee una sola vez, al inicio de la corrida, y se convierte en geometría — el costo no escala con la cantidad de filas.

Compruébelo a mano

Unas cuantas líneas sueltas para construir intuición:

  • points="0,0 50,100 100,0" y_range="0..100" — un triángulo: los valores suben hasta el medio y caen.
  • points="0,10 90,10 100,100" y_range="0..100" — una línea plana con un pico solo en el último 10% de las filas.
  • upper="0,0 50,40 100,0" y_range="0..100" — una banda: aleatorio desde 0 hasta un pico central.
  • src="chart.png" y_range="0..100" — una forma dibujada se vuelve datos a lo largo de su contorno.
  • agréguele spread="2" a cualquiera de ellas — la misma forma, ahora con bamboleo.
  • agréguele mode="density" en cambio — la misma forma ahora decide con qué frecuencia sale cada valor.

El caso del pico deja obvia la idea de «posición = número de fila»:

./run spike.tdc (20 filas)
filas 1–18: 10   (el tramo plano)
fila 19: 53      (subiendo)
fila 20: 100     (el pico)
Más adelante

Una herramienta de dibujo en el navegador que exporte justamente estos archivos SVG/PNG — para bosquejar una curva con el mouse en lugar de abrir un editor.

Vea también