santoku
santoku

Reputation: 3427

How to draw shape on top of other shape

I tried to plot rectangles/bars on top of another layer of shorter&fatter rectangles, but the results are unexpected - only one layer is shown:

var margin = {
    top: 20,
    right: 20,
    bottom: 30,
    left: 40
  },
  w = 600 - margin.left - margin.right,
  h = 300 - margin.top - margin.bottom;

var data = [{
  "Food": "Apples",
  "Deliciousness": 9,
  "new": 4
}, {
  "Food": "Green Beans",
  "Deliciousness": 5,
  "new": 4
}, {
  "Food": "Egg Salad Sandwich",
  "Deliciousness": 4,
  "new": 4
}, {
  "Food": "Cookies",
  "Deliciousness": 10,
  "new": 4
}, {
  "Food": "Liver",
  "Deliciousness": 2,
  "new": 4
}, ];



// format the data
data.forEach(function(d) {
  d.Deliciousness = +d.Deliciousness;
});


var svg = d3.select("body")
  .append("svg")
  .attr("width", w + margin.left + margin.right)
  .attr("height", h + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," +
    margin.top + ")");

var xScale = d3.scaleBand()
  .domain(d => d.Food)
  .range([0, w])
  .paddingInner(0.2);
xScale.domain(data.map(function(d) {
  return d.Food;
}));


var xAxis = d3.axisBottom()
  .scale(xScale)
  .ticks(5);

var yScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.Deliciousness)])
  .rangeRound([h, 0]);

var yAxis = d3.axisLeft()
  .scale(yScale)
  .ticks(5);

var chartgroup = svg.append("g");

chartgroup.selectAll('rect')
  .data(data)
  .enter()
  .append('rect')
  .attr('x', (d, i) => margin.left + i * w / data.length)
  .attr('y', d => yScale(d.Deliciousness))
  .attr('width', xScale.bandwidth())
  .attr('height', d => h - yScale(d.Deliciousness))
  .attr('fill', "blue");

chartgroup.selectAll('rect')
  .data(data)
  .enter()
  .append('rect')
  .attr('x', (d, i) => margin.left + i * w / data.length)
  .attr('y', d => yScale(d.Deliciousness))
  .attr('width', xScale.bandwidth() / 2)
  .attr('height', d => h - yScale(d.Deliciousness) / 2)
  .attr('fill', "yellow");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<html lang="en">

<head>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <meta charset="utf-8">
  <title>D3: Loading data from a CSV file</title>
</head>

<body>
  <!-- <p>click to see changes</p> -->
</body>

</html>

I added a group too but that doesn't solve the issue.

Am I missing something here?

chartgroup.selectAll('rect')
        .data(data)
        .enter()
        .append('rect')
        .attr('x',(d,i) => margin.left + i*w/data.length)
        .attr('y',d=>yScale(d.Deliciousness))
        .attr('width', xScale.bandwidth()/2)
        .attr('height',d =>h-yScale(d.Deliciousness)/2)
        .attr('fill',"yellow");

Any advice?

Upvotes: 1

Views: 246

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

When you do this for the second time...

chartgroup.selectAll('rect')

... you're selecting elements already painted in that SVG. Therefore, your second enter selection is empty. Let's prove it:

var margin = {
    top: 20,
    right: 20,
    bottom: 30,
    left: 40
  },
  w = 600 - margin.left - margin.right,
  h = 300 - margin.top - margin.bottom;

var data = [{
  "Food": "Apples",
  "Deliciousness": 9,
  "new": 4
}, {
  "Food": "Green Beans",
  "Deliciousness": 5,
  "new": 4
}, {
  "Food": "Egg Salad Sandwich",
  "Deliciousness": 4,
  "new": 4
}, {
  "Food": "Cookies",
  "Deliciousness": 10,
  "new": 4
}, {
  "Food": "Liver",
  "Deliciousness": 2,
  "new": 4
}, ];



// format the data
data.forEach(function(d) {
  d.Deliciousness = +d.Deliciousness;
});


