What do I need to do to change the size and color of the object with D3.js?

244 Views Asked by At

Please see the code in Observablehq notebook: https://observablehq.com/@erayalan/radial-scatter-plot

I am trying to understand how I can change the size and color of each item within this piece:

data = [{ name : "foo", r: 40, theta: 1.4 }, 
        { name : "bar", r: 30, theta: 4.8 }, 
        { name : "baz", r: 60, theta: 3.05}] 
2

There are 2 best solutions below

2
On

Here is a snippet with sizes and colors:

data = [
  {name: "foo", r: 40, theta: 1.4, size: 10, color: 'red'},
  {name: "bar", r: 30, theta: 4.8, size: 12, color: 'green'},
  {name: "baz", r: 60, theta: 3.05, size: 14, color: 'blue'}
];

const width = 200;
const height = 200;
const svg = d3.select('svg');

const radius = 100;
const r = d3.scaleLinear()
      .domain([0, 1])
      .range([0, radius]);

svg
  .attr("viewBox", `${-width / 2} ${-height / 2} ${width} ${height}`)
  .attr('width', width)
  .attr('height', height)
  .style("font", "10px sans-serif");

const gr = svg.append('g')
  .attr('class', 'r axis')
  .selectAll('g')
  .data(r.ticks(5).slice(1))
  .enter()
  .append('g');

gr.append('circle')
  .attr("fill", "none")
  .attr("stroke", "#ebebeb")
  .attr('r', r);

const ga = svg.append('g')
  .attr('class', 'a axis')
  .selectAll('g')
  .data(d3.range(0, 360, 30)) // line density
  .enter()
  .append('g')
  .attr('transform', d => 'rotate(' + -d + ')');

 ga.append('line')
   .attr("fill", "none")
   .attr("stroke", "#aaa")
   .attr('x2', radius);
  
 svg.selectAll("point") // No longer "rect"
  .data(data)
  .enter()
  .append("circle")
  .attr("cx", d => d.r * Math.cos(d.theta))
  .attr("cy", d => d.r * Math.sin(d.theta))
  .attr("r", d => d.size)
  .style('stroke', d => d.color)
  .style('fill', 'white');

  svg.selectAll("text") // Note "text", not "circle" or "rect"
  .data(data)
  .enter()
  .append("text")
  .text(d => d.name)
  .attr("x", d => d.r * Math.cos(d.theta))
  .attr("y", d => d.r * Math.sin(d.theta))
  .attr('alignment-baseline', 'middle')
  .attr('text-anchor', 'middle')
  .attr("font-family", "sans-serif")
  .attr("font-size", "11px")
  .attr("fill", d => d.color);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg/>

1
On

To change the size of the circles, you can use the "r" property, similar to how you are doing with the "cx" and "cy". I assume you want to map "theta" to size. First you need to create a scale:

  const radiusScale = d3.scaleLinear().domain(d3.extent(data, d=> d.theta)).range([10, 20])

The function extent in d3.extent(data, d=> d.theta) returns the maximum and minimum values of your data in the theta property (in this case, [1.4, 4.8]). That means this scale has a domain of [1.4, 4.8] and a range of [10, 20], so if you call radiusScale(1.4) it will output 10, and radiusScale(4.8) will output 20. With this, you mapped the theta value to an adequate radius, and can use it on the "r" property of the SVG circle:

  .attr("cx", d => d.r * Math.cos(d.theta))
  .attr("cy", d => d.r * Math.sin(d.theta))
  .attr("r", d => radiusScale(d.theta))

A similar logic applies to color, which is defined in the "fill" property. You want to map the "foo", "bar", and "baz" to different colors. You can use the scaleOrdinal and a color array for this:

   const colorScale = d3.scaleOrdinal().range(d3.schemeCategory10)

The range of this scale is d3.schemeCategory10, which is simply an array that contains some common colors (you can use any array of colors). You don't need a domain for this scale, because d3 can automatically assign a different color for each unique value the scale receives.

Then you can complement the mapping, assigning the color to the fill property.

  .attr("cx", d => d.r * Math.cos(d.theta))
  .attr("cy", d => d.r * Math.sin(d.theta))
  .attr("r", d => radiusScale(d.theta))
  .attr("fill", d => colorScale(d.name))

Here is a fork of your notebook with the changes above:

Side notes:

In my answer, I mapped the value of theta directly to the radius. It is not recommended to use the radius of a circle to encode a value, the best practice is to use its area. I'll not go into depth here for the sake of simplicity, but you can check better ways of mapping size here.

The learning curve of D3 can be little steep. You saw several concepts in this answer to change the color and size (scales, domains, ranges, extents, and more!). You might want to check the official d3 introduction to see these concepts in a more sequential manner.