Related
I trying to create subtransitions using transition.each(), but the transitions created in the each callback does not inherit the transition settings from the parent transition; the following code maintains the 8000 duration under v3, but not under v6.
let dataset = [{
x: 50,
y: 50,
color: 'red'
},
{
x: 100,
y: 50,
color: 'black'
},
{
x: 100,
y: 100,
color: 'blue'
},
{
x: 50,
y: 100,
color: 'yellow'
}
]
let svg = d3.select("body").append("p").append("svg")
.attr("width", 200)
.attr("height", 200)
let circles = svg.selectAll("circle").data(dataset)
.enter()
.append("circle")
.attr("r", 10)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", d => d.color)
parent_transition = d3.select({}).transition()
.duration(8000)
parent_transition.each(() => { // does not inherit duration
circles.transition()
.attr("cx", (d, i) => dataset[(i + 1) % 4].x)
.attr("cy", (d, i) => dataset[(i + 1) % 4].y)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
How should I do the equivalent in v6?
This was a deliberate change made more than 4 years ago, when D3 v4 was released. According to the changelog:
Transitions created this way inherit timing from the closest ancestor element, and thus are synchronized even when the referenced transition has variable timing such as a staggered delay. This method replaces the deeply magical behavior of transition.each in 3.x; in 4.0, transition.each is identical to selection.each. (emphasis mine)
That said, it's not exactly clear what's your goal here. One possible solution is using named transitions:
let dataset = [{
x: 50,
y: 50,
color: 'red'
},
{
x: 100,
y: 50,
color: 'black'
},
{
x: 100,
y: 100,
color: 'blue'
},
{
x: 50,
y: 100,
color: 'yellow'
}
]
let svg = d3.select("body").append("p").append("svg")
.attr("width", 200)
.attr("height", 200)
let circles = svg.selectAll("circle").data(dataset)
.enter()
.append("circle")
.attr("r", 10)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", d => d.color)
const parent_transition = d3.transition()
.duration(8000)
circles.transition(parent_transition)
.attr("cx", (d, i) => dataset[(i + 1) % 4].x)
.attr("cy", (d, i) => dataset[(i + 1) % 4].y);
<script src="https://d3js.org/d3.v6.min.js"></script>
I have a List of items that are inside a circle. I am using hardcoded values for the alignment. I need it to be based off the central point of the circle and by the length of the array.
Need to get rid of these "yAxis: -40, yAxis: -40, yAxis: 0, yAxis: 20";
And also have some space between line items.
const w = 500,
h = 400,
r = 160;
const STREAMS = [{
label: 'Emissions',
isSelected: true,
yAxis: -40
}, {
label: 'Energy Produced',
isSelected: false,
yAxis: -20
}, {
label: 'Energy Consumed',
isSelected: false,
yAxis: 0
}, {
label: 'Intensity',
isSelected: false,
yAxis: 20
}]
const SUB_STREAMS = [{
value: 0.15,
label: 'Total',
isSelected: true
}, {
value: 0.2,
label: 'CO2',
isSelected: false
}, {
value: 0.25,
label: 'Methane',
isSelected: false
}, {
value: 0.30,
label: 'N2O',
isSelected: false
}, {
value: 0.35,
label: 'Other',
isSelected: false
}];
const svg = d3.select("#foo")
.append("svg")
.attr("width", w)
.attr("height", h);
const g = svg.append("g")
.attr("transform", "translate(" + [w / 2, h / 2] + ")");
g.append("circle")
.attr("r", r)
.style("fill", "none")
.style("stroke", "black");
const points = g.selectAll(null)
.data(SUB_STREAMS)
.enter()
.append("circle")
.attr('stroke', 'dodgerblue')
.attr('stroke-width', 1)
.style("fill", function(d) {
return d.isSelected ? 'dodgerblue' : 'white'
})
.attr("r", 12)
.attr("cx", function(d) {
return r * Math.cos(d.value * Math.PI * 2 - Math.PI / 2)
})
.attr("cy", function(d) {
return r * Math.sin(d.value * Math.PI * 2 - Math.PI / 2)
})
points.on("click", function(d) {
console.log(d)
})
g.selectAll(null)
.data(SUB_STREAMS)
.enter()
.append('text')
.style('cursor', 'pointer')
.style('fill', 'black')
.attr('text-anchor', 'right')
.attr('font-size', '1.3em')
.attr('dx', (d) => 14 + r * Math.cos(d.value * Math.PI * 2 - Math.PI / 2))
.attr('dy', (d) => r * Math.sin(d.value * Math.PI * 2 - Math.PI / 2))
.text((d) => d.label)
const text = g
.selectAll('path')
.data(STREAMS)
.enter()
.append("text")
.attr("text-anchor", "left")
.attr('font-size', '1em')
.attr("y", function(d, a) {
return d.yAxis - 5
})
.text((d) => d.label);
text.on("click", function(d) {
console.log(d)
})
var arc = d3.symbol().type(d3.symbolTriangle)
var line = g.selectAll('path')
.data(STREAMS)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', 'red')
.attr('stroke', '#000')
.attr('stroke-width', 1)
.attr('transform', function(d) {
return `translate(-10,${d.yAxis - 5}) rotate(210)`;
});
text {
dominant-baseline: central;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="foo" />
One solution out of many is setting a padding...
const padding = 20
...and translating the container groups by their indices times that padding:
const groups = g
.selectAll('path')
.data(STREAMS)
.enter()
.append("g")
.attr("transform", (_, i) => "translate(0," +
(-padding * (STREAMS.length - 1) / 2 + i * padding) + ")");
Then, you append both texts and paths to those groups.
Here is your code with those changes:
const w = 500,
h = 400,
r = 160,
padding = 20;
const STREAMS = [{
label: 'Emissions',
isSelected: true
}, {
label: 'Energy Produced',
isSelected: false
}, {
label: 'Energy Consumed',
isSelected: false
}, {
label: 'Intensity',
isSelected: false
}]
const SUB_STREAMS = [{
value: 0.15,
label: 'Total',
isSelected: true
}, {
value: 0.2,
label: 'CO2',
isSelected: false
}, {
value: 0.25,
label: 'Methane',
isSelected: false
}, {
value: 0.30,
label: 'N2O',
isSelected: false
}, {
value: 0.35,
label: 'Other',
isSelected: false
}];
const svg = d3.select("#foo")
.append("svg")
.attr("width", w)
.attr("height", h);
const g = svg.append("g")
.attr("transform", "translate(" + [w / 2, h / 2] + ")");
g.append("circle")
.attr("r", r)
.style("fill", "none")
.style("stroke", "black");
const points = g.selectAll(null)
.data(SUB_STREAMS)
.enter()
.append("circle")
.attr('stroke', 'dodgerblue')
.attr('stroke-width', 1)
.style("fill", function(d) {
return d.isSelected ? 'dodgerblue' : 'white'
})
.attr("r", 12)
.attr("cx", function(d) {
return r * Math.cos(d.value * Math.PI * 2 - Math.PI / 2)
})
.attr("cy", function(d) {
return r * Math.sin(d.value * Math.PI * 2 - Math.PI / 2)
})
points.on("click", function(d) {
console.log(d)
})
g.selectAll(null)
.data(SUB_STREAMS)
.enter()
.append('text')
.style('cursor', 'pointer')
.style('fill', 'black')
.attr('text-anchor', 'right')
.attr('font-size', '1.3em')
.attr('dx', (d) => 14 + r * Math.cos(d.value * Math.PI * 2 - Math.PI / 2))
.attr('dy', (d) => r * Math.sin(d.value * Math.PI * 2 - Math.PI / 2))
.text((d) => d.label)
const groups = g
.selectAll('path')
.data(STREAMS)
.enter()
.append("g")
.attr("transform", (_, i) => "translate(0," + (-padding * (STREAMS.length - 1) / 2 + i * padding) + ")");
groups.append("text")
.attr('font-size', '1em')
.text((d) => d.label)
.on("click", function(d) {
console.log(d)
})
var arc = d3.symbol().type(d3.symbolTriangle)
groups.append('path')
.attr('d', arc)
.attr('fill', 'red')
.attr('stroke', '#000')
.attr('stroke-width', 1)
.attr('transform', function(d) {
return "translate(-10,0) rotate(210)";
});
text {
dominant-baseline: central;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="foo" />
And here the same code, with a bigger data array, so you can see that it dynamically sets the positions according to the number of elements:
const w = 500,
h = 400,
r = 160,
padding = 20;
const STREAMS = [{
label: 'Emissions',
isSelected: true
}, {
label: 'Energy Produced',
isSelected: false
}, {
label: 'Energy Consumed',
isSelected: false
}, {
label: 'Intensity',
isSelected: false
}, {
label: 'Foo',
isSelected: false
}, {
label: 'Bar',
isSelected: false
}, {
label: 'Baz',
isSelected: false
}]
const SUB_STREAMS = [{
value: 0.15,
label: 'Total',
isSelected: true
}, {
value: 0.2,
label: 'CO2',
isSelected: false
}, {
value: 0.25,
label: 'Methane',
isSelected: false
}, {
value: 0.30,
label: 'N2O',
isSelected: false
}, {
value: 0.35,
label: 'Other',
isSelected: false
}];
const svg = d3.select("#foo")
.append("svg")
.attr("width", w)
.attr("height", h);
const g = svg.append("g")
.attr("transform", "translate(" + [w / 2, h / 2] + ")");
g.append("circle")
.attr("r", r)
.style("fill", "none")
.style("stroke", "black");
const points = g.selectAll(null)
.data(SUB_STREAMS)
.enter()
.append("circle")
.attr('stroke', 'dodgerblue')
.attr('stroke-width', 1)
.style("fill", function(d) {
return d.isSelected ? 'dodgerblue' : 'white'
})
.attr("r", 12)
.attr("cx", function(d) {
return r * Math.cos(d.value * Math.PI * 2 - Math.PI / 2)
})
.attr("cy", function(d) {
return r * Math.sin(d.value * Math.PI * 2 - Math.PI / 2)
})
points.on("click", function(d) {
console.log(d)
})
g.selectAll(null)
.data(SUB_STREAMS)
.enter()
.append('text')
.style('cursor', 'pointer')
.style('fill', 'black')
.attr('text-anchor', 'right')
.attr('font-size', '1.3em')
.attr('dx', (d) => 14 + r * Math.cos(d.value * Math.PI * 2 - Math.PI / 2))
.attr('dy', (d) => r * Math.sin(d.value * Math.PI * 2 - Math.PI / 2))
.text((d) => d.label)
const groups = g
.selectAll('path')
.data(STREAMS)
.enter()
.append("g")
.attr("transform", (_, i) => "translate(0," + (-padding * (STREAMS.length - 1) / 2 + i * padding) + ")");
groups.append("text")
.attr('font-size', '1em')
.text((d) => d.label)
.on("click", function(d) {
console.log(d)
})
var arc = d3.symbol().type(d3.symbolTriangle)
groups.append('path')
.attr('d', arc)
.attr('fill', 'red')
.attr('stroke', '#000')
.attr('stroke-width', 1)
.attr('transform', function(d) {
return "translate(-10,0) rotate(210)";
});
text {
dominant-baseline: central;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="foo" />
I have the following d3 code:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import * as d3 from 'd3';
import tip from 'd3';
export default class BubbleChart extends Component {
constructor(props){
super(props);
this.renderChart = this.renderChart.bind(this);
this.renderBubbles = this.renderBubbles.bind(this);
this.renderLegend = this.renderLegend.bind(this);
}
componentDidMount() {
this.svg = ReactDOM.findDOMNode(this);
this.renderChart();
}
componentDidUpdate() {
const {
width,
height,
} = this.props;
if(width !== 0 && height !== 0) {
this.renderChart();
}
}
render() {
const {
width,
height,
} = this.props;
return (
<svg width={width} height={height} />
)
}
renderChart() {
const {
overflow,
graph,
data,
height,
width,
padding,
showLegend,
legendPercentage,
} = this.props;
// Reset the svg element to a empty state.
this.svg.innerHTML = '';
// Allow bubbles overflowing its SVG container in visual aspect if props(overflow) is true.
if(overflow)
this.svg.style.overflow = "visible";
const bubblesWidth = showLegend ? width * (1 - (legendPercentage / 100)) : width;
const legendWidth = width - bubblesWidth;
const color = d3.scaleOrdinal(d3.schemeCategory20c);
const pack = d3.pack()
.size([bubblesWidth * graph.zoom, bubblesWidth * graph.zoom])
.padding(padding);
// Process the data to have a hierarchy structure;
const root = d3.hierarchy({children: data})
.sum(function(d) { return d.value; })
.sort(function(a, b) { return b.value - a.value; })
.each((d) => {
if(d.data.label) {
d.label = d.data.label;
d.id = d.data.label.toLowerCase().replace(/ |\//g, "-");
}
});
// Pass the data to the pack layout to calculate the distribution.
const nodes = pack(root).leaves();
// Call to the function that draw the bubbles.
this.renderBubbles(bubblesWidth, nodes, color);
// Call to the function that draw the legend.
if (showLegend) {
this.renderLegend(legendWidth, height, bubblesWidth, nodes, color);
}
}
renderBubbles(width, nodes, color) {
const {
graph,
data,
bubbleClickFun,
valueFont,
labelFont
} = this.props;
const bubbleChart = d3.select(this.svg).append("g")
.attr("class", "bubble-chart")
.attr("transform", function(d) { return "translate(" + (width * graph.offsetX) + "," + (width * graph.offsetY) + ")"; });;
const node = bubbleChart.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", function(d) {
bubbleClickFun(d.label);
});
node.append("circle")
.attr("id", function(d) { return d.id; })
.attr("r", function(d) { return d.r - (d.r * .04); })
.style("fill", function(d) { return d.data.color ? d.data.color : color(nodes.indexOf(d)); })
.style("z-index", 5)
.on('mouseover', function(d) {
d3.select(this).attr("r", d.r * 2.04);
})
.on('mouseout', function(d) {
const r = d.r - (d.r * 0.04);
d3.select(this).attr("r", r);
});
node.append("clipPath")
.attr("id", function(d) { return "clip-" + d.id; })
.append("use")
.attr("xlink:href", function(d) { return "#" + d.id; });
// node.append("text")
// .attr("class", "value-text")
// .style("font-size", `${valueFont.size}px`)
// .attr("clip-path", function(d) { return "url(#clip-" + d.id + ")"; })
// .style("font-weight", (d) => {
// return valueFont.weight ? valueFont.weight : 600;
// })
// .style("font-family", valueFont.family)
// .style("fill", () => {
// return valueFont.color ? valueFont.color : '#000';
// })
// .style("stroke", () => {
// return valueFont.lineColor ? valueFont.lineColor : '#000';
// })
// .style("stroke-width", () => {
// return valueFont.lineWeight ? valueFont.lineWeight : 0;
// })
// .text(function(d) { return d.value; });
node.append("text")
.attr("class", "label-text")
.style("font-size", `${labelFont.size}px`)
.attr("clip-path", function(d) { return "url(#clip-" + d.id + ")"; })
.style("font-weight", (d) => {
return labelFont.weight ? labelFont.weight : 600;
})
.style("font-family", labelFont.family)
.style("fill", () => {
return labelFont.color ? labelFont.color : '#000';
})
.style("stroke", () => {
return labelFont.lineColor ? labelFont.lineColor : '#000';
})
.style("stroke-width", () => {
return labelFont.lineWeight ? labelFont.lineWeight : 0;
})
.text(function(d) {
return d.label;
});
// Center the texts inside the circles.
d3.selectAll(".label-text").attr("x", function(d) {
const self = d3.select(this);
const width = self.node().getBBox().width;
return -(width/2);
})
.style("opacity", function(d) {
const self = d3.select(this);
const width = self.node().getBBox().width;
d.hideLabel = width*1.05 > (d.r*2);
return d.hideLabel ? 0 : 1;
})
.attr("y", function(d) {
return labelFont.size/2
})
// Center the texts inside the circles.
d3.selectAll(".label-text").attr("x", function(d) {
const self = d3.select(this);
const width = self.node().getBBox().width;
return -(width/2);
})
.attr("y", function(d) {
if (!d.hideLabel) {
return valueFont.size / 3;
} else {
return -valueFont.size * 0.5;
}
});
node.append("title")
.text(function(d) { return d.label; });
}
renderLegend(width, height, offset, nodes, color) {
const {
data,
legendClickFun,
legendFont,
} = this.props;
const bubble = d3.select('.bubble-chart');
const bubbleHeight = bubble.node().getBBox().height;
const legend = d3.select(this.svg).append("g")
.attr("transform", function() { return `translate(${offset},${(bubbleHeight)* 0.05})`; })
.attr("class", "legend");
let textOffset = 0;
const texts = legend.selectAll(".legend-text")
.data(nodes)
.enter()
.append("g")
.attr("transform", (d, i) => {
const offset = textOffset;
textOffset+= legendFont.size + 10;
return `translate(0,${offset})`;
})
.on('mouseover', function(d) {
d3.select('#' + d.id).attr("r", d.r * 1.04);
})
.on('mouseout', function(d) {
const r = d.r - (d.r * 0.04);
d3.select('#' + d.id).attr("r", r);
})
.on("click", function(d) {
legendClickFun(d.label);
});;
texts.append("rect")
.attr("width", 30)
.attr("height", legendFont.size)
.attr("x", 0)
.attr("y", -legendFont.size)
.style("fill", "transparent");
texts.append("rect")
.attr("width", legendFont.size)
.attr("height", legendFont.size)
.attr("x", 0)
.attr("y", -legendFont.size)
.style("fill", function(d) { return d.data.color ? d.data.color : color(nodes.indexOf(d)); });
texts.append("text")
.style("font-size", `${legendFont.size}px`)
.style("font-weight", (d) => {
return legendFont.weight ? legendFont.weight : 600;
})
.style("font-family", legendFont.family)
.style("fill", () => {
return legendFont.color ? legendFont.color : '#000';
})
.style("stroke", () => {
return legendFont.lineColor ? legendFont.lineColor : '#000';
})
.style("stroke-width", () => {
return legendFont.lineWeight ? legendFont.lineWeight : 0;
})
.attr("x", (d) => { return legendFont.size + 10 })
.attr("y", 0)
.text((d) => { return d.label });
}
}
BubbleChart.propTypes = {
overflow: PropTypes.bool,
graph: PropTypes.shape({
zoom: PropTypes.number,
offsetX: PropTypes.number,
offsetY: PropTypes.number,
}),
width: PropTypes.number,
height: PropTypes.number,
padding: PropTypes.number,
showLegend: PropTypes.bool,
legendPercentage: PropTypes.number,
legendFont: PropTypes.shape({
family: PropTypes.string,
size: PropTypes.number,
color: PropTypes.string,
weight: PropTypes.string,
}),
valueFont: PropTypes.shape({
family: PropTypes.string,
size: PropTypes.number,
color: PropTypes.string,
weight: PropTypes.string,
}),
labelFont: PropTypes.shape({
family: PropTypes.string,
size: PropTypes.number,
color: PropTypes.string,
weight: PropTypes.string,
}),
}
BubbleChart.defaultProps = {
overflow: false,
graph: {
zoom: 1.1,
offsetX: -0.05,
offsetY: -0.01,
},
width: 1000,
height: 800,
padding: 0,
showLegend: true,
legendPercentage: 20,
legendFont: {
family: 'Arial',
size: 12,
color: '#000',
weight: 'bold',
},
valueFont: {
family: 'Arial',
size: 16,
color: '#fff',
weight: 'bold',
},
labelFont: {
family: 'Arial',
size: 11,
color: '#fff',
weight: 'normal',
},
bubbleClickFun: (label) => {console.log(`Bubble ${label} is clicked ...`)
},
legendClickFun: (label) => {console.log(`Legend ${label} is clicked ...`)}
}
when I click on the bubbleClickFun,I want a popover to be displayed with "label-text" attribute.I am using d3 inside a React component.Also a modal or tooltip works too.Any popover or panel kind of thing,that pops up as soon as the bubble is clicked works fine.
Also,I would be able to change the size of popover.This is really important
I am a newbie in d3.js. I have tried to create a static architecture with 5 nodes and link them with each other according to preferences, the nodes should be organized like so:
At the beginning I set the position of the nodes and then create the links. Though, when the nodes get linked, the architecture changes and the result is the one displayed below:
Here is my code:
var width = 640,
height = 400;
var nodes = [
{ x: 60, y: 0, id: 0},
{ x: 150, y: height/4, id: 1},
{ x: 220, y: height/4, id: 2},
{ x: 340, y: height/4, id: 3},
{ x: 420, y: height/2, id: 4},
{ x: 480, y: height/2, id: 5}
];
var links = [
{ source: 1, target: 5 },
{ source: 0, target: 5 },
{ source: 2, target: 1 },
{ source: 3, target: 2 },
{ source: 4, target: 5 }
];
var graph = d3.select('#graph');
var svg = graph.append('svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.links(links);
force.linkDistance(width/2);
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 1e-6);
var node = svg.selectAll('.node')
.data(nodes)
.enter().append("circle")
.attr("cx", d=> d.x)
.attr("cy", d=> d.y)
.attr('class', 'node')
.on("mouseover", function(d){
d3.select(this)
.transition()
.duration(500)
.style("cursor", "pointer")
div
.transition()
.duration(300)
.style("opacity", "1")
.style("display", "block")
console.log("label", d.label);
div
.html("IP: " + d.label + " x: " + d.x + " y: " + d.y)
.style("left", (d3.event.pageX ) + "px")
.style("top", (d3.event.pageY) + "px");
})
.on("mouseout", mouseout);
function mouseout() {
div.transition()
.duration(300)
.style("opacity", "0")
}
console.log("wait...");
force.on('end', function() {
node.attr('r', width/25)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });
link.attr('x1', function(d) { console.log("LINE x1-> ", d.source.x); return d.source.x; })
.attr('y1', function(d) { console.log("LINE y1-> ", d.source.y); return d.source.y; })
.attr('x2', function(d) { console.log("LINE x2-> ", d.source.x); return d.target.x; })
.attr('y2', function(d) { console.log("LINE y2-> ", d.source.y); return d.target.y; })
.attr("stroke-width", 2)
.attr("stroke","black");
});
force.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="graph"></div>
Could you please help me?
Thank you in advance.
A force layout offers some advantages that derive from its nature as a self organizing layout:
It places nodes and links automatically avoiding manual positioning of potentially thousands of elements
It organizes nodes and links based on assigned forces to an ideal spacing and layout
You have nodes to which you have already assigned positions, the two advantages listed above do not apply. You've already manually done the first item, and the second item will disturb and overwrite the positions you manually set.
We could fix the node positions, but if we do this with all nodes, it defeats the purpose of the force layout: to position nodes by simulating forces.
Instead, if you have the position of all nodes, we can skip the force and just append everything based on the data. The snippet below places the links first (so they are behind the nodes) using the index contained in d.source/d.target to access the specific node in the nodes array and get the appropriate x or y coordinate. The nodes are positioned normally.
It appears you have adjusted the code to use circles in your question though the screenshot uses images (as you also used in a previous question), I'll just use circles here. Based on the coordinates you've given some lines overlap. I modified the first node so that the y value wasn't 0 (which would have pushed half the circle off the svg)
var width = 640,
height = 400;
var nodes = [
{ x: 60, y: height/8, id: 0},
{ x: 150, y: height/4, id: 1},
{ x: 220, y: height/4, id: 2},
{ x: 340, y: height/4, id: 3},
{ x: 420, y: height/2, id: 4},
{ x: 480, y: height/2, id: 5}
];
var links = [
{ source: 1, target: 5 },
{ source: 0, target: 5 },
{ source: 2, target: 1 },
{ source: 3, target: 2 },
{ source: 4, target: 5 }
];
var graph = d3.select('#graph');
var svg = graph.append('svg')
.attr('width', width)
.attr('height', height);
// append links:
svg.selectAll()
.data(links)
.enter()
.append("line")
.attr("x1", function(d) { return nodes[d.source].x; })
.attr("y1", function(d) { return nodes[d.source].y; })
.attr("x2", function(d) { return nodes[d.target].x; })
.attr("y2", function(d) { return nodes[d.target].y; })
.attr("stroke-width", 2)
.attr("stroke","black");
// append nodes:
svg.selectAll()
.data(nodes)
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 8);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="graph"></div>
I have circles that is dynamically generated on my view with different sizes (d3.pack()) . Now I added a click event on it so that as it gets clicked it expands. Now, I want to elegantly reset when another circle is clicked? I did my reset similar to this answer D3 - Resetting an SVG object animation
But here's a snippet of my code
var objects= [
{ id: '1477', amounts: 7, color: '#ffd800' },
{ id: '1490', amounts: 10, color: '#b65959' },
{ id: '1300', amounts: 90, color: '#ff006e' },
{ id: '4000', amounts: 50, color: '#ffd800' },
{ id: '9000', amounts: 20, color: '#b20101' },
{ id: '1212', amounts: 28, color: '#ff006e' },
{ id: '2323', amounts: 7, color: '#ffd800' }
]
var width = 700,
height = 800,
color = d3.scale.category20b(),
maxDiameter = 500;
var container = d3.select('.chart')
var svg = container.append('svg')
.attr('width', maxDiameter * 2)
.attr('height', maxDiameter)
.attr('class', 'bubble')
var bubble = d3.layout.pack()
.sort(null)
.size([maxDiameter, maxDiameter])
.value(function (d) { return d.size; })
.padding(1.5)
var nodes = bubble
.nodes(processData(objects))
.filter(function (d) {
return !d.children;
})
var gCircle = svg.append('g')
var circle = gCircle.selectAll('circle')
.data(nodes)
.enter()
.append('circle')
.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function (d) {
return d.r;
})
.attr('fill', function (d) { return d.color;})
.attr('class', function (d) { return d.className; })
// onclick
circle.on('click', function (e, i) {
d3.select(this).transition()
.attr("x", function (d) {
console.log('d x ' + d.x);
return d.x;
})
.attr("y", function (d) {
console.log('d y ' + d.y);
return d.y;
})
.attr("r", function (d) {
console.log('d r ' + d.r);
return d3.select(this).attr('r') == d.r ? (d.r * 100) : d.r;
})
.duration(500);
});
function processData(data) {
var obj = data;
var newDataSet = [];
for (var l = 0; l < obj.length; l++) {
var objInData= obj[l];
newDataSet.push({ name: objInData.id, className: objInData.id, size: objInData.amounts, color: objInData.color });
}
return { children: newDataSet };
}
Before expanding the clicked circle, set all other circles to the initial size:
circle.transition()
.duration(500)
.attr('r', function (d) {
return d.r;
});
Here is the demo:
var objects= [
{ id: '1477', amounts: 7, color: '#ffd800' },
{ id: '1490', amounts: 10, color: '#b65959' },
{ id: '1300', amounts: 90, color: '#ff006e' },
{ id: '4000', amounts: 50, color: '#ffd800' },
{ id: '9000', amounts: 20, color: '#b20101' },
{ id: '1212', amounts: 28, color: '#ff006e' },
{ id: '2323', amounts: 7, color: '#ffd800' }
]
var width = 500,
height = 400,
color = d3.scale.category20b(),
maxDiameter = 500;
var container = d3.select('body')
var svg = container.append('svg')
.attr('width', maxDiameter * 2)
.attr('height', maxDiameter)
.attr('class', 'bubble')
var bubble = d3.layout.pack()
.sort(null)
.size([maxDiameter, maxDiameter])
.value(function (d) { return d.size; })
.padding(1.5)
var nodes = bubble
.nodes(processData(objects))
.filter(function (d) {
return !d.children;
})
var gCircle = svg.append('g')
var circle = gCircle.selectAll('circle')
.data(nodes)
.enter()
.append('circle')
.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function (d) {
return d.r;
})
.attr('fill', function (d) { return d.color;})
.attr('class', function (d) { return d.className; })
// onclick
circle.on('click', function (e, i) {
circle.transition().duration(500).attr('r', function (d) {
return d.r;
})
d3.select(this).transition()
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
})
.attr("r", function (d) {
return d3.select(this).attr('r') == d.r ? (d.r * 2) : d.r;
})
.duration(500);
});
function processData(data) {
var obj = data;
var newDataSet = [];
for (var l = 0; l < obj.length; l++) {
var objInData= obj[l];
newDataSet.push({ name: objInData.id, className: objInData.id, size: objInData.amounts, color: objInData.color });
}
return { children: newDataSet };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
PS: instead of expanding to r*100, in this demo the circles are expanding to just r*2.