具有力分布的散点图

时间:2019-05-02 10:10:20

标签: javascript d3.js data-visualization force-layout

我正在尝试在D3中使用两个X和Y轴的力模拟绘制散点图,我想应用力布局,只是为了避免重叠的点,但是我得到了完全相反的效果(点是重叠的,并且位置不正确)

到目前为止,这是我的代码:

// Create SVG and margins

var margin = {top: 52, right: 78, bottom: 52, left: 78}
var myWidth = 900 - margin.left - margin.right
var myHeight = 450 - margin.top - margin.bottom

var svg = d3.select('body').append('svg')
  .attr('width', myWidth + margin.left + margin.right)
  .attr('height', myHeight + margin.top + margin.bottom)

var g = svg.append("g")
  .attr("transform", "translate(" + margin.left + ", " + margin.top + ")")

// Scale

var y = d3.scaleLinear()
  .domain([2,10])
    .range([myHeight, 0])

var x = d3.scaleLinear()
    .domain([0,100])
    .range([0, myWidth])

// Axis

var yAxisCall = d3.axisLeft(y).tickSize(10)
g.append("g")
  .attr("class", "y-axis")
  .call(yAxisCall)

var xAxisCall = d3.axisBottom(x).tickSize(10)
g.append("g")
  .attr("class", "x-axis")
  .attr("transform", "translate(0, " + myHeight + ")")
  .call(xAxisCall)

 // Helper Functions

var myIbus = function(d,i){
  if (d.ibus) {
    return d.ibus[1] ? (y((d.ibus[0] + d.ibus[1])/2)) : (y(d.ibus[0]))
  }
  else return 0
 }

var myABV = function(d,i){
  if (d.abv) {
    return d.abv[1] ? (x((d.abv[0] + d.abv[1])/2)) : (x(d.abv[0]))
  }
  else return 0
 }


// Force Simulation

var simulation = d3.forceSimulation(nodes)
  .force('collide', d3.forceCollide())
  .on('tick', ticked)


function ticked() {

  var myCircles = g.selectAll('circle')
    .data(nodes)

  myCircles.enter()
    .append('circle')
    .attr("cx", myIbus)
    .attr("cy", myABV)
    .attr("r", 8)

  myCircles.exit().remove()

}

我在https://bl.ocks.org/Jesus82/ad5c6fb46f8be5a9d3e763f8a1ba03d7中有一个工作示例,其中包含我正在使用的数据(我想根据其ABV-酒精含量和IBUS苦味来形象化啤酒样式),并且数据在范围内,只是用他们的意思。

谢谢!

1 个答案:

答案 0 :(得分:1)

关于使用力模拟来避免散点图中重叠的点,最重要的是使用d3.forceXd3.forceY方法来设置位置,而d3.forceCollide只是为了避免重叠。

因此,您的模拟应为:

var simulation = d3.forceSimulation(nodes)
  .force('collide', d3.forceCollide().radius(8))
  .force('x', d3.forceX(myIbus))
  .force('y', d3.forceY(myABV))
  .on('tick', ticked);

在您的ticked函数中:

function ticked() {
  myCircles.attr("cx", function(d) {
      return d.x
    })
    .attr("cy", function(d) {
      return d.y
    });
};

您可以使用其中的strengths种力量:给forceX/Y力量提供更多的力量,可以使散点更加准确,但要重叠的点更多;为forceCollide增强强度会减少重叠,但会使可视化效果不准确。

