Aman
Aman

Reputation: 417

D3.js: Cannot make Y axis scrollable and X axis fixed

I am trying to recreate this example where the y axis is scrollable and the x axis remains fixed. I am making a heatmap and I need to display 100+ countries on my vertical axis, so the only way to display that reasonably in a page is making it scrollable.

My code looks like so:

const margin = { top: 20, right: 20, bottom: 30, left: 150 };
const width = 960 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;


let chart = d3.select("#chart2")
    .append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .style("overflow-y", "scroll")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


// Load the data
d3.csv("https://raw.githubusercontent.com/thedivtagguy/daily-data/master/dd_cropYieldsD3/cropYieldsD3/data/land_use.csv").then(function(data) {
    // console.log(data);
    const years = Array.from(new Set(data.map(d => d.year)));
    const countries = Array.from(new Set(data.map(d => d.entity)));

    // Sort countries based on change in land use in descending order
    const sortedCountries = countries.sort((a, b) => {
        const aChange = data.filter(d => d.entity === a).map(d => d.change).reduce((a, b) => a + b);
        const bChange = data.filter(d => d.entity === b).map(d => d.change).reduce((a, b) => a + b);
        return aChange - bChange;
    });



    const x = d3.scaleBand()
        .range([0, width])
        .domain(years)
        .padding(0.1);
    
    const y = d3.scaleBand()
        .range([height*6, 0])
        .domain(sortedCountries)
        .padding(0.1);

    
    chart.append("g")
        .attr("transform", "translate(0," + height + ")")
        // Only 10 years
        .call(d3.axisBottom(x).tickValues(years.filter((d, i) => !(i % 10))))
        .selectAll("text")
        .style("color", "black")
        .style("position", "fixed")
        .attr("transform", "translate(-10,10)rotate(-45)")
        .style("text-anchor", "end");

        chart.append("g")
        .call(d3.axisLeft(y))
        .selectAll("text")
        .style("color", "black")
        .attr("transform", "translate(-10,0)")
        .style("text-anchor", "end");    

    const colorScale = d3.scaleSequential()
        .domain([0, d3.max(data, d => d.change)])
        .interpolator(d3.interpolateInferno);


  // add the squares
  chart.selectAll()
    .data(data, function(d) {return d.year +':'+ d.entity;})
    .join("rect")
      .attr("x", function(d) { return x(d.year) })
      .attr("y", function(d) { return y(d.entity) })
      .attr("rx", 4)
      .attr("ry", 4)
      .attr("width", x.bandwidth() )
      .attr("height", y.bandwidth() )
      .style("fill", function(d) { return colorScale(d.change)
                    console.log(d.change);
    } )
      .style("stroke-width", 4)
      .style("stroke", "none")
      .style("opacity", 0.8)

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg id="chart2" width="1000" height="800"></svg>

This gives me the following output:

enter image description here

As you can see, the y axis is being cut off while the x axis is not fixed or properly visible. Based on the example linked above, the only thing I can see controlling the scrolling is overflow-y, which I've added to my svg here:

let chart = d3.select("#chart2")
    .append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .style("overflow-y", "scroll")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

This does not work. What am I doing wrong and how can I replicate the fixed x axis and scrollable y axis example with my code?

Live deploy here

Upvotes: 1

Views: 1204

Answers (1)

Ruben Helsloot
Ruben Helsloot

Reputation: 13129

When you look closely at the example you posted, you'll see that they used two (!) SVGs. One for the chart and one for the axis. Starting with that already solves a big part of the problem.

The next thing is that SVGs are not scrollable. Again, looking at the example shows you that the overflow: scroll is actually on the div, not on the SVG.

const margin = {
  top: 20,
  right: 20,
  bottom: 30,
  left: 150
};
const width = 960 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;


let chart = d3.select("#chart2")
  .append("div")
  .classed("chart", true)
  .append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .style("overflow-y", "scroll")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// Make sure to create a separate SVG for the XAxis
let axis = d3.select("#chart2")
  .append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", 40)
  .append("g")
  .attr("transform", "translate(" + margin.left + ", 0)");


// Load the data
d3.csv("https://raw.githubusercontent.com/thedivtagguy/daily-data/master/dd_cropYieldsD3/cropYieldsD3/data/land_use.csv").then(function(data) {
  // console.log(data);
  const years = Array.from(new Set(data.map(d => d.year)));
  const countries = Array.from(new Set(data.map(d => d.entity)));

  // Sort countries based on change in land use in descending order
  const sortedCountries = countries.sort((a, b) => {
    const aChange = data.filter(d => d.entity === a).map(d => d.change).reduce((a, b) => a + b);
    const bChange = data.filter(d => d.entity === b).map(d => d.change).reduce((a, b) => a + b);
    return aChange - bChange;
  });

  const x = d3.scaleBand()
    .range([0, width])
    .domain(years)
    .padding(0.1);

  const y = d3.scaleBand()
    .range([height * 6, 0])
    .domain(sortedCountries)
    .padding(0.1);

  // Only 10 years 
  axis.call(d3
      .axisBottom(x)
      .tickValues(years.filter((d, i) => !(i % 10))))
    .selectAll("text")
    .style("color", "black")
    .style("position", "fixed")
    .attr("transform", "translate(-10,10)rotate(-45)")
    .style("text-anchor", "end");

  chart.append("g")
    .call(d3.axisLeft(y))
    .selectAll("text")
    .style("color", "black")
    .attr("transform", "translate(-10,0)")
    .style("text-anchor", "end");

  const colorScale = d3.scaleSequential()
    .domain([0, d3.max(data, d => d.change)])
    .interpolator(d3.interpolateInferno);


  // add the squares
  chart.selectAll()
    .data(data, function(d) {
      return d.year + ':' + d.entity;
    })
    .join("rect")
    .attr("x", function(d) {
      return x(d.year)
    })
    .attr("y", function(d) {
      return y(d.entity)
    })
    .attr("rx", 4)
    .attr("ry", 4)
    .attr("width", x.bandwidth())
    .attr("height", y.bandwidth())
    .style("fill", function(d) {
      return colorScale(d.change)
      console.log(d.change);
    })
    .style("stroke-width", 4)
    .style("stroke", "none")
    .style("opacity", 0.8)

});
#chart2 .chart {
  width: 960px;
  max-height: 470px;
  overflow-y: scroll;
  overflow-x: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div id="chart2"></div>

Upvotes: 1

Related Questions