Public
Edited
May 2
Insert cell
Insert cell
Insert cell
circlesData = [
{ x: 50, y: 50, color: 'red' },
{ x: 100, y: 100, color: 'blue' },
{ x: 150, y: 150, color: 'green' },
]
Insert cell
Insert cell
{
// crear elemento SVG externo con tamaño 200x200
const width = 200;
const height = 200;
const radius = 10;
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);
// agregar un fondo
svg.append('rect')
.attr('width', 200)
.attr('height', 200)
.attr('fill', '#F2ECF3');
// crear un círculo por cada elemento en circlesData
// y definir su posición, color y tamaño
const circles = svg.selectAll('circle')
// copiar los datos para no modificar los objetos originales
.data(circlesData.map(d => ({...d})))
.join('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('fill', d => d.color)
.attr('r', radius);
// nuevo código para arrastrar
// cuando comienza el arrastre
function onDragStart(event, d) {
d3.select(this)
// hacer que el círculo arrastrado aparezca encima
// de otros círculos sobre los que se mueve
.raise()
.attr('fill', 'yellow')
.attr('stroke', 'black');
}
// mientras el arrastre está en progreso
function onDrag(event, d) {
d.x = event.x;
d3.select(this)
.attr('cx', d.x)
.attr('cy', d.y = event.y);
}
// cuando termina el arrastre
function onDragEnd(event, d) {
d3.select(this)
.attr('fill', d.color)
.attr('stroke', 'none');
}
const drag = d3.drag()
.on('start', onDragStart)
.on('drag', onDrag)
.on('end', onDragEnd);
circles.call(drag);
return svg.node();
}

Insert cell
Insert cell
{
const width = 200;
const height = 200;
const circleRadius = 10;
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);
svg.append('rect')
.attr('width', 200)
.attr('height', 200)
.attr('fill', '#F2ECF3');
const circles = svg.selectAll('circle')
.data(circlesData.map(d => ({...d})))
.join('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('fill', d => d.color)
.attr('r', circleRadius);
function onDragStart(event, d) {
d3.select(this)
.raise()
.attr('fill', 'yellow')
.attr('stroke', 'black');
}
function onDrag(event, d) {
d3.select(this)
.attr('cx', event.x)
.attr('cy', event.y);
}
function onDragEnd(event, d) {
d3.select(this)
.attr('fill', d.color)
.attr('stroke', 'none');
}
const drag = d3.drag()
.on('start', onDragStart)
.on('drag', onDrag)
.on('end', onDragEnd);
circles.call(drag);
return svg.node();
}

Insert cell
Insert cell
{
const width = 200;
const height = 200;
const radius = 10;
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);
svg.append('rect')
.attr('width', 200)
.attr('height', 200)
.attr('fill', '#F2ECF3');
const circles = svg.selectAll('circle')
.data(circlesData)
.join('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('fill', d => d.color)
.attr('r', radius);

circles.call(circleDrag(width, height, radius));
return svg.node();
}
Insert cell
function circleDrag(width, height, radius) {
function onDragStart(event, d) {
d3.select(this)
.raise()
.attr('fill', 'yellow')
.attr('stroke', 'black');
}
function onDrag(event, d) {
const x = Math.max(radius, Math.min(width - radius, event.x));
const y = Math.max(radius, Math.min(height - radius, event.y));
d3.select(this)
.attr('cx', d.x = x)
.attr('cy', d.y = y);
}
function onDragEnd(event, d) {
d3.select(this)
.attr('fill', d.color)
.attr('stroke', 'none');
}
const drag = d3.drag()
.on('start', onDragStart)
.on('drag', onDrag)
.on('end', onDragEnd);
return drag;
}
Insert cell
Insert cell
{
const width = 200;
const height = 200;
const radius = 10;
let tooltip = d3.select('body').select('.tooltip');
if (tooltip.empty()) {
tooltip = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('background', 'rgba(0, 0, 0, 0.7)')
.style('color', 'white')
.style('padding', '5px')
.style('border-radius', '3px')
.style('pointer-events', 'none')
.style('opacity', 0);
}
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height)
.style('overflow', 'hidden')
.style('display', 'block')
.style('touch-action', 'none');
svg.append('rect')
.attr('width', 200)
.attr('height', 200)
.attr('fill', '#F2ECF3');
const circles = svg.selectAll('circle')
.data(circlesData)
.join('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('fill', d => d.color)
.attr('r', radius);
function circleDrag(width, height, radius) {
let startX, startY, startDX, startDY;
function onDragStart(event, d) {
startX = d.x;
startY = d.y;
startDX = event.x - d.x;
startDY = event.y - d.y;
d3.select(this)
.raise()
.attr('fill', 'yellow')
.attr('stroke', 'black');
tooltip
.style('opacity', 1)
.html(`x: ${Math.round(d.x)}, y: ${Math.round(d.y)}`)
.style('left', (event.sourceEvent.pageX + 10) + 'px')
.style('top', (event.sourceEvent.pageY - 30) + 'px');
}
function onDrag(event, d) {
// limites
const newX = Math.max(radius, Math.min(width - radius, event.x));
const newY = Math.max(radius, Math.min(height - radius, event.y));

const atBoundaryX = newX !== event.x - startDX + startX;
const atBoundaryY = newY !== event.y - startDY + startY;
d3.select(this)
.attr('cx', d.x = newX)
.attr('cy', d.y = newY);
tooltip
.html(`x: ${Math.round(newX)}, y: ${Math.round(newY)}`)
.style('left', (event.sourceEvent.pageX + 10) + 'px')
.style('top', (event.sourceEvent.pageY - 30) + 'px');

if (atBoundaryX || atBoundaryY){
startDX = event.x - newX;
startDY = event.y - newY;
}
}
function onDragEnd(event, d) {
d3.select(this)
.attr('fill', d.color)
.attr('stroke', 'none');
tooltip.style('opacity', 0);
}
const drag = d3.drag()
.on('start', onDragStart)
.on('drag', onDrag)
.on('end', onDragEnd);
return drag;
}
circles.call(circleDrag(width, height, radius));
return svg.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