除此之外,您还有一些小问题:

  1. 将输入,更新和退出选择移至{strong>外部 ticked功能;
  2. 也许我错了,但是您的myIbusmyABV方法似乎有错误的尺度(只需交换它们)。
  3. 将数据变量初始化移动到选择更新之前。

这是您更新的代码:

<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <style>
    body {
      margin: 0;
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
    }
  </style>
</head>

<body>
  <script>
    var nodes = [{
        name: 'abbey_dubbel',
        abv: [6, 7.6],
        ibus: [15, 25]
      },
      {
        name: 'abbey_tripel',
        abv: [7.5, 9.5],
        ibus: [20, 40]
      },
      {
        name: 'ale',
        abv: [0],
        ibus: [0]
      },
      {
        name: 'amber_ale',
        abv: [0],
        ibus: [0]
      },
      {
        name: 'amber_lager',
        abv: [4.7, 5.5],
        ibus: [18, 30]
      },
      {
        name: 'american_IPA',
        abv: [6, 14],
        ibus: [40, 70]
      },
      {
        name: 'american_pale_ale',
        abv: [4.5, 6.2],
        ibus: [30, 50]
      },
      {
        name: 'american_strong_ale',
        abv: [8, 12],
        ibus: [30, 60]
      },
      {
        name: 'baltic_porter',
        abv: [6.5, 9.5],
        ibus: [20, 40]
      },
      {
        name: 'barley_wine',
        abv: [8, 12],
        ibus: [50, 100]
      },
      {
        name: 'belgian_ale',
        abv: [8, 5.5],
        ibus: [20, 30]
      },
      {
        name: 'belgian_strong_ale',
        abv: [7.5, 10.5],
        ibus: [22, 35]
      },
      {
        name: 'berliner_weisse',
        abv: [2.8, 3.8],
        ibus: [3, 8]
      },
      {
        name: 'biere_de_garde',
        abv: [6, 8.5],
        ibus: [18, 28]
      },
      {
        name: 'black_IPA',
        abv: [5.5, 9],
        ibus: [50, 90]
      },
      {
        name: 'blond_ale',
        abv: [6, 7.5],
        ibus: [15, 30]
      },
      {
        name: 'brown_ale',
        abv: [4.2, 5.4],
        ibus: [20, 30]
      },
      {
        name: 'brut_ipa',
        abv: [5, 7.5],
        ibus: [40, 60]
      },
      {
        name: 'cider',
        abv: [0],
        ibus: [0]
      },
      {
        name: 'doppelbock',
        abv: [7, 10],
        ibus: [16, 26]
      },
      {
        name: 'dunkel',
        abv: [4.5, 5.6],
        ibus: [18, 28]
      },
      {
        name: 'ESB',
        abv: [4.6, 6.2],
        ibus: [30, 50]
      },
      {
        name: 'foreign_extra_stout',
        abv: [6.3, 8],
        ibus: [50, 70]
      },
      {
        name: 'fruit_beer',
        abv: [2, 8],
        ibus: [40]
      },
      {
        name: 'fruity_lambic',
        abv: [5, 7],
        ibus: [10]
      },
      {
        name: 'gose',
        abv: [4.2, 4.8],
        ibus: [5, 12]
      },
      {
        name: 'gueuze_lambic',
        abv: [5, 8],
        ibus: [10]
      },
      {
        name: 'imperial_IPA',
        abv: [7.5, 10],
        ibus: [60, 120]
      },
      {
        name: 'imperial_pils',
        abv: [0],
        ibus: [0]
      },
      {
        name: 'imperial_porter',
        abv: [4.8, 6.5],
        ibus: [25, 50]
      },
      {
        name: 'imperial_stout',
        abv: [5, 7.5],
        ibus: [40, 60]
      },
      {
        name: 'india_style_lager',
        abv: [0],
        ibus: [0]
      },
      {
        name: 'IPA',
        abv: [5, 7.5],
        ibus: [40, 60]
      },
      {
        name: 'lager',
        abv: [0],
        ibus: [0]
      },
      {
        name: 'lambic',
        abv: [5, 6.5],
        ibus: [10]
      },
      {
        name: 'landbier',
        abv: [4.7, 7.4],
        ibus: [16, 22]
      },
      {
        name: 'neipa',
        abv: [6, 9],
        ibus: [25, 60]
      },
      {
        name: 'old_ale',
        abv: [5.5, 9],
        ibus: [30, 60]
      },
      {
        name: 'pale_lager',
        abv: [4.6, 6],
        ibus: [18, 25]
      },
      {
        name: 'pilsener',
        abv: [4.4, 5.2],
        ibus: [22, 40]
      },
      {
        name: 'porter',
        abv: [4, 5.4],
        ibus: [28, 35]
      },
      {
        name: 'premium_lager',
        abv: [4.2, 5.8],
        ibus: [30, 45]
      },
      {
        name: 'quadrupel',
        abv: [8, 12],
        ibus: [20, 35]
      },
      {
        name: 'saison',
        abv: [3.5, 9.5],
        ibus: [20, 35]
      },
      {
        name: 'scotch_ale',
        abv: [6.5, 10],
        ibus: [17, 35]
      },
      {
        name: 'session_IPA',
        abv: [3, 5],
        ibus: [35, 60]
      },
      {
        name: 'smoked',
        abv: [0],
        ibus: [0]
      },
      {
        name: 'sour_red_brown',
        abv: [4.6, 6.5],
        ibus: [10, 25]
      },
      {
        name: 'sour_wild_ale',
        abv: [0],
        ibus: [0]
      },
      {
        name: 'specialty_grain',
        abv: [0],
        ibus: [0]
      },
      {
        name: 'stout',
        abv: [4, 6],
        ibus: [20, 40]
      },
      {
        name: 'sweet_stout',
        abv: [4, 6],
        ibus: [20, 40]
      },
      {
        name: 'weissbier',
        abv: [4.3, 5.6],
        ibus: [8, 15]
      },
      {
        name: 'weizen_bock',
        abv: [6.5, 9],
        ibus: [15, 30]
      },
      {
        name: 'wheat_ale',
        abv: [4, 5.5],
        ibus: [15, 30]
      },
      {
        name: 'witbier',
        abv: [4.5, 5.5],
        ibus: [8, 20]
      }
    ]
    // Create SVG and margins

    var margin = {
      top: 52,
      right: 78,
      bottom: 52,
      left: 78
    }
    var myWidth = 900 - margin.left - margin.right
    var myHeight = 450 - margin.top - margin.bottom

    var svg = d3.select('body').append('svg')
      .attr('width', myWidth + margin.left + margin.right)
      .attr('height', myHeight + margin.top + margin.bottom)

    var g = svg.append("g")
      .attr("transform", "translate(" + margin.left + ", " + margin.top + ")")

    // Scale

    var y = d3.scaleLinear()
      .domain([2, 10])
      .range([myHeight, 0])

    var x = d3.scaleLinear()
      .domain([0, 100])
      .range([0, myWidth])

    // Axis

    var yAxisCall = d3.axisLeft(y).tickSize(10)
    g.append("g")
      .attr("class", "y-axis")
      .call(yAxisCall)

    var xAxisCall = d3.axisBottom(x).tickSize(10)
    g.append("g")
      .attr("class", "x-axis")
      .attr("transform", "translate(0, " + myHeight + ")")
      .call(xAxisCall)

    // Helper Functions

    var myIbus = function(d, i) {
      if (d.ibus) {
        return d.ibus[1] ? (x((d.ibus[0] + d.ibus[1]) / 2)) : (x(d.ibus[0]))
      } else return 0
    }

    var myABV = function(d, i) {
      if (d.abv) {
        return d.abv[1] ? (y((d.abv[0] + d.abv[1]) / 2)) : (y(d.abv[0]))
      } else return 0
    }


    // Force Simulation

    var simulation = d3.forceSimulation(nodes)
      .force('collide', d3.forceCollide().radius(8))
      .force('x', d3.forceX(myIbus))
      .force('y', d3.forceY(myABV))
      .on('tick', ticked);

    var myCircles = g.selectAll('circle')
      .data(nodes)

    myCircles = myCircles.enter()
      .append('circle')
      .attr("r", 8)
      .merge(myCircles);

    myCircles.exit().remove()


    function ticked() {
      myCircles.attr("cx", function(d) {
          return d.x
        })
        .attr("cy", function(d) {
          return d.y
        });
    };
  </script>
</body>