Kwhitejr
Kwhitejr

Reputation: 2306

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

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();
  }

Upvotes: 51

Views: 1550

Answers (1)

SmokeyShakers
SmokeyShakers

Reputation: 3402

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); });

Upvotes: 1

Related Questions