Статьи

Сексуальные анимированные спирографы в 35 слотах d3.js


Вы, наверное, помните спирографы как детские игрушки с юности.
У меня был простой набор, который представлял собой просто набор пластиковых звездочек с отверстиями для карандашей.

Бесконечные забавы, когда мне было два или три года. Я думаю … Я не очень хорошо помню с того времени, но я помню, как эти штуковины и мне нравилось играть с ними. Одно из моих самых ранних воспоминаний!

Прошлой ночью я делал анимационный пример для книги d3.js, которую я пишу, и у меня возникла идея постепенно рисовать круто выглядящие параметрические уравнения. Мало ли я знаю, это на самом деле спирографы! Узнал что-то, когда я учился чему-то.

Если не считать моего провала в общих знаниях, то можно анимировать рисование параметрических уравнений всего несколькими строками кода d3.js. То, что я собираюсь показать вам, имеет довольно большую проблему, но дает отличные результаты «в лаборатории». Попробуйте угадать, в чем проблема.

Анимационные спирографы

После нескольких шагов

После нескольких шагов

Мы сделаем таймер анимации и для каждого тика будем рисовать новый шаг спирографа. После того, как спирограф закончен, мы остановим таймер. Чтобы сделать анимацию более интересной для просмотра, мы также подделаем кисть, летящую вокруг (просто черная точка).

Мы начнем с некоторого базового HTML:

<!DOCTYPE html>
<title></title>
<link href="../bootstrap/css/bootstrap.min.css" rel="stylesheet">
 
<div id="graph"></div>
 
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="timers.js"></script>

Затем мы запрыгиваем в JavaScript, чтобы конкретизировать фактический код.

var width = 600,
    height = 600,
    svg = d3.select('#graph')
        .append('svg')
        .attr({width: width,
               height: height});

Я нашел параметрическую функцию в статье в Википедии о параметрических уравнениях. Мы дадим ему простой параметр, рассчитанный по таймеру анимации, и он вернет двумерную позицию.

var position = function (t) {
    var a = 80, b = 1, c = 1, d = 80;
 
    return {x: Math.cos(a*t) - Math.pow(Math.cos(b*t), 3),
            y: Math.sin(c*t) - Math.pow(Math.sin(d*t), 3)};
};

Изменение параметров a , b , c и d изменит окончательную форму.

Далее мы собираемся определить некоторые масштабы, чтобы помочь нам переводить между математическим пространством и нашим пространством рисования.

var t_scale = d3.scale.linear().domain([500, 30000]).range([0, 2*Math.PI]),
    x = d3.scale.linear().domain([-2, 2]).range([100, width-100]),
    y = d3.scale.linear().domain([-2, 2]).range([height-100, 100]);
 
var brush = svg.append('circle')
        .attr({r: 4}),
    previous = position(0);

T_scale собирается перевести время в параметр, х и у вычислить правильные позиции на финальном изображении , используя координаты , возвращаемые функцией положения.

We also put a simple circle on the image – this will represent the brush – and we need to take note of the previous position so we can draw lines between our current and previous state.

var step = function (time) {
    if (time > t_scale.domain()[1]) {
        return true;
    }
 
    var t = t_scale(time),
        pos = position(t);
 
    brush.attr({cx: x(pos.x),
                cy: y(pos.y)});
    svg.append('line')
        .attr({x1: x(previous.x),
               y1: y(previous.y),
               x2: x(pos.x),
               y2: y(pos.y),
               stroke: 'steelblue',
               'stroke-width': 1.3});
 
    previous = pos;
};

This is our step function. It draws every consecutive step of the animation by moving the brush and putting a line between the current and previous position. The animation will stop when this function returns true so we make sure the time parameter doesn’t go beyond t_scales‘s domain.

Finally, we simply start the timer.

var timer = d3.timer(step, 500);

The timer will start running after 500 milliseconds and repeatedly call the step function until it returns true.

You can check the animation out via the magic of github pages. The final spirograph looks like this:

Параметрическое уравнение визуализируется

A parametric equation visualised

Figured out the problem yet?

The problem with this approach is that I’m using the animation timer itself as a parameter to the function, which means point density depends on how long you’re willing to let the animation run. It will always draw the complete function because of how d3 scales work, but it might look very approximate. Think squares for circles approximate.

Another problem is that using a slower computer, or doing anything that lags the CPU even a little bit while the animation is running will ruin the final picture.

For instance, this is what happens when I switch desktops around while the browser is drawing.

Глицкий спирограф

Glitchy spirograph