How to center a List inside a D3 circle - javascript

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" />

Related

Problems displaying the label in the middle of the link using D3.js

I have been learning about how to make a directed multigraph using D3.js, currently I have most of what I need, I just need to show in the graph what is the weight of the relationship between one node and another.
Currently I can show only for one relationship and not for all.
Here is my code
const width: any = screen.width
const height: any = screen.height
const simulation = d3.forceSimulation(this.nodes)
.force('link', d3.forceLink(this.links).id((d: any) => d.id))
.force('charge', d3.forceManyBody().strength(-400))
.force("x", d3.forceX())
.force("y", d3.forceY());
const svg = d3.select('#divGraph').append('svg')
.attr("viewBox", [-width / 2, -(height - 500) / 2, width, height - 500])
.style("font", "12px sans-serif");
svg.append("svg:defs").selectAll("marker")
.data(['arrow'])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", -0.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("fill", 'black')
.attr("d", "M0,-5L10,0L0,5");
const link = svg.append("g")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.selectAll("path")
.data(this.links)
.join("path")
.attr("id", (d: any) => `${d.source.name}x${d.target.name}`)
.attr("stroke", "black")
.attr("marker-end", "url(#arrow)")
const linkLabel = svg
.data(this.links)
.append("text")
.append("textPath")
.attr("stroke", "black")
.attr("fill", "black")
.attr("startOffset", "50%")
.attr("text-anchor", "middle")
.attr("xlink:href", (d: any) => `#${d.source.name}x${d.target.name}`)
.text((d: any) => `${d.type}`)
const node = svg.append("g")
.attr("fill", "white")
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.selectAll("g")
.data(this.nodes)
.join("g")
const circles = node
.append('circle')
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.attr("r", 4)
.style('fill', "#0d6efd")
.style('cursor', 'pointer')
.call(
d3.drag<SVGCircleElement, any>()
.on('start', (e, d) => dragstarted(e, d))
.on('drag', (e, d) => dragged(e, d))
.on('end', (e, d) => dragended(e, d))
);
const labelsCircles = node.append('text')
.attr("x", 0)
.attr("y", "1em")
.text((d: any) => d.name)
.clone(true).lower()
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 3);
simulation.on("tick", () => {
link.attr("d", this.linkArc);
node.attr("transform", (d: any) => `translate(${d.x},${d.y})`);
});
const dragstarted = (e: any, d: any) => {
if (!e.active) {
simulation.alphaTarget(0.3).restart();
}
d.fx = d.x;
d.fy = d.y;
};
const dragged = (e: any, d: any) => {
d.fx = e.x;
d.fy = e.y;
};
const dragended = (e: any, d: any) => {
if (!e.active) {
simulation.alphaTarget(0);
}
d.fx = null;
d.fy = null;
};
}
linkArc(d: any) {
const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y);
return `
M${d.source.x},${d.source.y}
A${r},${r} 0 0,1 ${d.target.x},${d.target.y}
`;
}
here is an example of data that I will show
nodes = [
{ index: 0, name: 'El Molino', group: 0 },
{ index: 1, name: 'El Nogal', group: 1 },
{ index: 2, name: 'Doña Carmen', group: 2 },
{ index: 3, name: 'Superpan', group: 1 },
{ index: 4, name: 'Pasteles Sweet', group: 1 },
{ index: 5, name: 'Banana', group: 1 },
{ index: 6, name: 'Peach', group: 1 },
{ index: 7, name: 'Bean', group: 2 },
{ index: 8, name: 'Pea', group: 2 },
{ index: 9, name: 'Carrot', group: 2 },
];
links = [
{ source: this.nodes[0], target: this.nodes[1], type: 10 },
{ source: this.nodes[0], target: this.nodes[2], type: 3 },
{ source: this.nodes[1], target: this.nodes[3], type: 4 },
{ source: this.nodes[1], target: this.nodes[4], type: 2 },
{ source: this.nodes[1], target: this.nodes[5], type: 1 },
{ source: this.nodes[1], target: this.nodes[6], type: 4 },
{ source: this.nodes[2], target: this.nodes[7], type: 6 },
{ source: this.nodes[2], target: this.nodes[8], type: 9 },
{ source: this.nodes[2], target: this.nodes[9], type: 4 },
{ source: this.nodes[1], target: this.nodes[0], type: 1 },
];
Here is an image of the execution of the code
enter image description here
you can see the 10 in the image, that's what I want but I can't repeat it for the rest.
this is the code excerpt that causes the conflict, I try to make all the links have its text, but I only get the first one to display it
const linkLabel = svg
.data(this.links)
.append("text")
.append("textPath")
.attr("stroke", "black")
.attr("fill", "black")
.attr("startOffset", "50%")
.attr("text-anchor", "middle")
.attr("xlink:href", (d: any) => `#${d.source.name}x${d.target.name}`)
.text((d: any) => `${d.type}`)

adding tooltip to a bubble in reactjs

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

Update polygon when points change in d3

