How to center a List inside a D3 circle

Viewed 40

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.

PIC

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

1 Answers

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