Details of stacked to grouped bars in D3.js

Stacked-to-Grouped Bars

Talking about stacked histogram and clustered graph
In the example of D3.js official website, switch the two kinds of histogram together. See the figure below:

Stacked histogram

Cluster graph

Next, the implementation code of these two kinds of histogram is explained in detail

index.html-Source code

<!DOCTYPE html>
<meta charset="utf-8">
<style>

form {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  position: absolute;
  left: 10px;
  top: 10px;
}

label {
  display: block;
}

</style>
<form>
  <label><input type="radio" name="mode" value="grouped"> Grouped</label>
  <label><input type="radio" name="mode" value="stacked" checked> Stacked</label>
</form>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

var n = 4, // The number of series. Here n refers to the number of series, that is, the data of several series or groups to be displayed
    m = 58; // The nu m ber of values per series

// The xz array has m elements, representing the x-values shared by all series.
// The yz array has n elements, representing the y-values of each of the n series.
// Each yz[i] is an array of m non-negative numbers representing a y-value for xz[i].
// The y01z array has the same structure as yz, but with stacked [y₀, y₁] instead of y.

// The array returned by d3.range(m) is stored in xz, the content of which is from 0-57, representing the value displayed on the x-axis
var xz = d3.range(m),
    // yz stores 4 * 58 matrix, 4 represents 4 sequences, 58 represents 58 values of each sequence
    yz = d3.range(n).map(function() { return bumps(m); }),

    // 1. D3. Post (matrix) method is used to transpose matrix. After calling D3. Post (YZ), return
    // A matrix of 58 * 4;
    // 2. The d3.stack() method returns a default stack generator
    // 3. The d3.stack().keys() method sets the primary key for the stack generator
    // 4. Finally, d3.stack().keys(d3.range(n)) returns a stack generator function with a primary key of 0-3,
    // Temporarily marked as stackGen()
    // 5. So y01z = stackgen (D3. Post (yz)), which converts yz's transpose matrix into stack form
    // In conclusion, from 1, 2, 3, 4, 5, we can see that y01z stores the data format prepared for drawing the stack graph
    y01z = d3.stack().keys(d3.range(n))(d3.transpose(yz)),

    // Get the maximum value in yz matrix
    yMax = d3.max(yz, function(y) { return d3.max(y); }),
    // Get the maximum value in y01z matrix, where d[1] is taken later because in stack structure [y ₀, y ₁], Y1 > Y0
    y1Max = d3.max(y01z, function(y) { return d3.max(y, function(d) { return d[1]; }); });

// Get svg elements
var svg = d3.select("svg"),
    // Define outer margin
    margin = {top: 40, right: 10, bottom: 20, left: 10},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    // Define and position the g element
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// Define x-axis scale function
var x = d3.scaleBand()
    // Specify definition field
    .domain(xz)
    // Specify value field
    .rangeRound([0, width])
    // Set fill
    .padding(0.08);

// Define y-scale function
var y = d3.scaleLinear()
    // Specify definition field
    .domain([0, y1Max])
    // Specify value field
    .range([height, 0]);

// Define color function
var color = d3.scaleOrdinal()
     // Specify the definition field of the color function. You can see that different colors are calculated by the index of the series
    .domain(d3.range(n))
     // Specify the value range of the color function
    .range(d3.schemeCategory20c);

// Define the elements of the stack diagram
var series = g.selectAll(".series")
  // Bind stack data
  .data(y01z)
  .enter().append("g")
    // Define fill color according to series index
    .attr("fill", function(d, i) { return color(i); });

// Defining the elements of a histogram
var rect = series.selectAll("rect")
  // Binding data for histogram
  .data(function(d) { return d; })
  .enter().append("rect")
    // The abscissa of each rectangular column is calculated by the x() scale function defined above
    .attr("x", function(d, i) { return x(i); })
    .attr("y", height)
    // Set the width of the rectangular bar to the scale width of the x-scale as defined above
    .attr("width", x.bandwidth())
    // Set the initial height of the rectangular bar to 0
    .attr("height", 0);

// Add a transition animation to a rectangular bar
rect.transition()
     // Delay time of animation
    .delay(function(d, i) { return i * 10; })
    // Calculate the value of Y attribute at the end of the action through the scale function y
    .attr("y", function(d) { return y(d[1]); })
    // Calculate the value of the height attribute at the end of the action through the scale function y
    .attr("height", function(d) { return y(d[0]) - y(d[1]); });

