Public
Edited
Apr 9
Insert cell
Insert cell
Insert cell
Insert cell
html`<svg width="100" height="100">
<circle cx="40" cy="60" r="20" style="fill:red;">
</circle>`
Insert cell
html`<svg width="110" height="110">
<polygon points="50,5 20,99 95,39 5,39 80,99" style="fill:blue;stroke:red;stroke-width:5;fill-rule:evenodd;" />
</circle>`
Insert cell
Insert cell
{
const svg = d3.create("svg")
.attr("width", 256)
.attr("height", 256);
// 1. Circulo verde 1.
svg.append("circle")
.attr("cx", 128)
.attr("cy", 128)
.attr("r", 120)
.attr("fill", "green");
// 2. Circulo violeta 2.
svg.append("circle")
.attr("cx", 128)
.attr("cy", 128)
.attr("r", 72)
.attr("fill", "purple");
// 3. Círculo rojo 3.
svg.append("circle")
.attr("cx", 128)
.attr("cy", 128)
.attr("r", 36)
.attr("fill", "red");
// Retornamos el nodo SVG para que se muestre en la celda.
return svg.node();
}
Insert cell
Insert cell
{
const width = 256, height = 256;
const nRows = 17, nCols = 17; // Numero de filas y columnas
const radius = 6; // Radio de cada círculo
const xSpacing = width / nCols; // Espacio horizontal
const ySpacing = height / nRows; // Espacio vertical

// Función para interpolar linealmente dos colores.
function blendRGB(rgbA, rgbB, t) {
return [
Math.round(rgbA[0] + (rgbB[0] - rgbA[0]) * t),
Math.round(rgbA[1] + (rgbB[1] - rgbA[1]) * t),
Math.round(rgbA[2] + (rgbB[2] - rgbA[2]) * t)
];
}

// Definimos los colores de las esquinas en RGB.
const black = [0, 0, 0];
const red = [255, 0, 0];
const green = [0, 255, 0];
const yellow= [255, 255, 0];

// Creamos nuestro SVG
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

// Generamos cada circulo
for (let row = 0; row < nRows; row++) {
for (let col = 0; col < nCols; col++) {
// Normalizamos fila/columna a [0..1]
const rowNorm = row / (nRows - 1);
const colNorm = col / (nCols - 1);

// Interpolacion bilineal de color:
// - topColor: de black (izq) a red (der)
// - bottomColor: de green (izq) a yellow (der)
// Luego mezclamos top/bottom según rowNorm
const topColor = blendRGB(black, red, colNorm);
const bottomColor = blendRGB(green, yellow, colNorm);
const finalColor = blendRGB(topColor, bottomColor, rowNorm);

const fill = `rgb(${finalColor[0]},${finalColor[1]},${finalColor[2]})`;

// Posición del círculo en el SVG
const cx = col * xSpacing + xSpacing/2;
const cy = row * ySpacing + ySpacing/2;

// Agregamos el círculo
svg.append("circle")
.attr("cx", cx)
.attr("cy", cy)
.attr("r", radius)
.attr("fill", fill);
}
}

return svg.node();
}

