Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I position subgroups of small multiples together on the page?

I have a dataset with a column called CategoryLevel1 which contains the names of groups. I applied the nest function to Categorylevel1 and generated a series of svgs based on the keys. I then created rectangles representing items in the entire dataset and repeated the rectangles in each svg. I applied a filter to each svg, so that only the dataset items with the key of that svg can be seen.

My real dataset is bigger than the toy dataset represented here. The result of the above code is a long webpage of svgs - very confusing. To make things more clear, I would like svgs to be grouped according to a column called CategoryLevel2. Here is the effect I am after:

enter image description here

Here's what I have so far:

var doc = `Manual	Name	CategoryLevel1	CategoryLevel2
DOG	"General Furry, Program and Subject Files"	Average Quantity and Planning	Edibles
TR	Senate Committee on animal Standards	Bowl and Plate Design	Edibles
TR	Published Canine	Bowl and Plate Design	Edibles
TR	Canine case files	Bowl and Plate Design	Edibles
DOG	Canine Files 	Avoiding Neck Strain	Edibles
DOG	Canine Files 	Drooling	Edibles
DOG	Canine Files 	Drooling	Edibles
DG	ADVERTISING	At home	At home
DG	PROMOTIONS	At home	At home
DG3	Publications	At home	At home
TR	Public and Information Services	At home	At home
TR	Petting Services	Getting special treats	At home
TR	Petting Services	Getting special treats	At home
TR	Petting Services	Getting special treats	At home
TR	Petting Services	Getting special treats	At home
TR	Petting Services	Getting special treats	At home
TR	Petting Services	Getting special treats	At home
DG	DEVELOPMENT	Optimal time of day - walking	Walks and outings
DG	INCOME AND REVENUE	Optimal time of day - walking	Walks and outings
TR	Fundraising	Optimal time of day - walking	Walks and outings
TR	Fundraising	Optimal time of day - walking	Walks and outings
DG	DEVELOPMENT	Optimal time of day - walking	Walks and outings
DG	INCOME AND REVENUE	Optimal time of day - walking	Walks and outings
TR	Wishbone	Protective Measures	Walks and outings
TR	Wishbone	Protective Measures	Walks and outings
DG	Wishbone	Observant of Limps Etc	Walks and outings
DOG	Wishbone	Observant of Limps Etc	Walks and outings
TR	Wishbone	Observant of Limps Etc	Walks and outings`;

const data = d3.tsvParse(doc, function(d) {
  return {
    Manual: d.Manual,
    Name: d.Name,
    CategoryLevel1: d.CategoryLevel1,
    CategoryLevel2: d.CategoryLevel2
  };
});


var nest = d3.nest()
  .key(function(d) {
    return d.CategoryLevel1;
  })
  .entries(data);

var div = d3.select("body").append("div")
  .attr("class", "tooltip")
  .style("opacity", 0)

var height = 100,
  width = 300;

var color = d3.scaleOrdinal(["#edf8fb", "#b3cde3", "#8c96c6", "#88419d"]);

/* var svg = d3.select("body").append("svg").attr("height", "100%").attr("width", "100%");

var g = d3.select("svg").attr("height", "100%").attr("width", "100%"); */



var svgs = d3.select("body")
  .selectAll("svg")
  .data(nest)
  .enter()
  .append('svg')
  .attr("width", width)
  .attr("height", height + 20);

svgs.append("text")
  .attr('class', 'label')
  .data(nest)
  .attr('x', width / 2)
  .attr('y', height)
  .text(function(d) {
    return d.key;
  })
  .attr('text-anchor', 'middle')

svgs.selectAll("rect")
  .data(data)
  .enter().append("rect")
  .attr("class", "bar")
  .filter(function(d, i) {
    const x = d3.select(this.parentNode).datum();
    return x.key == d.CategoryLevel1 ? 1 : 0;
  })
  .attr("height", function(d) {
    return 50;
  })
  .attr("width", "5")
  .attr("x", function(d, i) {
    return i * 10;
  })
  .attr("y", 0)

  .attr("fill", function(d) {
    return color(d.Manual)
  })

  .on("mouseover", function(d, i) {
    div.transition()
      .duration(200)
      .style("opacity", .9);
    div.html(`${d.Name}`)
      .style("left", (d3.event.pageX) + "px")
      .style("top", (d3.event.pageY - 50) + "px");
  })
  .on("mouseout", function(d) {
    div.transition()
      .duration(500)
      .style("opacity", 0);
  });
