Public
Edited
May 28
Insert cell
Insert cell
Insert cell
Insert cell
md`# Ejercicio 2: Gráfico de Barras`
Insert cell
datos = [
{nombre: "Producto A", valor: 30},
{nombre: "Producto B", valor: 80},
{nombre: "Producto C", valor: 45},
{nombre: "Producto D", valor: 60},
{nombre: "Producto E", valor: 95},
{nombre: "Producto F", valor: 25}
]
Insert cell
config = ({
width: 600,
height: 350,
margin: {top: 20, right: 20, bottom: 60, left: 50}
})
Insert cell
{
// Crear SVG
const svg = d3.create("svg")
.attr("width", config.width)
.attr("height", config.height)
.style("background", "#fafafa")
.style("border", "1px solid #ddd");
// Escalas
const xScale = d3.scaleBand()
.domain(datos.map(d => d.nombre))
.range([config.margin.left, config.width -
config.margin.right])
.padding(0.2);
const yScale = d3.scaleLinear()
.domain([0, d3.max(datos, d => d.valor)])
.range([config.height - config.margin.bottom,
config.margin.top]);
// Escala de colores
const colorScale =
d3.scaleSequential(d3.interpolateBlues)
.domain([0, d3.max(datos, d => d.valor)]);
// Crear barras
svg.selectAll(".bar")
.data(datos)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => xScale(d.nombre))
.attr("y", d => yScale(d.valor))
.attr("width", xScale.bandwidth())

.attr("height", d => config.height -
config.margin.bottom - yScale(d.valor))
.attr("fill", d => colorScale(d.valor))
.attr("stroke", "#2c3e50")
.attr("stroke-width", 1);
// Etiquetas de valores
svg.selectAll(".label")
.data(datos)
.enter()
.append("text")
.attr("class", "label")
.attr("x", d => xScale(d.nombre) +
xScale.bandwidth()/2)
.attr("y", d => yScale(d.valor) - 5)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.style("font-weight", "bold")
.style("fill", "#2c3e50")
.text(d => d.valor);
return svg.node();
}
Insert cell
md`# Ejercicio 3: Gráfico de Barras con Ejes`
Insert cell
{
// Crear SVG
const svg = d3.create("svg")
.attr("width", config.width)
.attr("height", config.height)
.style("font-family", "Arial, sans-serif");
// Escalas (reutilizamos las anteriores)
const xScale = d3.scaleBand()
.domain(datos.map(d => d.nombre))

.range([config.margin.left, config.width -
config.margin.right])
.padding(0.2);
const yScale = d3.scaleLinear()
.domain([0, d3.max(datos, d => d.valor)])
.range([config.height - config.margin.bottom,
config.margin.top]);
// Crear barras con animación
svg.selectAll(".bar")
.data(datos)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => xScale(d.nombre))
.attr("y", config.height - config.margin.bottom) //
.attr("width", xScale.bandwidth())
.attr("height", 0) // Altura inicial 0
.attr("fill", "#3498db")
.attr("stroke", "#2980b9")
.attr("stroke-width", 1)
.transition() // Agregar transición
.duration(1000)
.delay((d, i) => i * 100)
.attr("y", d => yScale(d.valor))
.attr("height", d => config.height -
config.margin.bottom - yScale(d.valor));
// Eje X
svg.append("g")
.attr("transform", `translate(0,${config.height -
config.margin.bottom})`)
.call(d3.axisBottom(xScale))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)");

