{
"name": "bubble",
"children": [
{
"name": "Atlas",
"description": "Atlas of Global Agriculture",
"children": [
{
"name": "",
"address": "",
"note": ""
},
]
},
]
}
import * as d3 from 'd3';
export default function bubbleChart(id, dataset) {
let w = window.innerWidth * 0.68 * 0.95;
let h = Math.ceil(w);
let oR = 0;
let nTop = 0;
const colVals = d3.scaleOrdinal(d3.schemeCategory10);
const svgContainer = d3.select('#mainBubble').style('height', `${h}px`);
const svg = d3
.select(id)
.append('svg')
.attr('class', 'mainBubbleSVG')
.attr('width', w)
.attr('height', h)
.on('mouseleave', function() {
return resetBubbles();
});
const bubbleObj = svg
.selectAll('.topBubble')
.data(dataset.children)
.enter()
.append('g')
.attr('id', (d, i) => {
return `topBubbleAndText_${i}`;
});
nTop = dataset.children.length;
oR = w / (1 + 3 * nTop);
h = Math.ceil((w / nTop) * 2);
svgContainer.style('height', `${h}px`);
bubbleObj
.append('circle')
.attr('class', 'topBubble')
.attr('id', (d, i) => {
return `topBubble${i}`;
})
.attr('r', () => {
return oR;
})
.attr('cx', (d, i) => {
return oR * (3 * (1 + i) - 1);
})
.attr('cy', (h + oR) / 3)
.style('fill', (d, i) => {
return colVals(i);
})
.style('opacity', 0.3)
.on('mouseover', (d, i) => {
return activateBubble(d, i);
});
bubbleObj
.append('text')
.attr('class', 'topBubbleText')
.style('fill', (d, i) => {
return colVals(i);
})
.attr('x', (d, i) => {
return oR * (3 * (1 + i) - 1);
})
.attr('y', (h + oR) / 3)
.attr('font-size', 12)
.attr('text-anchor', 'middle')
.text(d => d.name);
dataset.children.map((value, index) => {
return renderChildBubble(value.children, index);
});
function renderChildBubble(value, index) {
const childBubbleObj = svg
.selectAll('.childBubble')
.data(value)
.enter()
.append('g');
childBubbleObj
.append('circle')
.attr('class', `childBubble${index}`)
.attr('r', () => {
return oR / 3;
})
.attr('cx', (d, i) => {
return oR * (3 * (index + 1) - 1) + oR * 1.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926);
})
.attr('cy', (d, i) => {
return (h + oR) / 3 + oR * 1.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
})
.style('opacity', 0.5)
.style('fill', colVals(index));
childBubbleObj
.append('text')
.attr('class', `childBubbleText${index}`)
.attr('x', function(d, i) {
return oR * (3 * (index + 1) - 1) + oR * 1.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926);
})
.attr('y', function(d, i) {
return (h + oR) / 3 + oR * 1.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
})
.attr('text-anchor', 'middle')
.attr('font-size', 6)
.style('opacity', 0.5)
.text(d => d.name);
}
function activateBubble(d, i) {
const t = svg.transition().duration(d3.event.altKey ? 7500 : 350);
t.selectAll('.topBubble')
.attr('cx', (_, ii) => {
if (i === ii) {
return oR * (3 * (1 + ii) - 1) - 0.6 * oR * (ii - 1);
} else {
if (ii < i) {
return oR * 0.6 * (3 * (1 + ii) - 1);
}
return oR * (nTop * 3 + 1) - oR * 0.6 * (3 * (nTop - ii) - 1);
}
})
.attr('r', (_, ii) => {
if (i === ii) return oR * 1.8;
return oR * 0.8;
});
t.selectAll('.topBubbleText')
.attr('x', (_, ii) => {
if (i === ii) {
return oR * (3 * (1 + ii) - 1) - 0.6 * oR * (ii - 1);
} else {
if (ii < i) {
return oR * 0.6 * (3 * (1 + ii) - 1);
}
return oR * (nTop * 3 + 1) - oR * 0.6 * (3 * (nTop - ii) - 1);
}
})
.attr('font-size', (_, ii) => {
if (i === ii) return 30;
return 12 * 0.8;
});
let signSide = -1;
for (let k = 0; k < nTop; k += 1) {
signSide = 1;
mapBubble(t, k, i, signSide);
}
}
function mapBubble(t, k, a, signSide) {
if (k < nTop / 2) {
signSide = 1;
}
t.selectAll(`.childBubbleText${k}`)
.attr('x', (d, i) => {
return (
oR * (3 * (k + 1) - 1) -
0.6 * oR * (k - 1) +
signSide * oR * 2.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926)
);
})
.attr('y', (d, i) => {
return (h + oR) / 3 + signSide * oR * 2.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
})
.attr('font-size', () => {
return k === a ? 12 : 6;
})
.style('opacity', () => {
return k === a ? 1 : 0;
});
t.selectAll(`.childBubble${k}`)
.attr('cx', (d, i) => {
return (
oR * (3 * (k + 1) - 1) -
0.6 * oR * (k - 1) +
signSide * oR * 2.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926)
);
})
.attr('cy', (d, i) => {
return (h + oR) / 3 + signSide * oR * 2.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
})
.attr('r', () => {
return k === a ? oR * 0.55 : oR / 3.0;
})
.style('opacity', function() {
return k === a ? 1 : 0;
});
}
function resetBubbles() {
w = window.innerWidth * 0.68 * 0.95;
oR = w / (1 + 3 * nTop);
h = Math.ceil((w / nTop) * 2);
svgContainer.style('height', `${h}px`);
svg.attr('width', w);
svg.attr('height', h);
const t = svg.transition().duration(650);
t.selectAll('.topBubble')
.attr('r', () => {
return oR;
})
.attr('cx', (d, i) => {
return oR * (3 * (1 + i) - 1);
})
.attr('cy', (h + oR) / 3);
t.selectAll('.topBubbleText')
.attr('font-size', 12)
.attr('x', function(d, i) {
return oR * (3 * (1 + i) - 1);
})
.attr('y', (h + oR) / 3);
for (let k = 0; k < nTop; k += 1) {
restmapBubble(t, k);
}
}
function restmapBubble(t, k) {
t.selectAll(`.childBubbleText${k}`)
.attr('x', function(d, i) {
return oR * (3 * (k + 1) - 1) + oR * 1.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926);
})
.attr('y', function(d, i) {
return (h + oR) / 3 + oR * 1.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
})
.attr('font-size', 6)
.style('opacity', 0.5);
t.selectAll(`.childBubble${k}`)
.attr('r', () => {
return oR / 3.0;
})
.style('opacity', 0.5)
.attr('cx', (d, i) => {
return oR * (3 * (k + 1) - 1) + oR * 1.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926);
})
.attr('cy', (d, i) => {
return (h + oR) / 3 + oR * 1.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
});
}
}