Published
Edited
Nov 7, 2020
1 star
Insert cell
Insert cell
chart = {
// init
const svg = d3.create('svg');
const g = svg.append('g');

// prepare arrows
const markerBoxWidth = 20;
const markerBoxHeight = 20;
const refX = markerBoxWidth / 2;
const refY = markerBoxHeight / 2;
svg
.append('defs')
.append('marker')
.attr('id', 'arrow')
.attr('viewBox', [0, 0, markerBoxWidth, markerBoxHeight])
.attr('refX', refX)
.attr('refY', refY)
.attr('markerWidth', markerBoxWidth)
.attr('markerHeight', markerBoxHeight)
.attr('orient', 'auto-start-reverse')
.append('path')
.attr('d', d3.line()(arrowPoints))
.attr('stroke', 'black')
.attr('opacity', .3);

svg.call(size, () => [width, config.height]);
g.call(trans, () => [config.margin, config.margin]);

const enterCircle = node =>
node
.append('circle')
.attr('r', config.radius)
.attr('fill', d => colorScale(d.id));
const enterText = node =>
node
.append('text')
.text(d => d.id)
.attr('alignment-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('font-family', 'sans-serif')
.attr('font-size', '10px');

const enterNode = node =>
node
.append('g')
.attr('class', 'spot')
.attr('data-id', d => d.id)
.call(enterCircle)
.call(enterText);

const updateNode = node => node.call(trans, d => [scaleX(d.x), scaleY(d.y)]);

const lineGen = d3.line();

const enterLink = link =>
link
.append('path')
.attr('stroke', 'black')
.attr('marker-end', 'url(#arrow)')
.attr('opacity', .3)
.call(updateLink);
const updateLink = link =>
link.attr('d', d => {
const sourceLoc = [scaleX(d.sourceData.x), scaleY(d.sourceData.y)];
const targetLoc = [scaleX(d.targetData.x), scaleY(d.targetData.y)];
return lineGen([sourceLoc, targetLoc]);
});

function render(_data, _links) {
const xExtent = d3.extent(_data.map(d => d.x));
const yExtent = d3.extent(_data.map(d => d.y));
// console.log(xExtent, yExtent);
scaleX.domain(xExtent).range([0, width - config.margin]);
scaleY.domain(yExtent).range([0, config.height - config.margin * 2]);

const nodes = g
.selectAll('.spot')
.data(_data)
.join(enterNode, updateNode);

const dataById = getDataById(_data);
// const compositeLinks = _links.map(l => {
// const sourceData = dataById.get(l.source) || [{ x: 0, y: 0 }];
// const targetData = dataById.get(l.target) || [{ x: 0, y: 0 }];
// return { ...l, sourceData: sourceData[0], targetData: targetData[0] };
// });
const compositeLinks = _links.map(l => {
return { ...l, sourceData: l.source, targetData: l.target };
});

console.log(compositeLinks);

const links = g
.selectAll('path')
.data(compositeLinks)
.join(enterLink, updateLink);
}

return Object.assign(svg.node(), { render });
}
Insert cell
chart.render(data, links)
Insert cell
simulation = d3
.forceSimulation()
.nodes(data)
.force('charge', d3.forceManyBody())
.force(
'link',
d3
.forceLink()
.links(links)
.id(d => d.id)
)
.on('tick', () => {
chart.render(data, links);
})
Insert cell
Insert cell
data = [
{ id: 'spot1' },
{ id: 'spot2' },
{ id: 'spot3' },
{ id: 'spot4' },
{ id: 'spot5' },
{ id: 'spot6' }
]
Insert cell
sampleDataById = d3.group(data, d => d.id)
Insert cell
getDataById = _data => d3.group(_data, d => d.id)
Insert cell
Insert cell
Insert cell
scaleX = d3.scaleLinear()
Insert cell
scaleY = d3.scaleLinear()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
config = ({
margin: 50,
height: 300,
radius: 20
})
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