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
。希望这对您有所帮助!
我正在尝试使用 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
。希望这对您有所帮助!