.page {
  width: 90%;
  margin: auto;
}

.menu {
  height: 100px;
  background-color: #B2D6FF;
  /* Medium blue */
}

.sidebar {
  height: 50px;
  width: 15%;
  background-color: #F09A9D;
  float: inline-start;
  display: block;
  margin: 0.1%;
  /* Red */
}

.title {
  width: 100%;
  background-color: none;
  display: inline-block;
  float: inline-start;
  /* Yellow */
}

div.tooltip {
  position: absolute;
  text-align: center;
  width: auto;
  height: auto;
  padding: 3px;
  font: 12px sans-serif;
  border: 0px;
  border-radius: 3px;
  pointer-events: none;
  background: lightgrey;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Mapping Dog Care Manuals</title>
  <script src="https://d3js.org/d3.v4.min.js"></script>
</head>


</html>

What I have tried:

I tried creating svgs representing CategoryLevel2, then appending an "innerSVG" and running a code that would generate CategoryLevel1 svgs. The problem is in the filter line - it doesn't access the correct parent of CategoryLevel1:

.filter(function(d, i) {
    const x = d3.select(this.parentNode).datum();
    return x.key == d.CategoryLevel1 ? 1 : 0;
  })

I also tried using the "transform, translate" function to separate the rectangles based on Categorylevel1, but rejected this because it would be tricky to move the text associated with CategoryLevel1.

I'm now working on trying to use one of the d3.hierarchy layouts. The problem is that once I apply d3.stratify to the dataset, the results cannot be used to generate a series of svgs. That is to say, nothing shows up in the DOM when the following code is applied: (FYI I also replaced treeData with root.descendants() etc -

var treeData = d3.stratify()
  .id(function(d) { return d.CategoryLevel1; })
  .parentId(function(d) { return d.CategoryLevel2; })
  (data);

  var svgs = d3.select("chart")
    .selectAll("svg")
    .data(treeData)
    .enter()
    .append('svg')
    .attr("width", width)
    .attr("height", height + 20);
like image 361
oymonk Avatar asked Mar 26 '19 00:03

oymonk


1 Answers

There is an important information missing here: you didn't told us how do you want to separate the CategoryLevel2 groups: In the same row? In the same column? Some other pattern?

Therefore, in my solution I'll use a container <div> with display: flex to separate the groups in 3 columns, one for each value of CategoryLevel2 (which are Edibles, At Home Walks and outings).

This can be done just using CategoryLevel2 as the first key in the nest function:

var nest = d3.nest()
  .key(function(d) {
    return d.CategoryLevel2;
  })
  .key(function(d) {
    return d.CategoryLevel1;
  })
  .entries(data);

This will create the following array:

[{key: "Edibles", values: Array},
 {key: "At home", values: Array},
 {key: "Walks and outings", values: Array}];

In each values value, you'll have your original nest, like:

{
    "key": "Edibles",
    "values": [{
        "key": "Average Quantity and Planning",
        "values": [
            //etc...
        ]
    }, {
        "key": "Bowl and Plate Design",
        "values": [
            //etc..
        ]
    }, {
        "key": "Avoiding Neck Strain",
        "values": [
            //etc...
        ]
    }, {
        "key": "Drooling",
        "values": [
            //etc
        ]
    }]
}

And then you append the divs using the nested data:

var divs = d3.select(".container")
  .selectAll(null)
  .data(nest)
  .enter()
  .append("div")
  .attr("class", "innerdiv");

Of course, don't forget to use the inner arrays for the real SVGs:

var svgs = divs.selectAll(null)
  .data(function(d) {
    return d.values;
  })
  .enter()
  //etc...

Here is the code with those changes:

var doc = `Manual	Name	CategoryLevel1	CategoryLevel2
DOG	"General Furry, Program and Subject Files"	Average Quantity and Planning	Edibles
TR	Senate Committee on animal Standards	Bowl and Plate Design	Edibles
TR	Published Canine	Bowl and Plate Design	Edibles
TR	Canine case files	Bowl and Plate Design	Edibles
DOG	Canine Files 	Avoiding Neck Strain	Edibles
DOG	Canine Files 	Drooling	Edibles
DOG	Canine Files 	Drooling	Edibles
DG	ADVERTISING	At home	At home
DG	PROMOTIONS	At home	At home
DG3	Publications	At home	At home
TR	Public and Information Services	At home	At home
TR	Petting Services	Getting special treats	At home
TR	Petting Services	Getting special treats	At home
TR	Petting Services	Getting special treats	At home
TR	Petting Services	Getting special treats	At home
TR	Petting Services	Getting special treats	At home
TR	Petting Services	Getting special treats	At home
DG	DEVELOPMENT	Optimal time of day - walking	Walks and outings
DG	INCOME AND REVENUE	Optimal time of day - walking	Walks and outings
TR	Fundraising	Optimal time of day - walking	Walks and outings
TR	Fundraising	Optimal time of day - walking	Walks and outings
DG	DEVELOPMENT	Optimal time of day - walking	Walks and outings
DG	INCOME AND REVENUE	Optimal time of day - walking	Walks and outings
TR	Wishbone	Protective Measures	Walks and outings
TR	Wishbone	Protective Measures	Walks and outings
DG	Wishbone	Observant of Limps Etc	Walks and outings
DOG	Wishbone	Observant of Limps Etc	Walks and outings
TR	Wishbone	Observant of Limps Etc	Walks and outings`;

const data = d3.tsvParse(doc, function(d) {
  return {
    Manual: d.Manual,
    Name: d.Name,
    CategoryLevel1: d.CategoryLevel1,
    CategoryLevel2: d.CategoryLevel2
  };
});


var nest = d3.nest()
  .key(function(d) {
    return d.CategoryLevel2;
  })
  .key(function(d) {
    return d.CategoryLevel1;
  })
  .entries(data);

var div = d3.select("body").append("div")
  .attr("class", "tooltip")
  .style("opacity", 0)

var height = 80,
  width = 150;

var color = d3.scaleOrdinal(["#edf8fb", "#b3cde3", "#8c96c6", "#88419d"]);

/* var svg = d3.select("body").append("svg").attr("height", "100%").attr("width", "100%");

var g = d3.select("svg").attr("height", "100%").attr("width", "100%"); */

var divs = d3.select(".container")
  .selectAll(null)
  .data(nest)
  .enter()
  .append("div")
  .attr("class", "innerdiv");
  
divs.append("p")
  .html(function(d){
  return d.key;
  });

var svgs = divs.selectAll(null)
  .data(function(d) {
    return d.values;
  })
  .enter()
  .append('svg')
  .attr("width", width)
  .attr("height", height + 20);

svgs.append("text")
  .attr('class', 'label')
  .attr('x', width / 2)
  .attr('y', height)
  .style("font-size", "10px")
  .text(function(d) {
    return d.key;
  })
  .attr('text-anchor', 'middle')

svgs.selectAll("rect")
  .data(data)
  .enter().append("rect")
  .attr("class", "bar")
  .filter(function(d, i) {
    const x = d3.select(this.parentNode).datum();
    return x.key == d.CategoryLevel1 ? 1 : 0;
  })
  .attr("height", function(d) {
    return 50;
  })
  .attr("width", "5")
  .attr("x", function(d, i) {
    return i * 10;
  })
  .attr("y", 0)

  .attr("fill", function(d) {
    return color(d.Manual)
  })

  .on("mouseover", function(d, i) {
    div.transition()
      .duration(200)
      .style("opacity", .9);
    div.html(`${d.Name}`)
      .style("left", (d3.event.pageX) + "px")
      .style("top", (d3.event.pageY - 50) + "px");
  })
  .on("mouseout", function(d) {
    div.transition()
      .duration(500)
      .style("opacity", 0);
  });
div.tooltip {
  position: absolute;
  text-align: center;
  width: auto;
  height: auto;
  padding: 3px;
  font: 12px sans-serif;
  border: 0px;
  border-radius: 3px;
  pointer-events: none;
  background: lightgrey;
}

.container {
  display: flex;
}

.innerdiv {
 text-align: center;
 font-size: 21px;
 font-family: "Arial";
 flex: 1 1 0;
}

.innerdiv + .innerdiv {
 padding-left: 16px;
  border-left: 2px solid lightgray;
  }
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Mapping Dog Care Manuals</title>
  <script src="https://d3js.org/d3.v4.min.js"></script>
</head>

<body>
  <div class="container"></div>
</body>

</html>
like image 172
Gerardo Furtado Avatar answered Oct 21 '22 10:10

Gerardo Furtado