D3 x3dom - 3d 散点图

D3 x3dom - 3d scatter plot

我正在尝试使用 d3 库重现 3d-ScatterPlot。该示例是用 v3 编写的,我很难在 v5 中重新创建它。

问题是没有绘制轴。

function drawAxis( axisIndex, key, duration ) {
var scale = d3.scaleLinear()
  .domain( [-5,5] ) // demo data range
  .range( axisRange )

scales[axisIndex] = scale;
var numTicks = 8;
var tickSize = 0.1;
var tickFontSize = 0.5;
// ticks along each axis
var ticks = scene.selectAll( "."+axisName("Tick", axisIndex) )
   .data( scale.ticks( numTicks ));
var newTicks = ticks.enter()
  .append("transform")
    .attr("class", axisName("Tick", axisIndex));
newTicks.append("shape").call(makeSolid)
  .append("box")
    .attr("size", tickSize + " " + tickSize + " " + tickSize);
// enter + update
ticks.attr("translation", function(tick) {
     return constVecWithAxisValue( 0, scale(tick), axisIndex ); })
ticks.exit().remove();
// tick labels
var tickLabels = ticks.selectAll("billboard shape text")
  .data(function(d) { return [d]; });
var newTickLabels = tickLabels.enter()
  .append("billboard")
     .attr("axisOfRotation", "0 0 0")    
  .append("shape")
  .call(makeSolid)
newTickLabels.append("text")
  .attr("string", scale.tickFormat(10))
  .attr("solid", "true")
  .append("fontstyle")
    .attr("size", tickFontSize)
    .attr("family", "SANS")
    .attr("justify", "END MIDDLE" );
tickLabels // enter + update
  .attr("string", scale.tickFormat(10))
tickLabels.exit().remove();
// base grid lines
if (axisIndex==0 || axisIndex==2) {
  var gridLines = scene.selectAll( "."+axisName("GridLine", axisIndex))
     .data(scale.ticks( numTicks ));
  gridLines.exit().remove();
  var newGridLines = gridLines.enter()
    .append("transform")
      .attr("class", axisName("GridLine", axisIndex))
      .attr("rotation", axisIndex==0 ? [0,1,0, -Math.PI/2] : [0,0,0,0])
    .append("shape")
  newGridLines.append("appearance")
    .append("material")
      .attr("emissiveColor", "gray")
  newGridLines.append("polyline2d");
  gridLines.selectAll("shape polyline2d").attr("lineSegments", "0 0, " + axisRange[1] + " 0")
  gridLines.attr("translation", axisIndex==0
        ? function(d) { return scale(d) + " 0 0"; }
        : function(d) { return "0 0 " + scale(d); }
      )
  } 
}

我的猜测是这个方法有问题。

我在这里创建了一个完整的例子:https://codepen.io/anon/pen/aevWQX

感谢您的帮助!

v4 开始,您需要 .merge() 您新添加的 .enter() 选择和您现有的选择,否则结果是空选择 - 这就是执行代码的原因, 只是没有应用到任何元素。

var x3d = d3.select("#divPlot")
  .append("x3d")
  .style("width", "500px")
  .style("height", "500px")
  .style("border", "none")

var scene = x3d.append("scene")

scene.append("orthoviewpoint")
  .attr("centerOfRotation", [5, 5, 5])
  .attr("fieldOfView", [-5, -5, 15, 15])
  .attr("orientation", [-0.5, 1, 0.2, 1.12 * Math.PI / 4])
  .attr("position", [8, 4, 15])

var rows = initializeDataGrid();
var axisRange = [0, 10];
var scales = [];
var initialDuration = 0;
var defaultDuration = 800;
var ease = 'linear';
var time = 0;
var axisKeys = ["x", "y", "z"]

// Helper functions for initializeAxis() and drawAxis()
function axisName(name, axisIndex) {
  return ['x', 'y', 'z'][axisIndex] + name;
}

function constVecWithAxisValue(otherValue, axisValue, axisIndex) {
  var result = [otherValue, otherValue, otherValue];
  result[axisIndex] = axisValue;
  return result;
}

// Used to make 2d elements visible
function makeSolid(selection, color) {
  selection.append("appearance")
    .append("material")
    .attr("diffuseColor", color || "black")
  return selection;
}

// Initialize the axes lines and labels.
function initializePlot() {
  initializeAxis(0);
  initializeAxis(1);
  initializeAxis(2);
}

function initializeAxis(axisIndex) {
  var key = axisKeys[axisIndex];
  drawAxis(axisIndex, key, initialDuration);

  var scaleMin = axisRange[0];
  var scaleMax = axisRange[1];

  // the axis line
  var newAxisLine = scene.append("transform")
    .attr("class", axisName("Axis", axisIndex))
    .attr("rotation", ([
      [0, 0, 0, 0],
      [0, 0, 1, Math.PI / 2],
      [0, 1, 0, -Math.PI / 2]
    ][axisIndex]))
    .append("shape")
  newAxisLine
    .append("appearance")
    .append("material")
    .attr("emissiveColor", "lightgray")
  newAxisLine
    .append("polyline2d")
    // Line drawn along y axis does not render in Firefox, so draw one
    // along the x axis instead and rotate it (above).
    .attr("lineSegments", "0 0," + scaleMax + " 0")

  // axis labels
  var newAxisLabel = scene.append("transform")
    .attr("class", axisName("AxisLabel", axisIndex))
    .attr("translation", constVecWithAxisValue(0, scaleMin + 1.1 * (scaleMax - scaleMin), axisIndex))

  var newAxisLabelShape = newAxisLabel
    .append("billboard")
    .attr("axisOfRotation", "0 0 0") // face viewer
    .append("shape")
    .call(makeSolid)

  var labelFontSize = 0.6;

  newAxisLabelShape
    .append("text")
    .attr("class", axisName("AxisLabelText", axisIndex))
    .attr("solid", "true")
    .attr("string", key)
    .append("fontstyle")
    .attr("size", labelFontSize)
    .attr("family", "SANS")
    .attr("justify", "END MIDDLE")
}