I'm currently fiddling with polygons in d3 and would like to update individual polygons whilst the user is dragging a point. Drawing them initially works fine, but I can't get the update to work. The fiddle below contains my awful attempt at getting it to work:
https://jsfiddle.net/z4g5817z/9/
Relevant code:
const areas = [{
uid: 'ajf9v0',
points: [{
x: 52,
y: 92
},
{
x: 50,
y: 151
},
{
x: 123,
y: 149
},
{
x: 125,
y: 91
}
],
foo: 'bar',
// ...
},
{
uid: 'ufnf12',
points: [{
x: 350,
y: 250
},
{
x: 450,
y: 250
},
{
x: 450,
y: 275
},
{
x: 350,
y: 275
}
],
foo: 'baz',
// ...
}
];
const svg = d3.select('#root');
svg.attr('width', 500)
.attr('height', 500);
const areasGroup = svg.append('g')
.attr('class', 'areas');
function drawAreas(areas) {
console.log('Called draw');
const self = this;
const aGroup = areasGroup.selectAll('g.area')
.data(areas, (d) => {
console.log('Areas', d.points.map((d) => [d.x, d.y].join('#')).join('#'));
return d.points.map((d) => [d.x, d.y].join('#')).join('#');
});
areasGroup.exit().remove();
const areaGroups = aGroup.enter()
.append('g')
.attr('class', 'area');
//const areaPolygon = area.append('g')
// .attr('class', 'polygon');
//const areaPoints = area.append('g')
// .attr('class', 'points');
const polygon = areaGroups.selectAll('polygon')
.data((d) => {
console.log('Polygon data points', [d.points]);
return [d.points];
}, (d) => {
console.log('Polygon key', d.map((d) => [d.x, d.y].join('#')).join('#'));
return d.map((d) => [d.x, d.y].join('#')).join('#');
});
polygon.enter()
.append('polygon')
.merge(polygon)
.attr('points', (d) => {
console.log('Polygon points', d);
return d.map((d) => [d.x, d.y].join(',')).join(' ');
})
.attr('stroke', '#007bff')
.attr('stroke-width', 1)
.attr('fill', '#007bff')
.attr('fill-opacity', 0.25)
.on('click', this.handlePolygonSelection)
polygon.exit().remove();
const circles = areaGroups.selectAll('circle')
.data((d) => d.points, (d) => d.x + '#' + d.y);
circles.enter()
.append('circle')
.attr('r', 4)
.attr('cx', (d) => d.x)
.attr('cy', (d) => d.y)
.attr('fill', '#007bff')
.on('click', (d, idx, j) => {
const parentArea = d3.select(j[idx].parentNode).datum().points;
const i = parentArea.findIndex((p) => p.x === d.x && p.y === d.y);
if (i === parentArea.length) {
parentArea.pop();
} else if (i === 0) {
parentArea.shift();
} else {
parentArea.splice(i, 1);
}
this.drawAreas(areas);
})
.call(d3.drag()
.on('start', function(d) {
d3.select(this).classed('active', true)
})
.on('drag', function(d) {
d3.select(this)
.attr('cx', d.x = d3.event.x)
.attr('cy', d.y = d3.event.y);
self.drawAreas(areas);
})
.on('end', function(d) {
d3.select(this).classed('active', false)
}));
circles.exit().remove();
}
this.drawAreas(areas);
Thank you to anybody who takes time to have a look, any and all help is appreciated.
So it looks like I found the issue: https://jsfiddle.net/z4g5817z/91/
Changing
const polygon = areaGroups.selectAll('polygon')
to
const polygon = areasGroup.selectAll('g.area').selectAll('polygon')
seems to have fixed it. I'm assuming this has to do with the areaGroups selection only handling enter events.
An alternative would be to keep it the way it is now and change
const areaGroups = aGroup.enter()
.append('g')
.attr('class', 'area');
to
const areaGroups = aGroup.enter()
.append('g')
.merge(aGroup)
.attr('class', 'area');
which will produce the same result, as the update event is now also handled appropriately.

How can I wrap text on the each arc in D3.js?

Below is my code. I made a circle with 5 arcs and now I want to add text to each arc such that: http://bl.ocks.org/nbremer/raw/b603c3e0f7a74794da87/
// declarations
const svgSize = {
width: 1000,
height: 800
};
// setup
let svg = d3
.select('body')
.append('svg')
.attr('width', svgSize.width)
.attr('height', svgSize.height)
.append('g')
.attr('transform', 'translate(' + svgSize.width / 2 + ',' + svgSize.height / 2 + ')');
// drawing
let arcGenerator = d3.arc()
.innerRadius(296)
.outerRadius(300);
let arcData = [
{ startAngle: 0, endAngle: 0.2 },
{ startAngle: 0.2, endAngle: 0.6 },
{ startAngle: 0.6, endAngle: 1.4 },
{ startAngle: 1.4, endAngle: 3 },
{ startAngle: 3, endAngle: 2 * Math.PI }
];
d3.select('g')
.selectAll('path')
.data(arcData)
.enter()
.append('path')
.attr('d', arcGenerator);
d3.select('g')
.selectAll('path')
.data(arcData)
.enter()
.append('path')
.attr("id", (d, i) => { return "uniqueId_" + i; })
.attr('d', arcGenerator);
let monthData = [{ month: 'Jan' }, { month: 'Feb' }, { month: 'Mar' }, { month: 'Apr' }, { month: 'May' },]
// append the month names to each slice
svg.selectAll(".monthText")
.data(monthData)
.enter().append("text")
.attr("class", "monthText")
.attr("x", 10) // move the text from the start angle of the arc
.attr("dy", -10) // move the text down
.append("textPath")
.attr("xlink:href", function (d, i) { return "#uniqueId_" + i; })
.text(function (d) { return d.month; });

D3 (v3) resetting a previously animated circle after clicking another one

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.

Categories