Я новичок в D3 и JS и пытаюсь понять, где я ошибаюсь. У меня есть данные временного ряда x/y, которые я хочу анимировать с помощью D3. Целью является график, который показывает все 4 точки и перемещает их через каждую из 5 меток времени в выборке данных со скоростью, которая рассчитывается с меткой времени.
Я структурировал данные в том формате, который, по моему мнению, является подходящим для D3, чтобы иметь возможность эффективно анимировать их. Вложенный json, который я использую, имеет следующий формат:
[{"time": 0, "info":[{"id": "A", "x": 10, "y": 20},
{"id": "B", "x": 40, "y": 90},
{"id": "C", "x": 10, "y": 90},
{"id": "D", "x": 80, "y": 70}]},
{"time": 0.5, "info":[{"id": "A", "x": 20, "y": 30},
{"id": "B", "x": 60, "y": 70},
{"id": "C", "x": 100, "y": 10},
{"id": "D", "x": 10, "y": 32}]},
{"time": 1, "info":[{"id": "A", "x": 50, "y": 60},
{"id": "B", "x": 0, "y": 0},
{"id": "C", "x": 80, "y": 10},
{"id": "D", "x": 50, "y": 50}]},
{"time": 1.5, "info":[{"id": "A", "x": 40, "y": 50},
{"id": "B", "x": 100, "y": 30},
{"id": "C", "x": 80, "y": 90},
{"id": "D", "x": 80, "y": 40}]},
{"time": 2, "info":[{"id": "A", "x": 60, "y": 10},
{"id": "B", "x": 0, "y": 50},
{"id": "C", "x": 10, "y": 50},
{"id": "D", "x": 30, "y": 60}]}]
Я могу построить отдельные моменты времени для этих данных, но есть несколько проблем при попытке анимировать это: Вот Fiddle с моим кодом. Продолжительность перехода задается разницей между текущим временем и предыдущим временем.
Кажется, что существует много способов объединения переходов, повторяющих данные, но они кажутся неэффективными. Вот как я заставил его (частично) работать, это также можно увидеть в Fiddle.
var data = [{"time": 0, "info":[{"id": "A", "x": 10, "y": 20},
{"id": "B", "x": 40, "y": 90},
{"id": "C", "x": 10, "y": 90},
{"id": "D", "x": 80, "y": 70}]},
{"time": 0.5, "info":[{"id": "A", "x": 20, "y": 30},
{"id": "B", "x": 60, "y": 70},
{"id": "C", "x": 100, "y": 10},
{"id": "D", "x": 10, "y": 32}]},
{"time": 1, "info":[{"id": "A", "x": 50, "y": 60},
{"id": "B", "x": 80, "y": 50},
{"id": "C", "x": 80, "y": 10},
{"id": "D", "x": 50, "y": 50}]},
{"time": 1.5, "info":[{"id": "A", "x": 40, "y": 50},
{"id": "B", "x": 100, "y": 30},
{"id": "C", "x": 80, "y": 90},
{"id": "D", "x": 80, "y": 40}]},
{"time": 2, "info":[{"id": "A", "x": 60, "y": 10},
{"id": "B", "x": 0, "y": 50},
{"id": "C", "x": 10, "y": 50},
{"id": "D", "x": 30, "y": 60}]}]
var margin = {top: 30, right: 30, bottom: 30, left: 30},
width = 300 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#container")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Add X axis
var x = d3.scaleLinear()
.domain([0, 100])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 100])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
function runTheSimulation() {
var id = 0
if (id == 0) {
var points = svg.selectAll('circle')
.data(data[0].info, function(d, i) {return d.id})
points.enter().append('circle')
.attr('cx', function(d) {return x(d.x)})
.attr('cy', function(d) {return y(d.y)})
.attr('r', 8)
.attr('fill', 'white')
.attr('stroke', 'black')
id++;
next_frame()
} else {
next_frame()
}
function next_frame() {
var delta_time = (data[id].time - data[id-1].time)*1000
var players = svg.selectAll('circle')
.data(data[id].info, function(d) {return d.id})
.transition()
.duration(delta_time)
.ease(d3.easeLinear)
.attr('cx', function(d) {return x(d.x)})
.attr('cy', function(d) {return y(d.y)})
.on('end', function() {
id++;
//console.info(id)
if (id >= data.length) {
return;
}
next_frame();
})
}
}
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
} <!-- Bootstrap CSS -->
<link rel = "stylesheet" href = "http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<!-- jQuery -->
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- Bootstrap JavaScript -->
<script src = "http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<!-- Load d3.js -->
<script src = "https://d3js.org/d3.v4.js"></script>
</head>
<body>
<button onclick = "runTheSimulation()">RUN THE SIMULATION</button>
<div id = "container"></div>Проблемы, которые у меня есть:
Спасибо за любые мысли и помощь!
Да, это именно так. Четыре точки одновременно перемещаются к своим соответствующим координатам для каждой временной метки в данных.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Поэтому я позволил себе некоторые вольности с вашим кодом, чтобы сделать его более похожим на d3. Моя попытка здесь состоит в том, чтобы ваши данные управляли визуализацией, связывая все переходы в цепочку вместо того, чтобы перехватывать конечное событие и переходить к следующему индексу.
<head>
<!-- Load d3.js -->
<script src = "https://d3js.org/d3.v4.js"></script>
<style>
</style>
</head>
<body>
<button onclick = "runTheSimulation()">RUN THE SIMULATION</button>
<div id = "container"></div>
<script>
var data = [
{
time: 0,
info: [
{ id: 'A', x: 10, y: 20 },
{ id: 'B', x: 40, y: 90 },
{ id: 'C', x: 10, y: 90 },
{ id: 'D', x: 80, y: 70 },
],
},
{
time: 0.5,
info: [
{ id: 'A', x: 20, y: 30 },
{ id: 'B', x: 60, y: 70 },
{ id: 'C', x: 100, y: 10 },
{ id: 'D', x: 10, y: 32 },
],
},
{
time: 1,
info: [
{ id: 'A', x: 50, y: 60 },
{ id: 'B', x: 80, y: 50 },
{ id: 'C', x: 80, y: 10 },
{ id: 'D', x: 50, y: 50 },
],
},
{
time: 1.5,
info: [
{ id: 'A', x: 40, y: 50 },
{ id: 'B', x: 100, y: 30 },
{ id: 'C', x: 80, y: 90 },
{ id: 'D', x: 80, y: 40 },
],
},
{
time: 2,
info: [
{ id: 'A', x: 60, y: 10 },
{ id: 'B', x: 0, y: 50 },
{ id: 'C', x: 10, y: 50 },
{ id: 'D', x: 30, y: 60 },
],
},
];
var margin = { top: 30, right: 30, bottom: 30, left: 30 },
width = 300 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3
.select('#container')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Add X axis
var x = d3.scaleLinear().domain([0, 100]).range([0, width]);
svg
.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear().domain([0, 100]).range([height, 0]);
svg.append('g').call(d3.axisLeft(y));
function runTheSimulation() {
var chainedTransition,
prevTime = 0,
elapsedTime = 0;
// create a null selection which reprensents each timepiont
svg
.selectAll(null)
.data(data)
.enter()
.each(function(d,i){
// for each timepoint, update the data
points = svg.selectAll('circle')
.data(d.info, (d) => d.id);
// handle enter to create circles first time through
points
.enter()
.append('circle')
.attr('r', 8)
.attr('fill', 'white')
.attr('stroke', 'black')
.attr('cx', d => x(d.x))
.attr('cy', d => y(d.y));
// calculate how long we'll transition
// between this timepoint and last
let transTime = (d.time - prevTime) * 1000;
// chain the transition for all data updates
chainedTransition = points
.transition()
.duration(transTime)
.delay(elapsedTime)
.ease(d3.easeLinear)
.attr('cx', d => x(d.x))
.attr('cy', d => y(d.y));
// rememeber this time
prevTime = d.time;
// and elpased time for chaining
elapsedTime += transTime;
});
}
</script>
</body>Это фантастика, спасибо! Формат очень чистый и простой для понимания. Я собираюсь изучить каждую функцию, я думаю, что это большой пробел в моих знаниях D3.
Мне потребовалась секунда, чтобы увидеть, как вы обновляете данные, очень умное использование «вложенного» выбора для представления временных точек. Определенно стоит плюс один; Я почти перестал печатать свой ответ в тот момент.
@AndrewReid, моим первым побуждением было изменить порядок данных, как ты; это гораздо более подходящий способ представить это. Я воспринял это как вызов оставить его в покое и все же найти d3-подобный способ работы с ним.
Я считаю, что вам будет намного проще, если вы реструктурируете свои данные. Вместо массива данных с кадрами, содержащими положение каждого узла, попробуйте массив данных, содержащий каждый узел с течением времени, например:
var data = [ {"id":"A", frames: [{time:0, x: 1},{time:1,x: 2}]}, ...
Это позволяет вам привязывать данные один раз, переходить к каждому элементу отдельно (запуская конечное событие без необходимости подсчитывать, сколько переходов произошло, поскольку вы не ждете завершения пакета), и вы даже можете сохранить текущий кадр в датум, а не отслеживать его отдельно.
Я использовал следующую структуру:
Который я получил, запустив ваш код через базовое транспонирование, расположенное во фрагменте ниже.
Каждый элемент в массиве данных представляет один элемент в DOM — в соответствии с идиомой D3. Индекс текущего отрисовываемого кадра хранится в d.currentFrame, а все кадры хранятся в d.frames.
В основном ваша функциональность перехода будет довольно простой:
function transition(d) {
// Is there another frame?
if (d.currentFrame == d.frames.length-1 ) return; // don't keep going if there is no more data.
// Change in time
var dt = -d.frames[d.currentFrame++].time + d.frames[d.currentFrame].time;
// Do the transition:
d3.select(this)
.transition()
.duration(dt*1000)
.attr('cx', function(d) {return x(d.frames[d.currentFrame].x)})
.attr('cy', function(d) {return y(d.frames[d.currentFrame].y)})
.on("end", transition) // and repeat for this element
}
Мы можем запустить симуляцию запуска, сбросив текущий кадр и запустив переход:
function runTheSimulation() {
svg.selectAll("circle")
.each(function(d) { d.currentFrame = 0; })
.attr('cx', function(d) {return x(d.frames[d.currentFrame].x)})
.attr('cy', function(d) {return y(d.frames[d.currentFrame].y)})
.each(transition);
}
Каждая точка данных в этом случае на самом деле не должна иметь одни и те же шаги, ни по количеству, ни по значениям, что, как я полагаю, соответствует идиоме D3: каждый элемент данных является отдельным и независимым от обработки других: в этом нет необходимости. дождаться завершения пакета переходов или использовать периоды времени других.
Собираем вместе:
var data = [{"time": 0, "info":[{"id": "A", "x": 10, "y": 20},
{"id": "B", "x": 40, "y": 90},
{"id": "C", "x": 10, "y": 90},
{"id": "D", "x": 80, "y": 70}]},
{"time": 0.5, "info":[{"id": "A", "x": 20, "y": 30},
{"id": "B", "x": 60, "y": 70},
{"id": "C", "x": 100, "y": 10},
{"id": "D", "x": 10, "y": 32}]},
{"time": 1, "info":[{"id": "A", "x": 50, "y": 60},
{"id": "B", "x": 80, "y": 50},
{"id": "C", "x": 80, "y": 10},
{"id": "D", "x": 50, "y": 50}]},
{"time": 1.5, "info":[{"id": "A", "x": 40, "y": 50},
{"id": "B", "x": 100, "y": 30},
{"id": "C", "x": 80, "y": 90},
{"id": "D", "x": 80, "y": 40}]},
{"time": 2, "info":[{"id": "A", "x": 60, "y": 10},
{"id": "B", "x": 0, "y": 50},
{"id": "C", "x": 10, "y": 50},
{"id": "D", "x": 30, "y": 60}]}];
// Manipulate array:
var newData = data[0].info.map((_, i) => { return {currentFrame: 0, id: data[0].info[i].id ,frames:data.map((row,t) => { return { x: row.info[i].x, y: row.info[i].y, time: data[t].time }}) } });
var margin = {top: 30, right: 30, bottom: 30, left: 30},
width = 300 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#container")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Add X axis
var x = d3.scaleLinear()
.domain([0, 100])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 100])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
var points = svg.selectAll('circle')
.data(newData)
.enter()
.append('circle')
.attr('cx', function(d) {return x(d.frames[d.currentFrame].x)})
.attr('cy', function(d) {return y(d.frames[d.currentFrame].y)})
.attr('r', 8)
.attr('fill', 'white')
.attr('stroke', 'black')
function runTheSimulation() {
svg.selectAll("circle")
.each(function(d) { d.currentFrame = 0; })
.attr('cx', function(d) {return x(d.frames[d.currentFrame].x)})
.attr('cy', function(d) {return y(d.frames[d.currentFrame].y)})
.each(transition);
}
function transition(d) {
// Is there another frame?
if (d.currentFrame == d.frames.length-1 ) return; // don't keep going if there is no more data.
// Change in time
var dt = -d.frames[d.currentFrame++].time + d.frames[d.currentFrame].time;
// Do the transition:
d3.select(this)
.transition()
.duration(dt*1000)
.attr('cx', function(d) {return x(d.frames[d.currentFrame].x)})
.attr('cy', function(d) {return y(d.frames[d.currentFrame].y)})
.on("end", transition)
} body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}<head>
<!-- Bootstrap CSS -->
<link rel = "stylesheet" href = "http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<!-- jQuery -->
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- Bootstrap JavaScript -->
<script src = "http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<!-- Load d3.js -->
<script src = "https://d3js.org/d3.v4.js"></script>
</head>
<body>
<button onclick = "runTheSimulation()">RUN THE SIMULATION</button>
<div id = "container"></div>
</body>
Мне трудно понять, как вы хотите, чтобы это произошло. Например, хотите ли вы, чтобы точка А начиналась с
"x": 10, "y": 20, затем двигалась к"x": 50, "y": 60, затем к"x": 40, "y": 50и затем к"x": 60, "y": 10; с переходом 0,5 с между каждым?