Как нарисовать поверхность по заданным параметрам { x, y, z } и прочее в d3.js?
Не нашел четких решений для того чтобы решить данную проблему. Есть какой нибудь пример?
Я пробовал вот это, но у меня не получилось модернизировать код под себя.
Вот задача: даны 6 точкек: A(x1,y1,z1), B(x2,y2,z2), C(x3,y3,z3) ,D(x4,y4,z4), E(x5,y5,z5), F(x6,y6,z6):
Вот вопрос: как нарисовать из этого поверхность, и/или кривые Безье в 3d пространстве используя d3.js? Да и прибавьте мне рейтинга а то я совсем уже того-с-с-с...
Ответы (2 шт):
То, что я смог реализовать, скорее идея, но не полное решение. Изменил пример по ссылке таким образом:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body{
font-family: sans;
padding: 10px;
}
svg path{
stroke: #000;
stroke-width: 1px;
stroke: rgba(0,0,0,0.2);
}
svg{
border: 1px solid #DED8BF;
background-color: #f4f4d9;
width: 700px;
height: 400px;
}
h1{
font-weight: normal;
margin: 0;
padding-left: 5px;
color: #53483e;
}
p{
margin: 0;
margin-bottom: 10px;
padding-left: 5px;
color: #917e6b;
}
ul{
width: 200px;
float: left;
list-style-type: none;
margin: 0;
padding: 0;
padding-right: 10px;
}
li{
cursor: pointer;
background-color: #c8ad93;
padding: 10px;
margin: 2px;
color: #fff;
}
</style>
</head>
<body>
<h1>D3.js Surface Plots</h1>
<p>Drag graph to change view</p>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="surface3d.js"></script>
<script>
var yaw=0.5,pitch=0.5, width=700, height=400, drag=false;
var coords = [ // тут координаты точек
[23,2,15,4,10,31], // x
[1,35,2,20,14,3], // y
[154,35,72,29,63,81] // z
]
var output=[];
for(var x=-20;x<20;x++){
var f0=[];
output.push(f0);
for(var y=-20;y<20;y++){
f0.push(0);
}
}
for(i=0;i<6;i++) output[coords[0][i]][coords[1][i]]=-coords[2][i];
var surfaces=[
{
name: 'Dataset 1',
data: output
}
];
var selected=surfaces[0];
var ul=d3.select('body')
.append('ul');
var svg=d3.select('body')
.append('svg')
.attr('height',height)
.attr('width',width);
var group = svg.append("g");
var md=group.data([surfaces[0].data])
.surface3D(width,height)
.surfaceHeight(function(d){
return d;
}).surfaceColor(function(d){
var c=d3.hsl((d+100), 0.6, 0.5).rgb();
return "rgb("+parseInt(c.r)+","+parseInt(c.g)+","+parseInt(c.b)+")";
});
ul.selectAll('li')
.data(surfaces)
.enter().append('li')
.html(function(d){
return d.name
}).on('mousedown',function(){
md.data([d3.select(this).datum().data]).surface3D()
.transition().duration(500)
.surfaceHeight(function(d){
return d;
}).surfaceColor(function(d){
var c=d3.hsl((d+100), 0.6, 0.5).rgb();
return "rgb("+parseInt(c.r)+","+parseInt(c.g)+","+parseInt(c.b)+")";
});
});
svg.on("mousedown",function(){
drag=[d3.mouse(this),yaw,pitch];
}).on("mouseup",function(){
drag=false;
}).on("mousemove",function(){
if(drag){
var mouse=d3.mouse(this);
yaw=drag[1]-(mouse[0]-drag[0][0])/50;
pitch=drag[2]+(mouse[1]-drag[0][1])/50;
pitch=Math.max(-Math.PI/2,Math.min(Math.PI/2,pitch));
md.turntable(yaw,pitch);
}
});
</script>
</body>
</html>
Получилось, что в местах с заданными координатами x и y точек образовались пирамидальные пики, высота которых соответствует координате z каждой из них. Останется плавно соединить вершины этих пиков между собой. Я пытался подобрать алгоритм, но за один вечер ничего не вышло.
Первый вариант: ряд кривых Безье. За основу берутся 16 опорных точек, разбитых на матрицу 4 группы по 4 точки. Каждая из этих групп рисует одну кривую Безье, располагающуюся циклом координат x и z нашего 3-д холста. А сами кривые расположены вдоль направления изменения координаты y холста.
<!DOCTYPE html>
<html>
<head>
<title>Ряд кривых Безье по 16 опорным точкам</title>
<meta charset="utf-8">
<style>
svg{
border: 1px solid #DED8BF;
background-color: #f4f4d9;
width: 700px;
height: 400px;
}
</style>
</head>
<body>
<h1>Ряд кривых Безье по 16 опорным точкам</h1>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="surface3d.js"></script>
<script>
var size=64;
var step=0.01;
var arr = new Array([],[],[],[]);
arr[0][0] = [5, 15]; // в скобках координаты x и z опорных точек для первой кривой Безье,
arr[0][1] = [15, 30]; // координата y для всех точек - 0
arr[0][2] = [32, 150];
arr[0][3] = [60, 15];
arr[1][0] = [10, 45]; // в скобках координаты x и y опорных точек для второй кривой Безье
arr[1][1] = [43, 15]; // координата y для всех точек - 1
arr[1][2] = [27, 150];
arr[1][3] = [57, 30];
arr[2][0] = [25, 15]; // в скобках координаты x и y опорных точек для третьй кривой Безье
arr[2][1] = [33, 165]; // координата y для всех точек - 2
arr[2][2] = [47, 69];
arr[2][3] = [55, 30];
arr[3][0] = [5, 5]; // в скобках координаты x и y опорных точек для четвёртой кривой Безье
arr[3][1] = [20, 170]; // координата y для всех точек - 3
arr[3][2] = [43, 45];
arr[3][3] = [57, 15];
var yaw=0.5,pitch=0.5, width=700, height=400, drag=false;
var output=[];
for(var x=-size/2;x<size/2;x++){
var f0=[];
output.push(f0);
for(var y=-2;y<2;y++) f0.push(0);
}
for(i=0;i<arr.length;i++){
var res = [];
for (var t = 0; t < 1 + step; t += step) {
var ind = res.length;
res[ind] = [0, 0];
for (var j = 0; j < arr[i].length; j++) {
var b = getBezierBasis(j, arr[i].length - 1, t);
res[ind][0] += arr[i][j][0] * b;
res[ind][1] += arr[i][j][1] * b;
}
res[ind][0] = Math.floor(res[ind][0]);
res[ind][1] = Math.floor(res[ind][1]);
}
for(j=0;j<res.length;j++) output[res[j][0]][i]=-res[j][1];
}
// i - номер вершины, n - количество вершин, t - положение кривой (от 0 до 1)
function getBezierBasis(i, n, t) {
// Факториал
function f(n) {return (n <= 1) ? 1 : n * f(n - 1)};
// считаем i-й элемент полинома Берштейна
return (f(n)/(f(i)*f(n - i)))* Math.pow(t, i)*Math.pow(1 - t, n - i);
}
var svg=d3.select('body')
.append('svg')
.attr('height',height)
.attr('width',width);
var group = svg.append("g");
var md=group.data([output])
.surface3D(width,height)
.surfaceHeight(function(d){
return d;
}).surfaceColor(function(d){
var c=d3.hsl((d+100), 0.6, 0.5).rgb();
return "rgb("+parseInt(c.r)+","+parseInt(c.g)+","+parseInt(c.b)+")";
});
svg.on("mousedown",function(){
drag=[d3.mouse(this),yaw,pitch];
}).on("mouseup",function(){
drag=false;
}).on("mousemove",function(){
if(drag){
var mouse=d3.mouse(this);
yaw=drag[1]-(mouse[0]-drag[0][0])/50;
pitch=drag[2]+(mouse[1]-drag[0][1])/50;
pitch=Math.max(-Math.PI/2,Math.min(Math.PI/2,pitch));
md.turntable(yaw,pitch);
}
});
</script>
</body>
</html>
Второй вариант: поверхность Безье. За основу взяты те же самые 16 опорных точек, что и в первом варианте.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Поверхность Безье по 16 опорным точкам</title>
<style>
svg{
border: 1px solid #DED8BF;
background-color: #f4f4d9;
width: 700px;
height: 400px;
}
</style>
</head>
<body>
<h1>Поверхность Безье по 16 опорным точкам</h1>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="surface3d.js"></script>
<script>
var size=64;
var step=1/size;
var arr = new Array([],[],[],[]);
arr[0][0] = [5, 15]; // в скобках координаты x и z опорных точек для поверхности Безье,
arr[0][1] = [15, 30]; // координата y для всех точек - 0
arr[0][2] = [32, 150];
arr[0][3] = [60, 15];
arr[1][0] = [10, 45]; // в скобках координаты x и y опорных точек для поверхности Безье
arr[1][1] = [43, 15]; // координата y для всех точек - 1
arr[1][2] = [27, 150];
arr[1][3] = [57, 30];
arr[2][0] = [25, 15]; // в скобках координаты x и y опорных точек для поверхности Безье
arr[2][1] = [33, 165]; // координата y для всех точек - 2
arr[2][2] = [47, 69];
arr[2][3] = [55, 30];
arr[3][0] = [5, 5]; // в скобках координаты x и y опорных точек для поверхности кривой Безье
arr[3][1] = [20, 170]; // координата y для всех точек - 3
arr[3][2] = [43, 45];
arr[3][3] = [57, 15];
var res = [];
for (var t1 = 0; t1 < 1 + step; t1 += step) {
var ind1 = res.length;
res[ind1] = [];
for (var t = 0; t < 1 + step; t += step) {
var ind = res[ind1].length;
res[ind1][ind] = [0, 0];
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr[i].length; j++) {
var b = getBezierBasis(i, arr.length - 1, t1);
var b1 = getBezierBasis(j, arr[i].length - 1, t);
res[ind1][ind][0] += arr[i][j][0] * b * b1;
res[ind1][ind][1] += arr[i][j][1] * b * b1;
}
}
res[ind1][ind][0] = Math.floor(res[ind1][ind][0]);
res[ind1][ind][1] = Math.floor(res[ind1][ind][1]);
}
}
// i - номер вершины, n - количество вершин, t - положение кривой (от 0 до 1)
function getBezierBasis(i, n, t) {
// Факториал
function f(n) {return (n <= 1) ? 1 : n * f(n - 1)};
// считаем i-й элемент полинома Берштейна
return (f(n)/(f(i)*f(n - i)))* Math.pow(t, i)*Math.pow(1 - t, n - i);
}
var yaw=0.5,pitch=0.5, width=700, height=400, drag=false;
var output=[];
for(var x=-size/2;x<size/2;x++){
var f0=[];
output.push(f0);
for(var y=-size/2;y<size/2;y++) f0.push(0);
}
for(j=0;j<size;j++) for(i=0;i<size;i++) output[res[j][i][0]][j]=-res[j][i][1];
var svg=d3.select('body')
.append('svg')
.attr('height',height)
.attr('width',width);
var group = svg.append("g");
var md=group.data([output])
.surface3D(width,height)
.surfaceHeight(function(d){
return d;
}).surfaceColor(function(d){
var c=d3.hsl((d+100), 0.6, 0.5).rgb();
return "rgb("+parseInt(c.r)+","+parseInt(c.g)+","+parseInt(c.b)+")";
});
svg.on("mousedown",function(){
drag=[d3.mouse(this),yaw,pitch];
}).on("mouseup",function(){
drag=false;
}).on("mousemove",function(){
if(drag){
var mouse=d3.mouse(this);
yaw=drag[1]-(mouse[0]-drag[0][0])/50;
pitch=drag[2]+(mouse[1]-drag[0][1])/50;
pitch=Math.max(-Math.PI/2,Math.min(Math.PI/2,pitch));
md.turntable(yaw,pitch);
}
});
</script>
</body>
</html>
К сожалению, с помощью библиотеки d3.js (в отличии от three.js на WebGl) невозможно отобразить абсолютно любую поверхность Безье, а только такую, у которой не встречается одинаковых совпадений координат x и y. (типо похожую на реальный участок поверхности земли) Поэтому приходится подбирать опорные точки, а как результат, поверхность Безье получается слишком сглаженной. Поэтому первый вариант более выразителен.