// Eje Y
svg.append("g")
.attr("transform",
`translate(${config.margin.left},0)`)
.call(d3.axisLeft(yScale).tickFormat(d => d + "%"));
// Etiqueta eje Y
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - config.margin.left)
.attr("x", 0 - (config.height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-size", "14px")
.style("fill", "#2c3e50")
.text("Porcentaje de Ventas (%)");
// Etiqueta eje X
svg.append("text")
.attr("transform", `translate(${config.width/2},
${config.height - 5})`)
.style("text-anchor", "middle")
.style("font-size", "14px")
.style("fill", "#2c3e50")
.text("Productos");
// Título del gráfico
svg.append("text")
.attr("x", config.width / 2)
.attr("y", config.margin.top / 2)
.attr("text-anchor", "middle")
.style("font-size", "18px")
.style("font-weight", "bold")
.style("fill", "#2c3e50")
.text("Ventas por Producto");
return svg.node();
}
Insert cell
md`# Ejercicio 4: Gráfico de Dispersión`
Insert cell
datosScatter = [
{x: 10, y: 20, categoria: "Tecnología", nombre: "Empresa A"},
{x: 25, y: 35, categoria: "Salud", nombre: "Empresa B"},
{x: 40, y: 15, categoria: "Tecnología", nombre: "Empresa C"},
{x: 55, y: 45, categoria: "Educación", nombre: "Empresa D"},
{x: 70, y: 30, categoria: "Salud", nombre: "Empresa E"},
{x: 85, y: 50, categoria: "Educación", nombre: "Empresa F"},
{x: 15, y: 60, categoria: "Tecnología", nombre: "Empresa G"},
{x: 90, y: 25, categoria: "Salud", nombre: "Empresa H"}
]
Insert cell
{
// Crear SVG
const svg = d3.create("svg")
.attr("width", config.width)
.attr("height", config.height)
.style("font-family", "Arial, sans-serif");

// Escalas
const xScaleScatter = d3.scaleLinear()
.domain(d3.extent(datosScatter, d => d.x))
.range([config.margin.left, config.width - config.margin.right]);
const yScaleScatter = d3.scaleLinear()
.domain(d3.extent(datosScatter, d => d.y))
.range([config.height - config.margin.bottom, config.margin.top]);
const colorScale = d3.scaleOrdinal()
.domain(["Tecnología", "Salud", "Educación"])
.range(["#e17045", "#00b884", "#626e72"]); //

// Crear tooltip
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("padding", "10px")
.style("background", "rgba(0,0,0,0.8)")
.style("color", "white")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0);

// Crear puntos
svg.selectAll(".dot")
.data(datosScatter)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", d => xScaleScatter(d.x))
.attr("cy", d => yScaleScatter(d.y))
.attr("r", 12)
.style("fill", d => colorScale(d.categoria))
.style("stroke", "#2c3e50")
.style("stroke-width", 2)
.style("cursor", "pointer")
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("r", 16)
.style("stroke-width", 3);
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(`
<strong>${d.nombre}</strong><br/>
Categoría: ${d.categoria}<br/>
X: ${d.x}, Y: ${d.y}
`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("r", 12)
.style("stroke-width", 2);
tooltip.transition()
.duration(500)
.style("opacity", 0);
});


return svg.node();
}
Insert cell
md`# 📊 Dashboard Interactivo de Ventas - Proyecto Final`
Insert cell
ventasData = [
{mes: "Enero", ventas: 1200, gastos: 800, categoria: "Electrónicos", beneficio: 400},
{mes: "Febrero", ventas: 1500, gastos: 900, categoria: "Electrónicos", beneficio: 600},
{mes: "Marzo", ventas: 1800, gastos: 950, categoria: "Electrónicos", beneficio: 850},
{mes: "Abril", ventas: 1300, gastos: 850, categoria: "Ropa", beneficio: 450},
{mes: "Mayo", ventas: 1600, gastos: 920, categoria: "Ropa", beneficio: 680},
{mes: "Junio", ventas: 2000, gastos: 1000, categoria: "Ropa", beneficio: 1000},
{mes: "Julio", ventas: 1750, gastos: 950, categoria: "Electrónicos", beneficio: 800},

{mes: "Agosto", ventas: 1900, gastos: 1100, categoria: "Ropa", beneficio: 800}
]
Insert cell
viewof categoriaSeleccionada = Inputs.select(
["Todas", ...new Set(ventasData.map(d => d.categoria))],
{label: "Filtrar por categoría:", value: "Todas"}
)
Insert cell
datosFiltrados = categoriaSeleccionada === "Todas"
? ventasData
: ventasData.filter(d => d.categoria === categoriaSeleccionada)
Insert cell
dashboardConfig = ({
width: 800,
height: 400,
margin: {top: 40, right: 40, bottom: 60, left: 60},
colors: {
electronicosColor: "#3498db",
ropaColor: "#e74c3c",
ventasColor: "#2ecc71",
gastosColor: "#f39c12",
beneficioColor: "#9b59b6"
}
})
Insert cell
{
const svg = d3.create("svg")
.attr("width", dashboardConfig.width)
.attr("height", dashboardConfig.height)
.style("background", "#f8f9fa")
.style("border-radius", "8px")
.style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)");
// Escalas
const xScale = d3.scaleBand()
.domain(datosFiltrados.map(d => d.mes))
.range([dashboardConfig.margin.left, dashboardConfig.width -
dashboardConfig.margin.right])
.padding(0.2);
const yScale = d3.scaleLinear()
.domain([0, d3.max(datosFiltrados, d => d.ventas)])
.range([dashboardConfig.height - dashboardConfig.margin.bottom,
dashboardConfig.margin.top])

