Artiga
Artiga

Reputation: 796

Exapandable Stacked bar charts using d3.js

I am trying to create stacked bar charts such that when you click on them it will give you more detailed information regarding that stack.

Consider each bar represents a subject and each section in the bar represents mastery of students learning that subject in six different levels: say level 1 to level 6; level 1- very poor performance of a student and level 6 indicates the student has mastered the subject

Lets say initially stacked bars look like this:

enter image description here

If you select a subject, lets say solid geometry.

Then I click on one of the bars:

enter image description here

As shown above one can see that there are 4 students who are struggling in this subject while 4 students need practice of this subject.. and so on...

Then that stacked bar will expand this way , giving me more information regarding what is aboutenter image description here

How can we do this in D3?

Any help appreciated.

Upvotes: 3

Views: 155

Answers (1)

Will
Will

Reputation: 453

I don't see any research into, or attempts at, learning or using D3. While D3 can initially be daunting, the documentation makes it fairly approachable and you'd likely get more traction here if your question was slightly more specific.

That said, take a look at the example below. You'll see that D3 is a helper for mapping data to elements and orchestrating DOM manipulation. Also note that plain old CSS can accomplish much of the interactivity requirements. Finally, understand that you could accomplish this many different ways; this is just one quick hack.

var student_data = [{
  category: 'Struggling',
  students: [
    'Ben',
    'Elizabeth'
  ]
}, {
  category: 'Level One',
  students: [
    'Jessica',
    'Matt'
  ]
}, {
  category: 'Mastered',
  students: [
    'John',
    'Katie'
  ]
}];

// the quantile scale maps an input domain of
// values to an output range of values by 'spreading'
// the domain across the range.
var colors = d3.scale.quantile()
  .domain([0, student_data.length])
  .range(['red', 'grey', 'lightblue', 'darkblue']);

// the base element
var root = d3.select('.panel');

// selectAll ~= querySelectorAll
// says, find all elements with class 'category'
// if none exist, prepare a selection of
// student_data.length elements
var display = root.selectAll('.category')
  .data(student_data);

// take the prepared selection and
// for each
display.enter()
  // create an element
  .append('div')
  // assign the 'category' class to the element
  .attr('class', 'category')
  // set the background color
  .style('background-color', function(d, i) {
    // d is the current array element of student_data
    // i is the index of the current array element of student_data
    // so, pass i to the colors scale/map to get back a color
    return colors(i);
  })
  // add a mouse enter handler to set the text of a
  // yet to be added label.
  .on('mouseenter', function(d) {
    // this is the current element
    d3.select(this)
      // so, select the first element
      // with class label
      .select('.label')
      // and set it's text to the category property
      // of current array element of student_data
      .text(d.category);
  })
  // add a mouse leave handler to reset the text of the still
  // yet to be added label.
  .on('mouseleave', function(d) {
    d3.select(this).select('.label').text(d.students.length);
  })
  // for each created element
  .each(function(d) {
    // select the current element
    var selection = d3.select(this);
    // create the label referenced above
    var label = selection.append('div')
      // assign it a class of 'label'
      .attr('class', 'label')
      // and set it's text to count of
      // the students property
      .text(function(d) {
        // d = student_data[i]
        return d.students.length;
      });
    // also, prepare a new selection of
    // elements for each of the student names
    var names = selection
      .selectAll('.student')
      .data(d.students);
    // take the prepared selection and
    // for each
    names.enter()
      // create an element
      .append('div')
      // assign the 'student' class
      .attr('class', 'student')
      // and set the text to that
      // of the current element
      .text(function(dd) {
        // dd = student_data[i].students[j]
        return dd;
      });
  });
div {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  font-family: arial;
}
.panel {
  display: -webkit-box;
  display: -moz-box;
  display: -ms-flexbox;
  display: -webkit-flex;
  display: flex;
  -webkit-box-align: start;
  -webkit-align-items: flex-start;
  -ms-flex-align: start;
  align-items: flex-start;
  width: 100%;
  font-size: 0.8rem;
}
.category {
  display: inline-block;
  padding: 0.5rem;
  min-width: 2rem;
  min-height: 2rem;
  color: white;
  background-color: black;
  transition: all 250ms ease;
}
.category:hover {
  min-width: 10rem;
  width: auto;
  height: auto;
  transition: all 250ms ease;
}
.label {
  transition: all 250ms ease;
}
.student {
  display: none;
  padding: 0.25rem;
  width: 100%;
  color: black;
  background-color: white;
  cursor: pointer;
  transition: all 250ms ease;
}
.student:hover {
  background-color: lightblue;
}
.category:hover .label {
  padding-bottom: 0.5rem;
}
.category:hover .student {
  display: inline-block;
}
<div class='panel'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Upvotes: 2

Related Questions