0

I am trying to graph this set of data (showing a snippet of a larger set) link to photo of chart shown below

month_started Member_casual count_of_rides

7 casual 435,289

8 casual 407,009

9 member 385,843

8 member 385,305

7 member 373,710

As you can see I have a month_started that maps to a member or casual attribute. I want to make a staked bar chart of the month_started as an independent variable (x) and the count_of_rides as a dependent (y) with the casual value stacked on top of the member per month.

I currently have a d3 bar chart that overlays both month_started = 7 on top of each other instead of stacked. I'm not sure how I can get d3 to recognize that it needs to read off of the member_casual variable to separate the two values.

I have additional code but here is the relevant sections I believe

Also, the .data(bike_trips) I believe should be .data(stackedData) but for some reason doesn't show up any bars, which makes me think my error could be with the stakedData variable.

Could anyone point me in the right direction please

bike_trip_chart = {
  const svg = d3.create("svg")
  .attr("viewBox", [0, 0, width, height]);

 svg.append("g")
      .attr("fill-opacity", .8)
    .selectAll("rect")
    .data(bike_trips)
    .join("rect")
      .attr("fill", (d) => colorScale(d.key))
      .attr("x", d => x(d.month_started))
      .attr("width", x.bandwidth())
      .attr("y", d => y1(d.count_of_rides))
      .attr("height", d => y1(0) - y1(d.count_of_rides));

  svg.append("path")
      .attr("fill", "none")
      .attr("stroke", "#274e13")
      .attr("stroke-miterlimit", 4)
      .attr("stroke-width", 4)
      .attr("d", line(chicago_weather));

  svg.append("g")
      .attr("fill", "none")
      .attr("pointer-events", "all")
    .selectAll("rect")
    .data(bike_trips)
    .join("rect")
      .attr("x", d => x(d.month_started))
      .attr("width", x.bandwidth())
      .attr("y", 0)
      .attr("height", height);

  svg.append("g")
      .call(xAxis);

  svg.append("g")
      .call(y1Axis);

  svg.append("g")
      .call(y2Axis);

  return svg.node();
}

stackedData = d3.stack()
    .keys(["member","casual"])
    (bike_trips)

colorScale = d3.scaleOrdinal()
  .domain(["member","casual"])
  .range(["#E4BA14","#45818e"]);
eleena
  • 1
  • 2

1 Answers1

1

Before you pass your data to d3.stack(), you want it to look like this:

[
  {month: 7, casual: 435289, member: 373710},
  {month: 8, casual: 407009, member: 385305},
  {month: 9, member: 385843}
]

Here's a full example:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <script src="https://d3js.org/d3.v7.js"></script>
</head>

<body>
  <div id="chart"></div>

  <script>
    // set up

    const margin = { top: 25, right: 25, bottom: 50, left: 50 };

    const width = 500 - margin.left - margin.right;
    const height = 500 - margin.top - margin.bottom;

    const svg = d3.select('#chart')
      .append('svg')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
      .append('g')
        .attr('transform', `translate(${margin.left},${margin.top})`);

    // data

    const data = [
      { month_started: 7, member_casual: "casual", count_of_rides: 435289 },
      { month_started: 8, member_casual: "casual", count_of_rides: 407009 },
      { month_started: 9, member_casual: "member", count_of_rides: 385843 },
      { month_started: 8, member_casual: "member", count_of_rides: 385305 },
      { month_started: 7, member_casual: "member", count_of_rides: 373710 },
    ];

    const keys = ["member", "casual"];
    const months = Array.from(new Set(data.map(d => d.month_started))).sort(d3.ascending);


    // get a map from the month_started to the member_casual to the count_of_rides
    const monthToTypeToCount = d3.rollup(
      data,
      // g is an array that contains a single element
      // get the count for this element
      g => g[0].count_of_rides,
      // group by month first
      d => d.month_started,
      // then group by member of casual
      d => d.member_casual
    );

    // put the data in the format mentioned above
    const countsByMonth = Array.from(monthToTypeToCount, ([month, counts]) => {
      // counts is a map from member_casual to count_of_rides
      counts.set("month", month);
      counts.set("total", d3.sum(counts.values()));
      // turn the map into an object
      return Object.fromEntries(counts);
    });

    const stackedData = d3.stack()
        .keys(keys)
        // return 0 if a month doesn't have a count for member/casual
        .value((d, key) => d[key] ?? 0)
        (countsByMonth);

    // scales

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

    const y = d3.scaleLinear()
        .domain([0, d3.max(countsByMonth, d => d.total)])
        .range([height, 0]);

    const color = d3.scaleOrdinal()
        .domain(keys)
        .range(["#E4BA14","#45818e"]);

    // axes

    const xAxis = d3.axisBottom(x);

    svg.append('g')
        .attr('transform', `translate(0,${height})`)
        .call(xAxis)

    const yAxis = d3.axisLeft(y);

    svg.append('g')
        .call(yAxis);

    // draw bars

    const groups = svg.append('g')
      .selectAll('g')
      .data(stackedData)
      .join('g')
        .attr('fill', d => color(d.key));

    groups.selectAll('rect')
      .data(d => d)
      .join('rect')
        .attr('x', d => x(d.data.month))
        .attr('y', d => y(d[1]))
        .attr('width', x.bandwidth())
        .attr('height', d => y(d[0]) - y(d[1]));

    // title

    svg.append('g')
        .attr('transform', `translate(${width / 2},${-10})`)
        .attr('font-family', 'sans-serif')
      .append('text')
        .attr('text-anchor', 'middle')
        .call(
          text => text.append('tspan')
            .attr('fill', color('member'))
            .text('member')
        )
        .call(
          text => text.append('tspan')
            .attr('fill', 'black')
            .text(' vs. ')
        )
        .call(
          text => text.append('tspan')
            .attr('fill', color('casual'))
            .text('casual')
        )
  </script>
</body>

</html>
Dan
  • 1,501
  • 9
  • 8
  • thanks I ended up just offsetting the y axis like this:.attr("y", d => y1(d.count_of_rides + (bike_trips_casual.find(item => item.month_started == d.month_started).count_of_rides))) .attr("height", d => y1(0) - y1(d.count_of_rides)); – eleena Jan 10 '22 at 01:35