d3.js legend overlapping chart area

2019-07-31 08:32发布

问题:

I'm pretty new to D3.js (I'm a R programmer).

I'm trying to create a scatter and put a legend in the right bottom. However, it's overlapping with chart area.

I've been trying to take some examples in internet with no lucky. When I try to move it to the right, the names and/or symbols are not visible.

I want to place the legend outside the chart frame, so it won't overlap.

Any help is appreciated.

Here's what I tried so far:

d3.csv('https://gist.githubusercontent.com/netj/8836201/raw/6f9306ad21398ea43cba4f7d537619d0e07d5ae3/iris.csv', function(data) {
  // CSV section
  var body = d3.select('body')
  var selectData = [{
      "text": "sepal.length"
    },
    {
      "text": "sepal.width"
    },
    {
      "text": "petal.length"
    },
    {
      "text": "petal.width"
    },
  ]

  // setup fill color
  var colors = ['#575757', '#5FB1B9', '#C94257'];

  var symbol = d3.svg.symbol()
    .type('circle')
    .size("160")

  var cValue = function(d) {
      return d.variety;
    },
    color = d3.scale.ordinal()
    .range(colors);

  // Select Y-axis Variable
  var span = body.append('span')
    .text('Select Y-Axis variable: ')
  var yInput = body.append('select')
    .attr('id', 'ySelect')
    .on('change', yChange)
    .selectAll('option')
    .data(selectData)
    .enter()
    .append('option')
    .attr('value', function(d) {
      return d.text
    })
    .text(function(d) {
      return d.text;
    })
  body.append('br')


  // Select X-axis Variable
  var span = body.append('span')
    .text('Select X-Axis variable: ')
  var yInput = body.append('select')
    .attr('id', 'xSelect')
    .on('change', xChange)
    .selectAll('option')
    .data(selectData)
    .enter()
    .append('option')
    .attr('value', function(d) {
      return d.text
    })
    .text(function(d) {
      return d.text;
    })
  body.append('br')


  // Variables
  var body = d3.select('body')
  var margin = {
    top: 50,
    right: 50,
    bottom: 50,
    left: 50
  }
  var h = 500 - margin.top - margin.bottom
  var w = 500 - margin.left - margin.right
  // var formatPercent = d3.format('.2%')
  // Scales
  // var colorScale = d3.scale.category20()
  var xScale = d3.scale.linear()
    .domain([
      d3.min([0, d3.min(data, function(d) {
        return d['sepal.length']
      })]),
      d3.max([0, d3.max(data, function(d) {
        return d['sepal.length']
      })])
    ])
    .range([0, w])
  var yScale = d3.scale.linear()
    .domain([
      d3.min([0, d3.min(data, function(d) {
        return d['sepal.length']
      })]),
      d3.max([0, d3.max(data, function(d) {
        return d['sepal.length']
      })])
    ])
    .range([h, 0])

  // SVG
  var svg = body.append('svg')
    .attr('height', h + margin.top + margin.bottom)
    .attr('width', w + margin.left + margin.right)
    .append('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

  // X-axis
  var xAxis = d3.svg.axis()
    .scale(xScale)
    // .tickFormat(formatPercent)
    .ticks(6)
    .outerTickSize(0)
    .tickSize(0)

    .orient('bottom')

  // Y-axis
  var yAxis = d3.svg.axis()
    .scale(yScale)
    // .tickFormat(formatPercent)
    .ticks(6)
    .tickSize(-w)
    .outerTickSize(0)
    .orient('left')

  // Circles
  var circles = svg.selectAll('circle')
    .data(data)
    .enter()
    .append('circle')
    .attr('cx', function(d) {
      return xScale(d['sepal.length'])
    })
    .attr('cy', function(d) {
      return yScale(d['sepal.length'])
    })
    .attr('r', '10')
    // .attr('stroke', 'black')
    .attr('stroke-width', 0.2)
    .attr("fill", function(d) {
      return color(cValue(d));
    })
    .attr('fill-opacity', 0.8)
    // .attr('fill', function(d, i) {
    //   return colorScale(i)
    // })
    .on('mouseover', function() {
      d3.select(this)
        .transition()
        .duration(300)
        .ease('elastic')
        .attr('r', 15)
        .attr('stroke-width', 1)
        .attr('fill-opacity', 1)

    })
    .on('mouseout', function() {
      d3.select(this)
        .transition()
        .duration(100)
        .attr('r', 10)
        .attr('stroke-width', 0.5)
    })
    .append('title') // Tooltip
    .text(function(d) {
      return d.variety +
        '\nSepal Length: ' + d['sepal.length'] +
        '\nSepal Width: ' + d['sepal.width'] +
        '\nPetal Length: ' + d['petal.length'] +
        '\nPetal Width: ' + d['petal.width']
    })

  // X-axis
  svg.append('g')
    .attr('class', 'axis')
    .attr('id', 'xAxis')
    .attr('transform', 'translate(0,' + h + ')')
    .call(xAxis)
    .append('text') // X-axis Label
    .attr('id', 'xAxisLabel')
    .attr('y', -25)
    .attr('x', w)
    .attr('dy', '.71em')
    .style('text-anchor', 'end')
    .text('Sepal Length')

  // labels distance from xaxis
  svg.selectAll(".axis text")
    .attr("dy", 15);

  // Y-axis
  svg.append('g')
    .attr('class', 'axis')
    .attr('id', 'yAxis')
    .call(yAxis)
    .append('text') // y-axis Label
    .attr('id', 'yAxisLabel')
    .attr('transform', 'rotate(-90)')
    .attr('x', 0)
    .attr('y', 5)
    .attr('dy', '.71em')
    .style('text-anchor', 'end')
    .text('Sepal Length')

  function yChange() {
    var value = this.value // get the new y value
    yScale // change the yScale
      .domain([
        d3.min([0, d3.min(data, function(d) {
          return d[value]
        })]),
        d3.max([0, d3.max(data, function(d) {
          return d[value]
        })])
      ])
    yAxis.scale(yScale) // change the yScale
    d3.select('#yAxis') // redraw the yAxis
      .transition().duration(500)
      .call(yAxis)
    d3.select('#yAxisLabel') // change the yAxisLabel
      .text(value)
    d3.selectAll('circle') // move the circles
      .transition().duration(500)
      .delay(function(d, i) {
        return i * 10
      })
      .attr('cy', function(d) {
        return yScale(d[value])
      })
  }

  function xChange() {
    var value = this.value // get the new x value
    xScale // change the xScale
      .domain([
        d3.min([0, d3.min(data, function(d) {
          return d[value]
        })]),
        d3.max([0, d3.max(data, function(d) {
          return d[value]
        })])
      ])
    xAxis.scale(xScale) // change the xScale
    d3.select('#xAxis') // redraw the xAxis
      .transition().duration(500)
      .call(xAxis)
    d3.select('#xAxisLabel') // change the xAxisLabel
      .transition().duration(500)
      .text(value)
    d3.selectAll('circle') // move the circles
      .transition().duration(500)
      .delay(function(d, i) {
        return i * 10
      })
      .attr('cx', function(d) {
        return xScale(d[value])
      })
  }

  // create legend
  var legend = svg.selectAll(".legend")
    .data(color.domain())
    .enter().append("g")
    .attr("class", "legend")
    .attr("transform", function(d, i) {
      return "translate(0," + i * 25 + ")";
    });

  // draw legend colored rectangles
  legend.append("path")
    .attr('d', symbol)
    .attr("transform", "translate(434, 313)") //much easier approach to position the symbols
    // .attr("x", w + 34)
    // .attr("y", h - 97)
    // .attr("width", 18)
    // .attr("height", 18)
    .style("fill", color);

  // draw legend text
  legend.append("text")
    .attr("transform", "translate(422, 311)")
    // .attr("x", w + 24)
    // .attr("y", h - 89)
    .attr("dy", ".35em")
    .style("text-anchor", "end")
    .text(function(d) {
      return d;
    })

})
body {
  font-size: 16px;
}


/*
      circle {
        fill: steelblue;
      } */

circle:hover {
  fill: orange;
}

.axis text {
  font-size: 13px;
  /* font-weight: bold; */
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
  stroke-width: 0.02px;
}


/* .circle {
        fill: orange;
      } */

label {
  position: absolute;
  top: 10px;
  right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>

<body></body>

回答1:

Why not just render your legend as HTML and use CSS to help with layout?

d3.csv('https://gist.githubusercontent.com/netj/8836201/raw/6f9306ad21398ea43cba4f7d537619d0e07d5ae3/iris.csv', function(data) {
  // CSV section
  var body = d3.select('body')
  var selectData = [{
      "text": "sepal.length"
    },
    {
      "text": "sepal.width"
    },
    {
      "text": "petal.length"
    },
    {
      "text": "petal.width"
    },
  ]

  // setup fill color
  var colors = ['#575757', '#5FB1B9', '#C94257'];

  var symbol = d3.svg.symbol()
    .type('circle')
    .size("160")

  var cValue = function(d) {
      return d.variety;
    },
    color = d3.scale.ordinal()
    .range(colors);

  var controls = body.append('div').attr('class', 'controls')


  // Select Y-axis Variable
  var yControls = controls.append('div')
  var span = yControls.append('span')
    .text('Select Y-Axis variable: ')
  var yInput = yControls.append('select')
    .attr('id', 'ySelect')
    .on('change', yChange)
    .selectAll('option')
    .data(selectData)
    .enter()
    .append('option')
    .attr('value', function(d) {
      return d.text
    })
    .text(function(d) {
      return d.text;
    })



  // Select X-axis Variable
  var xControls = controls.append('div')
  var span = xControls.append('span')
    .text('Select X-Axis variable: ')
  var yInput = xControls.append('select')
    .attr('id', 'xSelect')
    .on('change', xChange)
    .selectAll('option')
    .data(selectData)
    .enter()
    .append('option')
    .attr('value', function(d) {
      return d.text
    })
    .text(function(d) {
      return d.text;
    })


  // Variables
  var body = d3.select('body')
  var margin = {
    top: 50,
    right: 50,
    bottom: 50,
    left: 50
  }
  var h = 500 - margin.top - margin.bottom
  var w = 500 - margin.left - margin.right

  var xScale = d3.scale.linear()
    .domain([
      d3.min([0, d3.min(data, function(d) {
        return d['sepal.length']
      })]),
      d3.max([0, d3.max(data, function(d) {
        return d['sepal.length']
      })])
    ])
    .range([0, w])
  var yScale = d3.scale.linear()
    .domain([
      d3.min([0, d3.min(data, function(d) {
        return d['sepal.length']
      })]),
      d3.max([0, d3.max(data, function(d) {
        return d['sepal.length']
      })])
    ])
    .range([h, 0])

  // SVG
  var svgContainer = body.append('div').attr('class', 'svg-container')

  var svg = svgContainer.append('svg')
    .attr('height', h + margin.top + margin.bottom)
    .attr('width', w + margin.left + margin.right)
    .append('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

  // X-axis
  var xAxis = d3.svg.axis()
    .scale(xScale)
    // .tickFormat(formatPercent)
    .ticks(6)
    .outerTickSize(0)
    .tickSize(0)

    .orient('bottom')

  // Y-axis
  var yAxis = d3.svg.axis()
    .scale(yScale)
    // .tickFormat(formatPercent)
    .ticks(6)
    .tickSize(-w)
    .outerTickSize(0)
    .orient('left')

  // Circles
  var circles = svg.selectAll('circle')
    .data(data)
    .enter()
    .append('circle')
    .attr('cx', function(d) {
      return xScale(d['sepal.length'])
    })
    .attr('cy', function(d) {
      return yScale(d['sepal.length'])
    })
    .attr('r', '10')
    // .attr('stroke', 'black')
    .attr('stroke-width', 0.2)
    .attr("fill", function(d) {
      return color(cValue(d));
    })
    .attr('fill-opacity', 0.8)
    // .attr('fill', function(d, i) {
    //   return colorScale(i)
    // })
    .on('mouseover', function() {
      d3.select(this)
        .transition()
        .duration(300)
        .ease('elastic')
        .attr('r', 15)
        .attr('stroke-width', 1)
        .attr('fill-opacity', 1)

    })
    .on('mouseout', function() {
      d3.select(this)
        .transition()
        .duration(100)
        .attr('r', 10)
        .attr('stroke-width', 0.5)
    })
    .append('title') // Tooltip
    .text(function(d) {
      return d.variety +
        '\nSepal Length: ' + d['sepal.length'] +
        '\nSepal Width: ' + d['sepal.width'] +
        '\nPetal Length: ' + d['petal.length'] +
        '\nPetal Width: ' + d['petal.width']
    })

  // X-axis
  svg.append('g')
    .attr('class', 'axis')
    .attr('id', 'xAxis')
    .attr('transform', 'translate(0,' + h + ')')
    .call(xAxis)
    .append('text') // X-axis Label
    .attr('id', 'xAxisLabel')
    .attr('y', -25)
    .attr('x', w)
    .attr('dy', '.71em')
    .style('text-anchor', 'end')
    .text('Sepal Length')

  // labels distance from xaxis
  svg.selectAll(".axis text")
    .attr("dy", 15);

  // Y-axis
  svg.append('g')
    .attr('class', 'axis')
    .attr('id', 'yAxis')
    .call(yAxis)
    .append('text') // y-axis Label
    .attr('id', 'yAxisLabel')
    .attr('transform', 'rotate(-90)')
    .attr('x', 0)
    .attr('y', 5)
    .attr('dy', '.71em')
    .style('text-anchor', 'end')
    .text('Sepal Length')

  function yChange() {
    var value = this.value // get the new y value
    yScale // change the yScale
      .domain([
        d3.min([0, d3.min(data, function(d) {
          return d[value]
        })]),
        d3.max([0, d3.max(data, function(d) {
          return d[value]
        })])
      ])
    yAxis.scale(yScale) // change the yScale
    d3.select('#yAxis') // redraw the yAxis
      .transition().duration(500)
      .call(yAxis)
    d3.select('#yAxisLabel') // change the yAxisLabel
      .text(value)
    d3.selectAll('circle') // move the circles
      .transition().duration(500)
      .delay(function(d, i) {
        return i * 10
      })
      .attr('cy', function(d) {
        return yScale(d[value])
      })
  }

  function xChange() {
    var value = this.value // get the new x value
    xScale // change the xScale
      .domain([
        d3.min([0, d3.min(data, function(d) {
          return d[value]
        })]),
        d3.max([0, d3.max(data, function(d) {
          return d[value]
        })])
      ])
    xAxis.scale(xScale) // change the xScale
    d3.select('#xAxis') // redraw the xAxis
      .transition().duration(500)
      .call(xAxis)
    d3.select('#xAxisLabel') // change the xAxisLabel
      .transition().duration(500)
      .text(value)
    d3.selectAll('circle') // move the circles
      .transition().duration(500)
      .delay(function(d, i) {
        return i * 10
      })
      .attr('cx', function(d) {
        return xScale(d[value])
      })
  }

  // create legend
  var legendContainer = body.append('div')
    .attr('class', 'legend-container')

  var legend = legendContainer.selectAll(".legend")
    .data(color.domain())
    .enter().append("div")
    .attr("class", "legend")

  // draw legend colored rectangles
  legend.append("span")
    .attr("class", "legend-color")
    .style("background-color", color);

  // draw legend text
  legend.append("span")
    .text(function(d) {
      return d;
    })

})
body {
  font-size: 16px;
  display: flex;
  flex-wrap: wrap;
}

.controls {
  flex: 1 1 100%;
}

.legend-container {
  align-items: center;
  flex: 0 1 auto;
  align-self: center;
  margin: 0 auto;
}

circle:hover {
  fill: orange;
}

.axis text {
  font-size: 13px;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
  stroke-width: 0.02px;
}

label {
  position: absolute;
  top: 10px;
  right: 10px;
}

.legend {
  margin-bottom: 0.5em;
}

.legend-color {
  width: 20px;
  height: 20px;
  display: inline-block;
  border-radius: 50%;
  vertical-align: middle;
  margin-right: 1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>

<body></body>

With this approach you end up with a structure like this:

body
  .controls
  .svg-container
    svg
  .legend-container

which can make responsive layouts especially easy with CSS.