.nice();
// Crear barras
svg.selectAll(".bar")
.data(datosFiltrados)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => xScale(d.mes))
.attr("y", dashboardConfig.height - dashboardConfig.margin.bottom)
.attr("width", xScale.bandwidth())
.attr("height", 0)
.attr("fill", d => d.categoria === "Electrónicos" ?
dashboardConfig.colors.electronicosColor :
dashboardConfig.colors.ropaColor)
.attr("stroke", "#2c3e50")
.attr("stroke-width", 1)
.transition()
.duration(1000)
.delay((d, i) => i * 100)
.attr("y", d => yScale(d.ventas))
.attr("height", d => dashboardConfig.height -
dashboardConfig.margin.bottom - yScale(d.ventas));
// Etiquetas de valores
svg.selectAll(".value-label")
.data(datosFiltrados)
.enter()
.append("text")
.attr("class", "value-label")
.attr("x", d => xScale(d.mes) + xScale.bandwidth()/2)
.attr("y", d => yScale(d.ventas) - 5)
.attr("text-anchor", "middle")
.style("font-size", "11px")
.style("font-weight", "bold")
.style("fill", "#2c3e50")
.text(d => `${d.ventas}`);
// Ejes
svg.append("g")
.attr("transform", `translate(0,${dashboardConfig.height -
dashboardConfig.margin.bottom})`)
.call(d3.axisBottom(xScale))
.selectAll("text")
.style("font-size", "12px");
svg.append("g")
.attr("transform", `translate(${dashboardConfig.margin.left},0)`)

.call(d3.axisLeft(yScale).tickFormat(d => `${d}`))
.selectAll("text")
.style("font-size", "12px");
// Título
svg.append("text")
.attr("x", dashboardConfig.width / 2)
.attr("y", 25)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.style("fill", "#2c3e50")
.text(`Ventas por Mes - ${categoriaSeleccionada}`);
return svg.node();
}
Insert cell
{
const svg = d3.create("svg")
.attr("width", dashboardConfig.width)
.attr("height", dashboardConfig.height)
.style("background", "#f8f9fa")
.style("border-radius", "8px")
.style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)")
.style("margin-top", "20px");
// Escalas
const xScale = d3.scaleBand()
.domain(datosFiltrados.map(d => d.mes))
.range([dashboardConfig.margin.left, dashboardConfig.width -
dashboardConfig.margin.right])
.padding(0.1);
const yScale = d3.scaleLinear()
.domain([0, d3.max(datosFiltrados, d => Math.max(d.ventas,
d.gastos))])
.range([dashboardConfig.height - dashboardConfig.margin.bottom,
dashboardConfig.margin.top])
.nice();
// Función de línea
const lineVentas = d3.line()
.x(d => xScale(d.mes) + xScale.bandwidth()/2)
.y(d => yScale(d.ventas))
.curve(d3.curveMonotoneX);
const lineGastos = d3.line()

