Davtho1983
Davtho1983

Reputation: 3954

d3 Change colour of tick on axis with function

I have an x axis in d3 with labels and circles for each tick. I want to change the colour of each dot with a function so that the colour comes from an array.

I have a function already that gets ticks from the array and positions them on the scale, but the same logic doesn't work for changing attributes of each circle.

I would like to be able to select each circle that is a child of the .tick class and change it's stroke attribute:

svg.selectAll(".tick", "circle")
    .each(d => {
      var dot = d3.select(d, "circle").attr("stroke", "red")
      console.log("DOT", dot)
      return dot
    })

I would probably change the each to a proper for loop to iterate over both the data and circles arrays by matching indexes.

How would I make the circle colours correspond to those in the objects in the array 'data'?

import React, {useState, useEffect} from 'react';
import * as d3 from 'd3';
import './App.css';

function App() {
  const [currentYear, setCurrentYear] = useState(2020);
  const [maxYear, setMaxYear] = useState(30);
  const [data, setData] = useState(
    [
      {x: 2020, colour: "purple", y1: 0.001, y2: 0.63},
      {x: 2027, colour: "red", y1: 0.003, y2: 0.84},
      {x: 2031, colour: "yellow", y1: 0.024, y2: 0.56},
      {x: 2035, colour: "green", y1: 0.054, y2: 0.22},
      {x: 2040, colour: "blue", y1: 0.062, y2: 0.15},
      {x: 2050, colour: "orange", y1: 0.062, y2: 0.15}
    ]
  );

  const initialiseData = () => {
    const svg = d3.select( "svg" );
    const pxX = svg.attr( "width" );
    const pxY = svg.attr( "height" );

    const makeScale = ( accessor, range ) => {
      console.log("RANGE", accessor, range)
      return d3.scaleLinear()
        .domain( d3.extent( data, accessor ) )
        .range( range )
        .nice()
    }

    const scX = makeScale( d => d["x"], [0, pxX - 50]);

    const g = d3.axisBottom( scX ).tickValues(
      data.map(d => {
        return d.x 
      })
    )


    svg.append( "g" )
    .attr( "transform", "translate(" + 25 + "," + pxY/2 + ")")
    .call( g );

    svg.selectAll(".domain").attr( "visibility", "hidden" );

    svg.selectAll( ".tick" )
      .append("circle")
      .attr("cx", 0)
      .attr("cy", 0)
      .attr("r", 5)
      .attr("fill", "white")
      .attr("stroke", "grey")
      .attr("stroke-width", "4px")

    svg.selectAll(".tick line")
      .attr("visibility", "hidden")
      
    svg.selectAll( ".tick text")
      .attr("font-size", 20)
      .attr("dy", "1.5em")

    svg.selectAll(".tick", "circle")
    .each(d => {
      var dot = d3.select(d, "circle")
      console.log("DOT", dot)
      return 
    })

  }

  

  useEffect(() => {
    if (data) {
      initialiseData();
    }

  }, [data])

  return (
    <div className="App">
      <svg id="demo1" width="1200" height="400" style={{background: "lightgrey"}}/>
    </div>
  );
}

export default App;

Upvotes: 0

Views: 889

Answers (1)

xy2_
xy2_

Reputation: 7162

Your svg.selectAll( ".tick" ).append("circle") creates the circles. Using selectAll is a little like doing a for loop: it creates many elements, and each time, the data is bound to the created element.

You can provide a function to .attr() (and most other things in D3) that takes as an argument the bound data, usually written d. If you put in a selectAll, it'll be applied to each element.

See Learn D3: Joins for a more complete explanation. Putting it all together:

svg.selectAll( ".tick" )
  .data(data)
  .append("circle")
  .attr("fill", d => d.colour) // d is an element of the data: get colour from it

Upvotes: 1

Related Questions