// Assign key to axis, creating or updating its ticks, grid lines, and labels.
function drawAxis(axisIndex, key, duration) {

  var scale = d3.scaleLinear()
    .domain([-5, 5]) // demo data range
    .range(axisRange)

  scales[axisIndex] = scale;

  var numTicks = 8;
  var tickSize = 0.1;
  var tickFontSize = 0.5;

  // ticks along each axis
  var ticks = scene.selectAll("." + axisName("Tick", axisIndex))
    .data(scale.ticks(numTicks));
  var newTicks = ticks.enter()
    .append("transform")
    .attr("class", axisName("Tick", axisIndex));
  newTicks.append("shape").call(makeSolid)
    .append("box")
    .attr("size", tickSize + " " + tickSize + " " + tickSize);
  // enter + update
  ticks.attr("translation", function(tick) {
    return constVecWithAxisValue(0, scale(tick), axisIndex);
  })
  ticks.exit().remove();

  // tick labels
  var tickLabels = ticks.selectAll("billboard shape text")
    .data(function(d) {
      return [d];
    });
  var newTickLabels = tickLabels.enter()
    .append("billboard")
    .attr("axisOfRotation", "0 0 0")
    .append("shape")
    .call(makeSolid)
  newTickLabels.append("text")
    .attr("string", scale.tickFormat(10))
    .attr("solid", "true")
    .append("fontstyle")
    .attr("size", tickFontSize)
    .attr("family", "SANS")
    .attr("justify", "END MIDDLE");
  tickLabels // enter + update
    .attr("string", scale.tickFormat(10))
  tickLabels.exit().remove();

  // base grid lines
  if (axisIndex == 0 || axisIndex == 2) {
    debugger;

    var gridLines = scene.selectAll("." + axisName("GridLine", axisIndex))
      .data(scale.ticks(numTicks));
    gridLines.exit().remove();

    var newGridLines = gridLines.enter()
      .append("transform")
      .attr("class", axisName("GridLine", axisIndex))
      .attr("rotation", axisIndex == 0 ? [0, 1, 0, -Math.PI / 2] : [0, 0, 0, 0])
      .append("shape")

    newGridLines.append("appearance")
      .append("material")
      .attr("emissiveColor", "gray")
    newGridLines.append("polyline2d");

    gridLines = newGridLines
      .merge(gridLines);

    gridLines.selectAll("shape polyline2d").attr("lineSegments", "0 0, " + axisRange[1] + " 0")

    gridLines.attr("translation", axisIndex == 0 ?
      function(d) {
        return scale(d) + " 0 0";
      } :
      function(d) {
        return "0 0 " + scale(d);
      }
    )
  }
}

// Update the data points (spheres) and stems.
function plotData(duration) {

  if (!rows) {
    console.log("no rows to plot.")
    return;
  }

  var x = scales[0],
    y = scales[1],
    z = scales[2];
  var sphereRadius = 0.2;

  // Draw a sphere at each x,y,z coordinate.
  var datapoints = scene.selectAll(".datapoint").data(rows);
  datapoints.exit().remove()

  var newDatapoints = datapoints.enter()
    .append("transform")
    .attr("class", "datapoint")
    .attr("scale", [sphereRadius, sphereRadius, sphereRadius])
    .append("shape");
  newDatapoints
    .append("appearance")
    .append("material");
  newDatapoints
    .append("sphere")
  // Does not work on Chrome; use transform instead
  //.attr("radius", sphereRadius)

  datapoints.selectAll("shape appearance material")
    .attr("diffuseColor", 'steelblue')

  datapoints.transition().ease(d3.easeLinear).duration(duration)
    .attr("translation", function(row) {
      return x(row[axisKeys[0]]) + " " + y(row[axisKeys[1]]) + " " + z(row[axisKeys[2]])
    })
}

function initializeDataGrid() {
  var rows = [];
  // Follow the convention where y(x,z) is elevation.
  for (var x = -5; x <= 5; x += 1) {
    for (var z = -5; z <= 5; z += 1) {
      rows.push({
        x: x,
        y: 0,
        z: z
      });
    }
  }
  return rows;
}

function updateData() {
  time += Math.PI / 8;
  if (x3d.node() && x3d.node().runtime) {
    for (var r = 0; r < rows.length; ++r) {
      var x = rows[r].x;
      var z = rows[r].z;
      rows[r].y = 5 * (Math.sin(0.5 * x + time) * Math.cos(0.25 * z + time));
    }
    plotData(defaultDuration);
  } else {
    console.log('x3d not ready.');
  }
}

initializeDataGrid();
initializePlot();
setInterval(updateData, defaultDuration);
.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.axis text {
  font: 10px sans-serif;
}
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="chrome=1" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <title>3D Scatter Plot</title>
  <!--<script type="text/javascript" src="http://d3js.org/d3.v5.min.js"></script>-->
  <script type="text/javascript" src="http://d3js.org/d3.v5.js"></script>
  <script type="text/javascript" src="http://x3dom.org/x3dom/dist/x3dom-full.js"></script>
  <link rel="stylesheet" type="text/css" href="http://www.x3dom.org/download/dev/x3dom.css" />
</head>

<body>
  <div id="divPlot" style="width: 500px; height: 500px;"></div>
</body>

</html>

我所做的唯一更改是在 drawAxis 中的 if 语句中某处调用了 .merge。希望这对您有所帮助!