.x(d => xScale(d.mes) + xScale.bandwidth()/2)
.y(d => yScale(d.gastos))
.curve(d3.curveMonotoneX);
// Línea de ventas
svg.append("path")
.datum(datosFiltrados)
.attr("fill", "none")
.attr("stroke", dashboardConfig.colors.ventasColor)
.attr("stroke-width", 3)
.attr("d", lineVentas);
// Línea de gastos
svg.append("path")
.datum(datosFiltrados)
.attr("fill", "none")
.attr("stroke", dashboardConfig.colors.gastosColor)
.attr("stroke-width", 3)
.attr("d", lineGastos);
// Puntos de ventas
svg.selectAll(".dot-ventas")
.data(datosFiltrados)
.enter()
.append("circle")
.attr("class", "dot-ventas")
.attr("cx", d => xScale(d.mes) + xScale.bandwidth()/2)
.attr("cy", d => yScale(d.ventas))
.attr("r", 5)
.attr("fill", dashboardConfig.colors.ventasColor)
.attr("stroke", "white")
.attr("stroke-width", 2);
// Puntos de gastos
svg.selectAll(".dot-gastos")
.data(datosFiltrados)
.enter()
.append("circle")
.attr("class", "dot-gastos")
.attr("cx", d => xScale(d.mes) + xScale.bandwidth()/2)
.attr("cy", d => yScale(d.gastos))
.attr("r", 5)
.attr("fill", dashboardConfig.colors.gastosColor)
.attr("stroke", "white")
.attr("stroke-width", 2);
// Ejes
svg.append("g")

.attr("transform", `translate(0,${dashboardConfig.height -
dashboardConfig.margin.bottom})`)
.call(d3.axisBottom(xScale));
svg.append("g")
.attr("transform", `translate(${dashboardConfig.margin.left},0)`)
.call(d3.axisLeft(yScale).tickFormat(d => `${d}`));
// Leyenda
const legend = svg.append("g")
.attr("transform", `translate(${dashboardConfig.width - 150}, 50)`);
legend.append("line")
.attr("x1", 0).attr("x2", 20)
.attr("y1", 0).attr("y2", 0)
.attr("stroke", dashboardConfig.colors.ventasColor)
.attr("stroke-width", 3);
legend.append("text")
.attr("x", 25).attr("y", 5)
.text("Ventas")
.style("font-size", "12px");
legend.append("line")
.attr("x1", 0).attr("x2", 20)
.attr("y1", 20).attr("y2", 20)
.attr("stroke", dashboardConfig.colors.gastosColor)
.attr("stroke-width", 3);
legend.append("text")
.attr("x", 25).attr("y", 25)
.text("Gastos")
.style("font-size", "12px");
// Título
svg.append("text")
.attr("x", dashboardConfig.width / 2)
.attr("y", 25)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.style("fill", "#2c3e50")
.text("Tendencia de Ventas vs Gastos");
return svg.node();
}
Insert cell
{
const totalVentas = d3.sum(datosFiltrados, d => d.ventas);
const totalGastos = d3.sum(datosFiltrados, d => d.gastos);
const beneficioTotal = totalVentas - totalGastos;
const promedioVentas = d3.mean(datosFiltrados, d => d.ventas);
const div = d3.create("div")
.style("background", "#fff")
.style("padding", "20px")
.style("border-radius", "8px")
.style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)")
.style("margin-top", "20px")
.style("font-family", "Arial, sans-serif");
div.append("h3")
.text("📊 Resumen Estadístico")
.style("color", "#2c3e50")
.style("margin-bottom", "15px");
const stats = [
{label: "Total Ventas", value: `${totalVentas.toLocaleString()}`, color:
"#2ecc71"},
{label: "Total Gastos", value: `${totalGastos.toLocaleString()}`, color:
"#f39c12"},
{label: "Beneficio Total", value: `${beneficioTotal.toLocaleString()}`,
color: "#9b59b6"},
{label: "Promedio Ventas", value:
`${Math.round(promedioVentas).toLocaleString()}`, color: "#3498db"}
];
const statsContainer = div.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(200px, 1fr))")
.style("gap", "15px");
stats.forEach(stat => {
const statBox = statsContainer.append("div")
.style("padding", "15px")
.style("background", "#f8f9fa")
.style("border-radius", "6px")
.style("border-left", `4px solid ${stat.color}`);
statBox.append("div")
.text(stat.label)
.style("font-size", "14px")
.style("color", "#666")
.style("margin-bottom", "5px");

statBox.append("div")
.text(stat.value)
.style("font-size", "24px")
.style("font-weight", "bold")
.style("color", stat.color);
});
return div.node();
}
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