How to render rectangles in a D3 grouped bar chart using React Faux DOM?

Javascriptd3.jsReactjs

Javascript Problem Overview


I am achingly close to finishing a group bar chart in React using react-faux-dom package. Everything is set except for the actual rectangles. Been spinning my wheels for a few hours so I'm hoping someone can see what I'm missing. The example that I'm working from is here. My goal is to have a time scale x-axis with multi bar groups. The X and Y axes are currently rendering without issue.

Data structure currently looks like so:

[
  {key: "oauths", values: [
    { date: Tue Jul 26 2016 00:00:00 GMT-1000 (HST), key: "oauths", value: 3060},
    { date: Tue Jul 27 2016 00:00:00 GMT-1000 (HST), key: "oauths", value: 2060},
    { date: Tue Jul 28 2016 00:00:00 GMT-1000 (HST), key: "oauths", value: 3270},
  ...]},
  {key: "user_stats", values: [
    { date: Tue Jul 26 2016 00:00:00 GMT-1000 (HST), key: "user_stats", value: 2976},
    ...
  ]}
]

The React component's render method is below. It error on the final svg.append()...

render() {
    const data = [ {}, {} ]; // see above

    // Constants
    const margin = {top: 30, right: 20, bottom: 30, left: 50},
          width = 600 - margin.left - margin.right,
          height = 400 - margin.top - margin.bottom,
          n = this.props.dataQueryPeriod.length, // number of samples
          m = 2; // number of series

    // Parse the date / time
    const parseDate = d3.time.format("%Y-%m-%d").parse;

    // Set ranges
    const yScale = d3.scale.linear()
      .range([height, 0]);

    const x0 = d3.scale.ordinal()
      .domain(d3.range(n))
      .rangeBands([0, width], .2);

    const x1 = d3.scale.ordinal()
      .domain(d3.range(m))
      .rangeBands([0, x0.rangeBand()]);

    const xScale = d3.time.scale().range([0, width]);

    // Test colors
    var z = d3.scale.category10();

    // Define axes
    const xAxis = d3.svg.axis()
      .scale(xScale)
      .orient("bottom")
      .ticks(d3.time.days);

    const yAxis = d3.svg.axis()
      .scale(yScale)
      .orient("left")
      .tickFormat(d3.format("s"));

    // Convert structured data to nested data
    const nest = d3.nest()
        .key(function(d) { return d.key; })
        .sortKeys(d3.ascending);

    // Create node
    let node = ReactFauxDOM.createElement('svg');

    let svg = d3.select(node)
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom+40)
      .append("g")
        .attr("transform", `translate(${margin.left},${margin.top})`);

    data.forEach(function(d) {
      d.date = parseDate(d.date);
      d.value = +d.value;
    });

    const nestedData = nest.entries(data);
    console.log("nested data", nestedData);
    console.log("pre nest data", data);

    // Define axes domains
    xScale.domain(d3.extent(data, function(d) { return d.date; }));
    yScale.domain([0, d3.max(data, function(d) { return d.value; })]);

    svg.append("g")
      .attr("class", "y axis")
      .call(yAxis);

    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", `translate(0,${height})`)
      .call(xAxis)
      .selectAll("text")
          .style("text-anchor", "end")
          .attr("dx", "-.8em")
          .attr("dy", ".15em")
          .attr("transform", function(d) {
            return "rotate(-65)";
          });

    svg.append("g").selectAll("g")
        .data(nestedData)
      .enter().append("g")
        .style("fill", function(d, i) { return z(i); })
        .attr("transform", function(d, i) { return `translate(${x1(i)},0)`; })
      .selectAll("rect")
        .data(function(d) { return d.value; })
      .enter().append("rect")
        .attr("width", x1.rangeBand())
        .attr("height", yScale)
        .attr("x", function(d, i) { return x0(i); })
        .attr("y", function(d) { return height - yScale(d.value); });

    return node.toReact();
  }

Javascript Solutions


Solution 1 - Javascript

If your data is starting out like the example you provided, it's already nested and d3.nest(). Assuming that the data from your example is nestedData, then your problem is that you need to bind values not value. See below:

svg.append("g").selectAll("g")
    .data(nestedData)
    .enter()
    .append("g")
    .style("fill", function(d, i) { return z(i); })
    .attr("transform", function(d, i) { return `translate(${x1(i)},0)`; })
    .selectAll("rect")
    .data(function(d) { return d.values; }) // This needs to be values
    .enter().append("rect")
    .attr("width", x1.rangeBand())
    .attr("height", yScale)
    .attr("x", function(d, i) { return x0(i); })
    .attr("y", function(d) { return height - yScale(d.value); });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKwhitejrView Question on Stackoverflow
Solution 1 - JavascriptSmokeyShakersView Answer on Stackoverflow