// Draw x-axis
// d3.axisBottom(x) returns a bottom oriented coordinate generator using the x-scale defined above
// Call the d3.axisBottom() function for the defined g element to draw the x-axis
g.append("g")
    .attr("class", "axis axis--x")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x)
         // Set the scale size
        .tickSize(0)
         // Set the inside margin of the scale
        .tickPadding(6));

// Define an input element for which change event listening is bound to switch between stack graph and cluster graph
d3.selectAll("input")
    .on("change", changed);

//Define a timer to stop at the end of the first execution. setTimeout() similar to js
var timeout = d3.timeout(function() {
  d3.select("input[value=\"grouped\"]")
      // In D3, properties are used to set the properties of some special html elements
      // Therefore, the checked property is set by property
      .property("checked", true)
      // Bind a custom event change for the input element
      .dispatch("change");
}, 2000);

// Switch stack graph and cluster graph
function changed() {
  // Stop the current timer to prevent the execution of subsequent callback functions
  timeout.stop();

  if (this.value === "grouped") transitionGrouped();
  else transitionStacked();
}

// Switch to cluster
function transitionGrouped() {
  // Define the domain of y
  y.domain([0, yMax]);

  // Add transition animation to convert to cluster
  rect.transition()
      .duration(500)
      .delay(function(d, i) { return i * 10; })
      // Calculate the x position of each rectangular bar
      .attr("x", function(d, i) { return x(i) + x.bandwidth() / n * this.parentNode.__data__.key; })
      // Set the width of the rectangular bar and column. Since the cluster graph is a group of N series, the width of the rectangle to be calculated is divided by n
      .attr("width", x.bandwidth() / n)
    .transition()
      // Set the y position of the rectangular bar
      .attr("y", function(d) { return y(d[1] - d[0]); })
      // Set the height of the rectangular bar
      .attr("height", function(d) { return y(0) - y(d[1] - d[0]); });
}

// Switch to stack
function transitionStacked() {
  // Toggle definition field of y
  y.domain([0, y1Max]);

  // Add transition animation for conversion to stack diagram
  rect.transition()
      // Set the duration of the animation
      .duration(500)
      // Set delay time for animation
      .delay(function(d, i) { return i * 10; })
      // Set the y position of the rectangular bar
      .attr("y", function(d) { return y(d[1]); })
      // Set the height of the rectangular bar
      .attr("height", function(d) { return y(d[0]) - y(d[1]); })
    .transition()
      // Set the position of x that the rectangle bar needs to reach at the end of the animation
      .attr("x", function(d, i) { return x(i); })
      // Set the width of the rectangle at the end of the animation
      .attr("width", x.bandwidth());
}

// Returns an array of m psuedorandom, smoothly-varying non-negative numbers.
// Inspired by Lee Byron's test data generator.
// http://leebyron.com/streamgraph/
// The bumps(m) method is used to return an array of M pseudo-random numbers, and the data in the array is a smooth non negative number
function bumps(m) {
  var values = [], i, j, w, x, y, z;

  // Initialize with uniform random values in [0.1, 0.2).
  for (i = 0; i < m; ++i) {
    values[i] = 0.1 + 0.1 * Math.random();
  }

  // Add five random bumps.
  for (j = 0; j < 5; ++j) {
    x = 1 / (0.1 + Math.random());
    y = 2 * Math.random() - 0.5;
    z = 10 / (0.1 + Math.random());
    for (i = 0; i < m; i++) {
      w = (i / m - y) * z;
      values[i] += x * Math.exp(-w * w);
    }
  }

  // Ensure all values are positive.
  for (i = 0; i < m; ++i) {
    values[i] = Math.max(0, values[i]);
  }

  return values;
}

</script>

At this point, the stack graph and cluster graph as well as their line breaking interpretation are completed. What is more difficult here is the conversion of data matrix required by the stack graph and the calculation of x,y position and width of rectangular bar column when the stack graph and cluster graph switch.

Today, it's cold in the vulva, and it's drizzling. It's very cold when you go out.

Tags: Attribute

Posted on Wed, 27 May 2020 08:24:00 -0700 by I Am Chris