var svg = d3.select("body")
  .append("svg")
  .attr("width", w + margin.left + margin.right)
  .attr("height", h + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," +
    margin.top + ")");

var xScale = d3.scaleBand()
  .domain(d => d.Food)
  .range([0, w])
  .paddingInner(0.2);
xScale.domain(data.map(function(d) {
  return d.Food;
}));


var xAxis = d3.axisBottom()
  .scale(xScale)
  .ticks(5);

var yScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.Deliciousness)])
  .rangeRound([h, 0]);

var yAxis = d3.axisLeft()
  .scale(yScale)
  .ticks(5);

var chartgroup = svg.append("g");

chartgroup.selectAll("rect")
  .data(data)
  .enter()
  .append('rect')
  .attr('x', (d, i) => margin.left + i * w / data.length)
  .attr('y', d => yScale(d.Deliciousness))
  .attr('width', xScale.bandwidth())
  .attr('height', d => h - yScale(d.Deliciousness))
  .attr('fill', "blue");

var secondSelection = chartgroup.selectAll("rect")
  .data(data)
  .enter()
  .append('rect')
  .attr('x', (d, i) => margin.left + i * w / data.length)
  .attr('y', d => yScale(d.Deliciousness))
  .attr('width', xScale.bandwidth() / 2)
  .attr('height', d => h - yScale(d.Deliciousness) / 2)
  .attr('fill', "yellow");

console.log("Enter selection size is: " + secondSelection.size())
<script src="https://d3js.org/d3.v4.min.js"></script>

Instead of that (provided that you don't plan to have an update selection), select something that doesn't exist, like null:

chartgroup.selectAll(null)

To better understand why (and sometimes why not) selecting null have a look here: Selecting null: what is the reason of using 'selectAll(null)' in D3.js?

Here is your code with that change:

var margin = {
    top: 20,
    right: 20,
    bottom: 30,
    left: 40
  },
  w = 600 - margin.left - margin.right,
  h = 300 - margin.top - margin.bottom;

var data = [{
  "Food": "Apples",
  "Deliciousness": 9,
  "new": 4
}, {
  "Food": "Green Beans",
  "Deliciousness": 5,
  "new": 4
}, {
  "Food": "Egg Salad Sandwich",
  "Deliciousness": 4,
  "new": 4
}, {
  "Food": "Cookies",
  "Deliciousness": 10,
  "new": 4
}, {
  "Food": "Liver",
  "Deliciousness": 2,
  "new": 4
}, ];

// format the data
data.forEach(function(d) {
  d.Deliciousness = +d.Deliciousness;
});

var svg = d3.select("body")
  .append("svg")
  .attr("width", w + margin.left + margin.right)
  .attr("height", h + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," +
    margin.top + ")");

var xScale = d3.scaleBand()
  .range([0, w])
  .paddingInner(0.2)
  .domain(data.map(function(d) {
  return d.Food;
}));

var xAxis = d3.axisBottom()
  .scale(xScale)
  .ticks(5);

var yScale = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.Deliciousness)])
  .rangeRound([h, 0]);

var yAxis = d3.axisLeft()
  .scale(yScale)
  .ticks(5);

var chartgroup = svg.append("g");

chartgroup.selectAll(null)
  .data(data)
  .enter()
  .append('rect')
  .attr('x', d => xScale(d.Food))
  .attr('y', d => yScale(d.Deliciousness))
  .attr('width', xScale.bandwidth())
  .attr('height', d => h - yScale(d.Deliciousness))
  .attr('fill', "blue");

chartgroup.selectAll(null)
  .data(data)
  .enter()
  .append('rect')
  .attr('x', d => xScale(d.Food))
  .attr('y', d => yScale(d.Deliciousness))
  .attr('width', xScale.bandwidth() / 2)
  .attr('height', d => h - yScale(d.Deliciousness) / 2)
  .attr('fill', "yellow");
<script src="https://d3js.org/d3.v4.min.js"></script>

Upvotes: 2

Related Questions