I have data nested into 3 levels, which I need to dynamically update. The kicker is that the elements for the mid-level need to actually display on TOP of the elements for the low-level due to some hover behavior I need, so I'm having trouble with what the enter/update/exit/merge pattern should look like. (There don't need to be any elements displayed for the high-level).
The code I have right now updates the data successfully but is not rendering the rectangles at all, instead giving me an error, Uncaught TypeError: this.setAttribute is not a function.
How do I fix this problem, please?
Here's what it should look like before updating:
And here's what it should look like after updating:
Here's a CodePen with the code Below
```
let width = 0.9 * window.innerWidth,
height = 0.9 * window.innerHeight,
colors = ['darkviolet', 'steelblue', 'coral', 'Turquoise', 'firebrick', 'mediumslateblue', 'palevioletred', 'green', 'aqua'];
let data1 =
[{"group":"A","segment":"1","item":"1"},
{"group":"A","segment":"1","item":"2"},
{"group":"A","segment":"1","item":"3"},
{"group":"A","segment":"2","item":"4"},
{"group":"A","segment":"2","item":"5"},
{"group":"A","segment":"2","item":"6"},
{"group":"A","segment":"3","item":"7"},
{"group":"A","segment":"3","item":"8"},
{"group":"A","segment":"3","item":"9"},
{"group":"B","segment":"4","item":"1"},
{"group":"B","segment":"4","item":"2"},
{"group":"B","segment":"4","item":"3"},
{"group":"B","segment":"5","item":"4"},
{"group":"B","segment":"5","item":"5"},
{"group":"B","segment":"5","item":"6"},
{"group":"B","segment":"6","item":"7"},
{"group":"B","segment":"6","item":"8"},
{"group":"B","segment":"6","item":"9"},
{"group":"C","segment":"7","item":"1"},
{"group":"C","segment":"7","item":"2"},
{"group":"C","segment":"7","item":"3"},
{"group":"C","segment":"8","item":"4"},
{"group":"C","segment":"8","item":"5"},
{"group":"C","segment":"8","item":"6"},
{"group":"C","segment":"9","item":"7"},
{"group":"C","segment":"9","item":"8"},
{"group":"C","segment":"9","item":"9"}],
data2 =
[{"group":"A","segment":"1","item":"1"},
{"group":"A","segment":"8","item":"2"},
{"group":"A","segment":"9","item":"3"},
{"group":"A","segment":"2","item":"4"},
{"group":"A","segment":"2","item":"5"},
{"group":"A","segment":"2","item":"6"},
{"group":"A","segment":"5","item":"7"},
{"group":"A","segment":"3","item":"8"},
{"group":"A","segment":"3","item":"9"},
{"group":"B","segment":"4","item":"1"},
{"group":"B","segment":"4","item":"2"},
{"group":"B","segment":"7","item":"3"},
{"group":"B","segment":"5","item":"4"},
{"group":"B","segment":"5","item":"5"},
{"group":"B","segment":"5","item":"6"},
{"group":"B","segment":"5","item":"7"},
{"group":"B","segment":"6","item":"8"},
{"group":"B","segment":"6","item":"9"},
{"group":"C","segment":"7","item":"1"},
{"group":"C","segment":"7","item":"2"},
{"group":"C","segment":"3","item":"3"},
{"group":"C","segment":"8","item":"4"},
{"group":"C","segment":"8","item":"5"},
{"group":"C","segment":"8","item":"6"},
{"group":"C","segment":"9","item":"7"},
{"group":"C","segment":"6","item":"8"},
{"group":"C","segment":"1","item":"9"}];
let button = d3.select('body')
.append('button')
.attr('type', 'button')
.style('display', 'block')
.text('Update')
.on('click', function() { update(data2) });
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.append('g');
let color = d3.scaleOrdinal().range(colors);
update(data1);
function getxy(data) {
let grouped = Array.from(d3.group(data, d=> d.group, d=> d.segment), ([key, value]) => ({key, value}));
grouped.forEach(function(s) {
s.value = Array.from(s.value, ([key, value]) => ({key, value}));
s.value.forEach(function(d) {
d.start = d3.min(d.value, function(t) { t.segment = +t.segment; t.item = +t.item; return +t.item });
d.end = d3.max(d.value, function(t) { return t.item });
d.key = +d.key;
d.group = s.key;
})
})
let x1 = d3.scaleBand()
.domain([1, 2, 3, 4, 5, 6, 7, 8, 9])
.range([width*0.05, width])
.padding(0.0);
let y1 = d3.scaleBand()
.domain(['A', 'B', 'C'])
.range([10, height])
.padding(0.1);
return [x1, y1, grouped];
}
function update(data) {
let xy = getxy(data);
let x = xy[0], y = xy[1], groupedData = xy[2];
let barsAll = svg
.selectAll('.bars')
.data(groupedData);
barsAll.exit().remove();
let barsEnter = barsAll
.enter()
.append('g')
.attr('class', 'bars');
barsEnter = barsEnter.merge(barsAll);
let segmentsAll = barsEnter
.selectAll('.segments')
.data(function(d) { return d.value });
segmentsAll.exit().remove();
let segmentsEnter = segmentsAll.enter();
let bitsAll = segmentsEnter
.selectAll('.bits')
.data(function(d) { return d.value });
bitsAll.exit().remove();
let bitsEnter = bitsAll
.enter()
.append('circle')
.attr('class', 'bits')
.attr('r', width*0.05)
.attr('stroke', 'none');
bitsEnter = bitsEnter.merge(bitsAll);
bitsEnter
.attr('cx', function(d) { return x(d.item) })
.attr('cy', function(d) { return y(d.group) + y.bandwidth()/2 })
.attr('fill', function(d) { return color(d.segment) });
segmentsEnter.append('rect')
.attr('stroke', 'black')
.attr('class', 'segments')
.style('fill-opacity', 0.2);
segmentsEnter = segmentsEnter.merge(segmentsAll);
segmentsEnter
.attr('fill', function(d) { return color(d.key) })
.attr('height', y.bandwidth()*0.75)
.attr('x', function(d) { return x(d.start) - width*0.05 })
.attr('y', function(d) { return y(d.group) + y.bandwidth()*0.125 })
.attr('width', function(d) { return x(d.end) - x(d.start) + width*0.1 });
}
```
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-array.v2.min.js"></script>
Well, by going around the “merge” step on the mid-level segments and low-level bits (but not on the top-level bars), I was able to hack a fix, finally. Working pen
Still open to help from others because I feel like I should really get the hang of the whole flow - update, enter, exit, merge - at some point.
let width = 0.9 * window.innerWidth,
height = 0.9 * window.innerHeight,
colors = ['darkviolet', 'steelblue', 'coral', 'Turquoise', 'firebrick', 'mediumslateblue', 'palevioletred', 'green', 'aqua'];
let data1 =
[{"group":"A","segment":"1","item":"1"},
{"group":"A","segment":"1","item":"2"},
{"group":"A","segment":"1","item":"3"},
{"group":"A","segment":"2","item":"4"},
{"group":"A","segment":"2","item":"5"},
{"group":"A","segment":"2","item":"6"},
{"group":"A","segment":"3","item":"7"},
{"group":"A","segment":"3","item":"8"},
{"group":"A","segment":"3","item":"9"},
{"group":"B","segment":"4","item":"1"},
{"group":"B","segment":"4","item":"2"},
{"group":"B","segment":"4","item":"3"},
{"group":"B","segment":"5","item":"4"},
{"group":"B","segment":"5","item":"5"},
{"group":"B","segment":"5","item":"6"},
{"group":"B","segment":"6","item":"7"},
{"group":"B","segment":"6","item":"8"},
{"group":"B","segment":"6","item":"9"},
{"group":"C","segment":"7","item":"1"},
{"group":"C","segment":"7","item":"2"},
{"group":"C","segment":"7","item":"3"},
{"group":"C","segment":"8","item":"4"},
{"group":"C","segment":"8","item":"5"},
{"group":"C","segment":"8","item":"6"},
{"group":"C","segment":"9","item":"7"},
{"group":"C","segment":"9","item":"8"},
{"group":"C","segment":"9","item":"9"}],
data2 =
[{"group":"A","segment":"1","item":"1"},
{"group":"A","segment":"8","item":"2"},
{"group":"A","segment":"9","item":"3"},
{"group":"A","segment":"2","item":"4"},
{"group":"A","segment":"2","item":"5"},
{"group":"A","segment":"2","item":"6"},
{"group":"A","segment":"5","item":"7"},
{"group":"A","segment":"3","item":"8"},
{"group":"A","segment":"3","item":"9"},
{"group":"B","segment":"4","item":"1"},
{"group":"B","segment":"4","item":"2"},
{"group":"B","segment":"7","item":"3"},
{"group":"B","segment":"5","item":"4"},
{"group":"B","segment":"5","item":"5"},
{"group":"B","segment":"5","item":"6"},
{"group":"B","segment":"5","item":"7"},
{"group":"B","segment":"6","item":"8"},
{"group":"B","segment":"6","item":"9"},
{"group":"C","segment":"7","item":"1"},
{"group":"C","segment":"7","item":"2"},
{"group":"C","segment":"3","item":"3"},
{"group":"C","segment":"8","item":"4"},
{"group":"C","segment":"8","item":"5"},
{"group":"C","segment":"8","item":"6"},
{"group":"C","segment":"9","item":"7"},
{"group":"C","segment":"6","item":"8"},
{"group":"C","segment":"1","item":"9"}];
let button = d3.select('body')
.append('button')
.attr('type', 'button')
.style('display', 'block')
.text('Update')
.on('click', function() { update(data2) });
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.append('g');
let color = d3.scaleOrdinal().range(colors);
function getxy(data) {
let grouped = Array.from(d3.group(data, d=> d.group, d=> d.segment), ([key, value]) => ({key, value}));
grouped.forEach(function(s) {
s.value = Array.from(s.value, ([key, value]) => ({key, value}));
s.value.forEach(function(d) {
d.start = d3.min(d.value, function(t) { t.segment = +t.segment; t.item = +t.item; return +t.item });
d.end = d3.max(d.value, function(t) { return t.item });
d.key = +d.key;
d.group = s.key;
})
})
let x1 = d3.scaleBand()
.domain([1, 2, 3, 4, 5, 6, 7, 8, 9])
.range([width*0.05, width])
.padding(0.0);
let y1 = d3.scaleBand()
.domain(['A', 'B', 'C'])
.range([10, height])
.padding(0.1);
return [x1, y1, grouped];
}
function update(data) {
let xy = getxy(data);
let x = xy[0], y = xy[1], groupedData = xy[2];
// update
let barsAll = svg
.selectAll('.bars')
.data(groupedData);
// exit
barsAll.exit().remove();
// enter
let barsEnter = barsAll
.enter();
barsEnter = barsEnter.merge(barsAll).append('g');
barsEnter.selectAll('.segments').remove();
d3.selectAll('.segments').remove();
let segmentsAll = barsEnter
.selectAll('.segments')
.data(function(d) { return d.value });
segmentsAll.exit().remove();
let segmentsEnter = segmentsAll
.enter();
let bitsAll = segmentsEnter
.selectAll('.bits')
.data(function(d) { return d.value });
bitsAll.exit().remove();
bitsAll
.enter()
.append('circle')
.attr('class', 'bits')
.attr('r', width*0.05)
.attr('stroke', 'none')
.attr('cx', function(d) { return x(d.item) })
.attr('cy', function(d) { return y(d.group) + y.bandwidth()/2 })
.attr('fill', function(d) { return color(d.segment) });
// bitsEnter = bitsEnter.merge(bitsAll);
bitsAll
.attr('cx', function(d) { return x(d.item) })
.attr('cy', function(d) { return y(d.group) + y.bandwidth()/2 })
.attr('fill', function(d) { return color(d.segment) });
segmentsEnter
.append('rect')
.attr('class', 'segments')
.attr('stroke', 'black')
.style('fill-opacity', 0.2)
.attr('fill', function(d) { return color(d.key) })
.attr('height', function() { return y.bandwidth()*0.75 })
.attr('x', function(d) { return x(d.start) - width*0.05 })
.attr('y', function(d) { return y(d.group) + y.bandwidth()*0.125 })
.attr('width', function(d) { return x(d.end) - x(d.start) + width*0.1 });
segmentsAll
.attr('fill', function(d) { return color(d.key) })
.attr('height', function() { return y.bandwidth()*0.75 })
.attr('x', function(d) { return x(d.start) - width*0.05 })
.attr('y', function(d) { return y(d.group) + y.bandwidth()*0.125 })
.attr('width', function(d) { return x(d.end) - x(d.start) + width*0.1 });
//segmentsAll = segmentsEnter.merge(segmentsAll);
// segmentsEnter
// .attr('fill', function(d) { return color(d.key) })
// .attr('height', function() { return y.bandwidth()*0.75 })
// .attr('x', function(d) { return x(d.start) - width*0.05 })
// .attr('y', function(d) { return y(d.group) + y.bandwidth()*0.125 })
// .attr('width', function(d) { return x(d.end) - x(d.start) + width*0.1 });
}
update(data1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script>
<script src="https://d3js.org/d3-array.v2.min.js"></script>
I've been implementing a force layout in d3js and I can zoom and drag the layout / nodes.
When I'm dragging a node, the behavior is fine, except that when I stopped moving, the node instantly starts moving back to it's original position, even though I haven't released the mouse click and so the event should still be considered in progress.
I tried to fix the behavior by setting d.fx and d.fy to current values, which doesn't solve anything.
Here's the code:
const WIDTH = 1600;
const HEIGHT = 900;
const V_MARGIN = 20;
const H_MARGIN = 50;
const ALPHA_DECAY = 0.03;
const VELOCITY_DECAY = 0.6;
const LINK_DISTANCE = Math.min(WIDTH, HEIGHT) / 10;
const CHARGE_FORCE = -Math.min(WIDTH, HEIGHT) / 3;
const ITERATIONS = 16;
const CIRCLE_WIDTH = 3;
const ON_HOVER_OPACITY = 0.1;
const c10 = d3.scaleOrdinal(d3.schemeCategory10);
const SVG = d3.select('body')
.append('svg')
.attr('width', WIDTH)
.attr('height', HEIGHT)
;
const g = SVG.append('g')
.attr('class', 'everything')
;
d3.json('got_social_graph.json')
.then(data => {
const nodes = data.nodes;
const links = data.links;
//Create force layout
const force = d3.forceSimulation()
.nodes(nodes)
.alphaDecay(ALPHA_DECAY)
.velocityDecay(VELOCITY_DECAY)
.force('links', d3.forceLink(links).distance(LINK_DISTANCE))
.force("collide",d3.forceCollide(d => d.influence > 15 ? d.influence : 15).iterations(ITERATIONS))
.force('charge_force', d3.forceManyBody().strength(CHARGE_FORCE))
.force('center_force', d3.forceCenter((WIDTH - H_MARGIN) / 2, (HEIGHT - V_MARGIN) / 2))
.force("y", d3.forceY(0))
.force("x", d3.forceX(0))
;
//Create links
const link = g.append('g')
.attr('class', 'link')
.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke-width', d => d.weight / 10)
;
//Create nodes elements
const node = g.append('g')
.attr('class', 'node')
.selectAll('circle')
.data(nodes)
.enter()
.append('g')
;
//Append circles to nodes
const circle = node.append('circle')
.attr('r', d => d.influence > 10 ? d.influence : 10)
.attr('stroke-width', CIRCLE_WIDTH)
.attr('stroke', d => c10(d.zone * 10))
.attr('fill', 'black')
;
/*const label = node.append('text')
.attr('x', 12)
.attr('y', '0.25em')
.attr('font-size', d => d.influence * 1.5 > 9 ? d.influence * 1.5 : 9)
.text(d => d.character)
;*/
//Refresh force layout data
force.on('tick', () => {
node.attr('cx', d => d.x = Math.max(H_MARGIN, Math.min(WIDTH - H_MARGIN, d.x - H_MARGIN)))
.attr('cy', d => d.y = Math.max(V_MARGIN, Math.min(HEIGHT - V_MARGIN, d.y - V_MARGIN)))
.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
link.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y)
;
});
//Handle mouse events
circle
.on('click', (d, k, n) => {
d3.select(n[k])
})
.on('mouseover', (d, k, n) => {
})
.on('mouseout', (d, k, n) => {
circle
.attr('opacity', 1)
})
;
//Handle translation events
node.call(d3.drag()
.on('start', (d, n, k) => {
if (!d3.event.active) force.alphaTarget(0.3).restart();
})
.on('drag', (d, n, k) => {
d3.select(n[k])
.attr('cx', d.x = d3.event.x)
.attr('cy', d.y = d3.event.y)
})
.on('end', (d, n, k) => {
if (!d3.event.active) force.alphaTarget(0);
}))
;
//Handle zoom events
g.call(d3.zoom()
.scaleExtent([0.8, 2.5])
.on('start', () => {
if (!d3.event.active) force.alphaTarget(0.3).restart();
d3.event.sourceEvent.stopPropagation();
})
.on('zoom', () => {
if (!d3.event.active) force.alphaTarget(0.3).restart();
g.attr('transform', d3.event.transform)
})
.on('end', () => {
if (!d3.event.active) force.alphaTarget(0);
})
);
});
I want the node to stay over my mouse cursor until the click is released. The node doesn't need to be sticky.
I am drawing a simple bar chart with two buttons to toggle the chart based on two datasets.
However, the chart does not do the transition when I click on the buttons.
The data is loaded from a csv file.
party,ge14,latest
PKR,47,50
DAP,42,42
Umno,54,38
GPS,0,19
PAS,18,18
Bersatu,13,16
Independent,3,12
Amanah,11,11
Warisan,8,9
GBS,0,3
Other BN parties,25,2
Upko,0,1
Source code
d3.csv('data/seatcount.csv')
.then(data => {
const width = 900,
height = 700,
margin = 25;
const svg = d3.select('#bar')
.append('svg')
.attr('width', width)
.attr('height', height)
.attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height))
.attr('preserveAspectRatio', 'xMinYMin')
.append('g');
// define scale
const xScale = d3.scaleBand()
.domain(data.map(d => d.party))
.range([margin, (width - margin)])
.padding(.2);
const yScale = d3.scaleLinear()
.domain([0, 60])
.range([(height - margin), margin]);
// define axes
const xAxis = d3.axisBottom(xScale)
.ticks(12)
const yAxis = d3.axisLeft(yScale)
.ticks(6);
svg.append('g')
.attr('transform', `translate(0, ${height - margin})`)
.call(xAxis)
.style('font-size', '.7em');
svg.append('g')
.attr('transform', `translate(${margin}, 0)`)
.call(yAxis)
.style('font-size', '.7em');
// plot columns
let cols = svg.selectAll('.col')
.data(data)
.enter();
cols.append('rect')
.attr('x', (d) => xScale(d.party))
.attr('y', (height - margin))
.attr('width', xScale.bandwidth())
.attr('height', 0)
.style('fill', '#dddddd')
.transition()
.delay((d, i) => 100 * i)
.attr('y', (d) => {
return yScale(d.latest)
})
.attr('height', (d) => (height - margin - yScale(d.latest)));
// label the bars
cols.append('text')
.attr('x', d => {
return (xScale(d.party) + xScale.bandwidth() / 2);
})
.attr('y', d => {
return (yScale(d.latest) + 13);
})
.style('fill', '#333333')
.attr('text-anchor', 'middle')
.text(d => d.latest)
.style('font-size', '.8em')
.style('visibility', 'hidden')
.transition()
.delay((d, i) => 100 * i)
.style('visibility', 'visible');
function moveCols(data, period) {
cols.data(data)
.transition()
.attr('x', function(d) {
return xScale(d.party);
})
.attr('y', function(d) {
console.log(yScale(d[period]));
return yScale(d[period]);
})
.attr('width', xScale.bandwidth())
.attr('height', function(d) {
return (height - margin - yScale(d[period]));
});
cols.selectAll('text')
.remove();
cols.append('text')
.attr('x', d => {
return (xScale(d.party) + xScale.bandwidth() / 2);
})
.attr('y', d => {
return (yScale(d[period]) + 13);
})
.style('fill', '#333333')
.attr('text-anchor', 'middle')
.text(d => d[period])
.style('font-size', '.8em')
.style('visibility', (d, i) => {
// console.log(d[period]);
if (d[period] <= 0) {
return 'hidden';
} else {
return 'visible';
}
});
}
d3.select('#latest').on('click', () => {
moveCols(data, 'latest')
});
d3.select('#ge14').on('click', () => {
moveCols(data, 'ge14')
})
});
In the end, once I have clicked on #latest or #ge14, only the label of the bars changed, but not the bars themselves.
And there are errors showed in the console.
Uncaught TypeError: this.getAttribute is not a function
at ot.<anonymous> (d3.v5.min.js:2)
at ot.e (d3.v5.min.js:2)
at o (d3.v5.min.js:2)
at d3.v5.min.js:2
at fr (d3.v5.min.js:2)
at cr (d3.v5.min.js:2)
I have reformatted my code and it works! I got no complaint.
const margin = { top: 20, right: 20, bottom: 50, left: 40 },
width = 900
height = 700
const svg = d3.select('#bar')
.append('svg')
.attr('width', width)
.attr('height', height)
.attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height))
.attr('preserveAspectRatio', 'xMinYMin');
const x = d3.scaleBand()
.rangeRound([0, (width - margin.left - margin.right)])
.padding(0.1);
const y = d3.scaleLinear()
.rangeRound([(height - margin.bottom), 0]);
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
d3.csv('data/seatcount.csv')
.then(data => {
data.forEach(d => {
d.party = d.party;
d.ge14 = +d.ge14;
d.latest = +d.latest;
});
x.domain(data.map(d => d.party));
y.domain([0, d3.max(data, d => d.latest)]);
let duration = 1000;
// define axes
g.append('g')
.attr('class', 'axis, x-axis')
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x));
g.append('g')
.attr('class', 'axis, y-axis')
.call(d3.axisLeft(y));
// plot columns
g.selectAll('.col')
.data(data)
.enter()
.append('rect')
.attr('x', d => x(d.party))
.attr('y', height - margin.bottom)
.attr('width', x.bandwidth())
.attr('height', 0)
.attr('class', 'col')
.transition()
.duration(duration)
.attr('y', d => y(d.latest))
.attr('height', d => (height - margin.bottom - y(d.latest)))
.ease(d3.easeBounce);
g.selectAll('.label')
.data(data)
.enter()
.append('text')
.attr('class', 'label')
.attr('x', d => {
return (x(d.party) + x.bandwidth() / 2);
})
.attr('y', (d, i) => {
if (d.latest <= 5) {
return (y(d.latest) - 5);
} else {
return (y(d.latest) + 13);
}
})
.text(d => d.latest)
.style('font-size', '.8em')
.style('visibility', 'hidden')
.transition()
.delay(duration)
.style('visibility', 'visible');
function moveCols(data, period) {
y.domain([0, d3.max(data, d => d[period])]);
g.select('.y-axis')
.transition()
.call(d3.axisLeft(y));
g.selectAll('.label')
.remove();
g.selectAll('.col')
.data(data)
.transition()
.attr('x', d => x(d.party))
.attr('y', d => y(d[period]))
.attr('width', x.bandwidth())
.attr('height', d => (height - margin.bottom - y(d[period])))
.ease(d3.easeBounce);
g.selectAll('.label')
.data(data)
.enter()
.append('text')
.attr('class', 'label')
.attr('x', d => {
return (x(d.party) + x.bandwidth() / 2);
})
.attr('y', (d, i) => {
if (d[period] <= 5) {
return (y(d[period]) - 5);
} else {
return (y(d[period]) + 13);
}
})
.style('fill', '#333333')
.attr('text-anchor', 'middle')
.text(d => d[period])
.style('font-size', '.8em');
}
d3.select('#latest').on('click', () => {
moveCols(data, 'latest');
});
d3.select('#ge14').on('click', () => {
moveCols(data, 'ge14');
})
});
I want to display the nodes and relationship between them from json file.
I am using force layout d3.js but there is alot of movement of nodes when the page is opened.
I want to stop these movements and also want to stop the other node movements when one node is dragged.
Code : http://jsfiddle.net/bhgq017u/13/
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
node,
link;
svg.append('defs').append('marker')
.attrs({'id':'arrowhead',
'viewBox':'-0 -5 10 10',
'refX':13,
'refY':0,
'orient':'auto',
'markerWidth':8,
'markerHeight':8,
'xoverflow':'visible'})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', "gray");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {return d.id;}).distance(500).strength(1))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.json('https://api.myjson.com/bins/beqb0', function (error, graph) {
if (error) throw error;
update(graph.links, graph.nodes);
})
function update(links, nodes) {
link = svg.selectAll(".link")
.data(links)
.enter()
.append("line")
.attr("class", "link")
.attr('marker-end','url(#arrowhead)')
link.append("title")
.text(function (d) {return d.type;});
edgepaths = svg.selectAll(".edgepath")
.data(links)
.enter()
.append('path')
.attrs({
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'id': function (d, i) {return 'edgepath' + i}
})
.style("pointer-events", "none");
edgelabels = svg.selectAll(".edgelabel")
.data(links)
.enter()
.append('text')
.style("pointer-events", "none")
.attrs({
'class': 'edgelabel',
'id': function (d, i) {return 'edgelabel' + i},
'font-size': 10,
'fill': 'black'
});
edgelabels.append('textPath')
.attr('xlink:href', function (d, i) {return '#edgepath' + i})
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(function (d) {return d.type});
node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
// .on("end", dragended)
);
node.append("circle")
.attr("r", 8)
.style("fill", function (d, i) {return colors(i);})
node.append("text")
.attr("dy", -10)
.text(function (d) {return d.name});
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
}
function ticked() {
link
.attr("x1", function (d) {return d.source.x;})
.attr("y1", function (d) {return d.source.y;})
.attr("x2", function (d) {return d.target.x;})
.attr("y2", function (d) {return d.target.y;});
node
.attr("transform", function (d) {return "translate(" + d.x + ", " + d.y + ")";});
edgepaths.attr('d', function (d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
});
edgelabels.attr('transform', function (d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart()
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
In the dragend you must set the alphaTraget to 0.
Remove the forces on dragstart
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
simulation.force("link", null).force("charge", null).force("center", null);
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
}
Edit
If you also want to restrict at startup use
function update(links, nodes) {
// ...
simulation
.nodes(nodes)
.on("tick", ticked)
.stop();
simulation.force("link")
.links(links);
d3.range(100).forEach( () => simulation.tick() );
ticked();
simulation.force("link", null).force("charge", null).force("center", null);
simulation.start();
}