Insert cell
Insert cell
{
const width = 256, height = 256;
const n = 21;
const spacingX = width / (n - 1);
const spacingY = height / (n - 1);
const center = (n - 1) / 2;
const maxRadius = spacingX / 2.5;
const svg = d3.create("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("width", width)
.attr("height", height)
.style("display", "block")
.style("background", "white");
// Bucle para las filas
for (let row = 0; row < n; row++) {
// Bucle para las columnas
for (let col = 0; col < n; col++) {
const dx = col - center;
const dy = row - center;
let r;
// Cálculo de distancia desde el centro
const gridDistanceX = Math.abs(dx);
const gridDistanceY = Math.abs(dy);
const gridDistance = Math.max(gridDistanceX, gridDistanceY);
const distance = Math.sqrt(dx * dx + dy * dy);
// Configurar el tamaño basado en la posición
if (gridDistance === 0) {
// Punto central más grande
r = maxRadius;
} else if (gridDistance === 1 && (dx === 0 || dy === 0)) {
// Puntos adyacentes al centro en forma de cruz
r = maxRadius * 0.78;
} else if (gridDistance <= 2) {
// Puntos cercanos al centro
r = maxRadius * 0.55 * (3 - gridDistance) / 2;
} else {
// Puntos más alejados del centro
r = maxRadius * 0.3 * Math.max(0.2, 1 - distance / 10);
}
const cx = col * spacingX;
const cy = row * spacingY;
// Solo añadir círculos con radio > 0.1
if (r > 0.1) {
svg.append("circle")
.attr("cx", cx)
.attr("cy", cy)
.attr("r", r)
.attr("fill", "black");
}
}
}
return svg.node();
}
Insert cell
Insert cell
{
// Configuración del Canvas y Grid
const svgWidth = 256;
const svgHeight = 256;

const numRows = 16;
const numCols = 17;
const totalCols = numCols * 2;

const horizontalOverlapFactor = 0.8;
const verticalSpacingFactor = 0.7;

// Cálculo del Tamaño Óptimo del Triángulo
const widthConstraintFactor = ((totalCols - 1) * horizontalOverlapFactor + 1);
const maxTriangleBaseW = svgWidth / widthConstraintFactor;
const heightPerTriangleUnit = Math.sqrt(4) / 2;
const heightConstraintFactor = (numRows + (numRows - 1) * verticalSpacingFactor) * heightPerTriangleUnit;
const maxTriangleBaseH = svgHeight / heightConstraintFactor;
const triangleBase = Math.min(maxTriangleBaseW, maxTriangleBaseH);

const triangleHeight = heightPerTriangleUnit * triangleBase;
const horizontalStep = triangleBase * horizontalOverlapFactor;
const rowSpacing = triangleHeight * verticalSpacingFactor;
const actualContentWidth = ((totalCols - 1) * horizontalStep + triangleBase);
const actualContentHeight = (numRows * triangleHeight + (numRows - 1) * rowSpacing);
const xOffsetTotal = (svgWidth - actualContentWidth) / 2;
const yOffsetTotal = (svgHeight - actualContentHeight) / 2;

// Configuración del Triángulo Interior
const innerScale = 0.65;
const innerBase = triangleBase * innerScale;
const innerHeight = triangleHeight * innerScale;
const innerOffsetX = (triangleBase - innerBase) / 2;

// Colores
const colorSet1_Outer = "blue";
const colorSet1_Inner = "yellow";
const colorSet2_Outer = "red";
const colorSet2_Inner = "white";

// Creación del SVG
const svg = d3.create("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`)
.style("background-color", "white");

// Generación de la grilla de triángulos
for (let r = 0; r < numRows; r++) {
for (let c = 0; c < totalCols; c++) {
const x_base = xOffsetTotal + c * horizontalStep;
const y_base = yOffsetTotal + r * (triangleHeight + rowSpacing);

let outerColor, innerColor;
let outerPoints, innerPoints;

if (c % 2 === 0) {
outerColor = colorSet1_Outer;
innerColor = colorSet1_Inner;
outerPoints = [
[x_base + triangleBase / 2, y_base],
[x_base, y_base + triangleHeight],
[x_base + triangleBase, y_base + triangleHeight]
].map(p => p.join(",")).join(" ");
// Ajuste para centrar visualmente el triángulo amarillo
const innerVerticalOffset = (triangleHeight - innerHeight) / 2;
innerPoints = [
[x_base + triangleBase / 2, y_base + innerVerticalOffset],
[x_base + innerOffsetX, y_base + triangleHeight - innerVerticalOffset],
[x_base + triangleBase - innerOffsetX, y_base + triangleHeight - innerVerticalOffset]
].map(p => p.join(",")).join(" ");
} else {
outerColor = colorSet2_Outer;
innerColor = colorSet2_Inner;
outerPoints = [
[x_base, y_base],
[x_base + triangleBase, y_base],
[x_base + triangleBase / 2, y_base + triangleHeight]
].map(p => p.join(",")).join(" ");
// Ajuste para centrar visualmente el triángulo blanco
const innerVerticalOffset = (triangleHeight - innerHeight) / 2;
innerPoints = [
[x_base + innerOffsetX, y_base + innerVerticalOffset],
[x_base + triangleBase - innerOffsetX, y_base + innerVerticalOffset],
[x_base + triangleBase / 2, y_base + triangleHeight - innerVerticalOffset]
].map(p => p.join(",")).join(" ");
}

// Dibujar en el SVG
svg.append("polygon")
.attr("points", outerPoints)
.attr("fill", outerColor)
.attr("stroke", outerColor)
.attr("stroke-width", 1);
svg.append("polygon")
.attr("points", innerPoints)
.attr("fill", innerColor)
.attr("stroke", innerColor)
.attr("stroke-width", 0.6);
}
}

return svg.node();
}
Insert cell
Insert cell
{
// Configuración
const width = 256;
const height = 256;
const r = 17; // Radio del hexágono
const num_rows = 7; // 7 filas según tu descripción
const num_cols = 7; // 7 columnas en CADA fila según tu descripción
const maxStroke = 3; // Grosor máximo de línea (fila inferior)
const minStroke = 0.5; // Grosor mínimo de línea (fila superior)

// Espaciado entre hexágonos
const horizontalPadding = 5; // Espacio HORIZONTAL entre hexágonos de la misma fila
const verticalPadding = 4; // Espacio VERTICAL adicional entre filas

// Calcular geometría derivada
const hexWidth = Math.sqrt(3) * r; // Ancho del hexágono (~34.64)
const hexHeight = 1.5 * r; // Alto del hexágono (40)

// Calcular distancias CENTRO a CENTRO (incluyendo padding)
// dx: Distancia horizontal entre centros
const dx = hexWidth + horizontalPadding; // (~34.64 + 10 = 44.64)
// dy: Distancia vertical entre centros
const dy = hexHeight + maxStroke + verticalPadding; // (40 + 8 + 10 = 58)

// Calcular la indentación para filas impares (índice 1, 3, 5)
const indentation = dx / 2; // (~44.64 / 2 = 22.32)

// Calcular dimensiones totales de la cuadrícula para poder centrarla
// El ancho máximo debe considerar la fila indentada más ancha
const totalGridWidth = (num_cols - 1) * dx + hexWidth + indentation;
const totalGridHeight = (num_rows - 1) * dy + hexHeight;
// Calcular coordenadas X, Y iniciales para centrar el bloque completo
const gridStartX = (width - totalGridWidth) / 2;
const gridStartY = (height - totalGridHeight) / 2;

// Función para generar puntos de hexágono (Pointy Top)
// (Esta función no cambia)
function hexagon_points(cx, cy, r) {
var points = [];
for (var i = 0; i < 6; i++) {
var angle_rad = Math.PI / 3 * i - Math.PI / 6; // Ángulo para pointy top
points.push( `${cx + r * Math.cos(angle_rad)},${cy + r * Math.sin(angle_rad)}` );
}
return points.join(" ");
}

// 1. Crear el contenedor SVG
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`);
// .style("border", "1px solid lightgray"); // Borde opcional para depuración

// 2. Iterar por las filas (índice de 0 a 6)
for (let row = 0; row < num_rows; row++) {

// a. Determinar si esta fila necesita indentación
// Las filas con índice IMPAR (1, 3, 5) llevan indentación
const needsIndent = row % 2 !== 0;

// b. Calcular el grosor de línea para esta fila
let strokeWidth = minStroke + ((maxStroke - minStroke) * row / (num_rows - 1));
strokeWidth = Math.max(minStroke, Math.min(maxStroke, strokeWidth)); // Asegurar límites

// c. Calcular la coordenada Y del centro de los hexágonos de esta fila
// Se suma 'r' porque cy es el centro, no el borde superior.
const cy = gridStartY + r + row * dy;

// d. Calcular la coordenada X inicial para el centro del PRIMER hexágono de la fila
// Se parte del X inicial de la cuadrícula centrado.
// Se suma la mitad del ancho para llegar al centro del primer hexágono.
// Se suma la indentación SI esta fila la necesita.
let startXForRowCenter = gridStartX + hexWidth / 2 + (needsIndent ? indentation : 0);

// e. Iterar por las columnas (índice de 0 a 6)
for (let col = 0; col < num_cols; col++) {
// Calcular la coordenada X del centro del hexágono actual
let cx = startXForRowCenter + col * dx;

// f. Añadir el hexágono al SVG
svg.append("polygon")
.attr("points", hexagon_points(cx, cy, r)) // Puntos del hexágono
.attr("fill", "none") // Sin relleno
.attr("stroke", "black") // Borde negro
.attr("stroke-width", strokeWidth); // Grosor calculado
}
}

// n. Retornar el nodo DOM del SVG generado
return svg.node();
}
Insert cell
// Función para generar hexágonos en x,y de radio r.
function hexagon(x, y, r) {
var x1 = x;
var y1 = y - r;
var x2 = x + Math.cos(Math.PI / 6) * r;
var y2 = y - Math.sin(Math.PI / 6) * r;
var x3 = x + Math.cos(Math.PI / 6) * r;
var y3 = y + Math.sin(Math.PI / 6) * r;
var x4 = x;
var y4 = y + r;
var x5 = x - Math.cos(Math.PI / 6) * r;
var y5 = y + Math.sin(Math.PI / 6) * r;
var x6 = x - Math.cos(Math.PI / 6) * r;
var y6 = y - Math.sin(Math.PI / 6) * r;

var path = x1 + ',' + y1 + " " + x2 + ',' + y2 + " " + x3 + ',' + y3 + " " + x4 + ',' + y4 + " " + x5 + ',' + y5 + " " + x6 + ',' + y6;
return path;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more