如何在使用 calcMode="linear" 时在 chrome 上模拟 firefox 动画行为?

问题描述 投票:0回答:1

我有两张图表,第一个绘制了两个函数:捕食者和猎物。每个都包含一个带有沿着函数路径的 animateMotion 的圆。

另一张图是函数生成的相位曲线。还有一个带有 animateMotion 的圆圈。

第一个图表上的标记相对于 X 轴应具有恒定的速度。第二张图上的标记应沿着第一张图上的 Y 值。

在 Firefox 上,使用

calcMode="linear"
可以正常工作: firefox 线性动画.

在 chrome 上,

calcMode="linear"
的行为与
calcMode="paced"
相同,因此速度沿曲线恒定: chrome“线性”动画

包括一段“最小”的代码,我保持轴打开,这样更容易理解发生了什么。

<script src="https://d3js.org/d3.v4.js"></script>

<div style="min-width: 100px; max-width: 450px; width:100%">
    <div id="prey_predator_chart" style="width:100%;">
    </div>
    <div id="prey_predator_phase_chart" style="width:100%;">
    </div>
</div>
<script>
let prey_predator = {
    prey_color: "blue",
    predator_color: "green",
    phase_curve_color: "red",
    draw_graph: function() {
        // set the dimensions and margins of the graph
        var margin = {top: 0, right: 40, bottom: 40, left: 40},
            width = 450 - margin.left - margin.right,
            height = 400 - margin.top - margin.bottom;

        var total_width = width + margin.left + margin.right;
        var total_height = height + margin.top + margin.bottom;

        // line graph
        var svg_pop = d3.select("#prey_predator_chart")
            .append("svg")
                .attr("viewBox", "0 0 " + total_width + " " + total_height)
            .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        // phase graph
        svg_pop_phase = d3.select("#prey_predator_phase_chart")
            .append("svg")
                .attr("viewBox", "0 0 " + total_width + " " + total_height)
            .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


        var xDomain = [0,40];
        var yDomain = [0,30];

        var prey_c = {i_density: 10, growth: 1.1, death: 0.4}
        var predator_c = {i_density: 10, growth: 0.1, death: 0.4}
        
        var yScale = d3.scaleLinear().range([height,0]).domain(yDomain);
        var xScale = d3.scaleLinear().range([0,width]).domain(xDomain);

        var yScale_phase = d3.scaleLinear().range([height,0]).domain(yDomain);
        var xScale_phase = d3.scaleLinear().range([0,width]).domain(yDomain);

        var eps = 0.0005
        var x_space = d3.range(xDomain[0], xDomain[1], eps)

        var prey_growth = function(current_prey, current_predator, eps) {
            return prey_c.growth*eps*current_prey - prey_c.death*current_prey*eps*current_predator
        }
        var predator_growth = function(current_prey, current_predator, eps) {
            return predator_c.growth*current_prey*eps*current_predator - predator_c.death*eps*current_predator
        }

        var preys = []
        var predators = []
        preys = [prey_c.i_density]
        predators = [predator_c.i_density]

        x_space.forEach((_, i) => {
            preys.push(preys[i] + prey_growth(preys[i], predators[i], eps))
            predators.push(predators[i] + predator_growth(preys[i], predators[i], eps))
        });

        var c_preys = d3.line()
                .x(function(i) { return xScale(x_space[i]) })
                .y(function(i) { return yScale(preys[i]) })

        var c_predators = d3.line()
            .x(function(i) { return xScale(x_space[i]) })
            .y(function(i) { return yScale(predators[i]) })

        var c_phase = d3.line()
            .x(function(i) {return xScale_phase(preys[i])})
            .y(function(i) {return yScale_phase(predators[i])})

        predators_curve = svg_pop.append('path')
            .attr('stroke', this.predator_color)
            .attr('fill', 'none')
            .attr('stroke-width', 2).attr('d', c_predators(d3.range(0, x_space.length, 1)));

        predators_marker = svg_pop.append('circle')
            .attr('r', 3)
            .attr('stroke', this.predator_color)
        predators_marker.append('animateMotion')
            .attr('repeatCount', 'indefinite')
            .attr('fill', 'freeze')
            .attr('calcMode','linear')
            .attr('dur', '10s')
            .attr('path', c_predators(d3.range(0, x_space.length, 1)));

        preys_curve = svg_pop.append('path')
            .attr('stroke', this.prey_color)
            .attr('fill', 'none')
            .attr('stroke-width', 1).attr('d', c_preys(d3.range(0, x_space.length, 1)));
            
        preys_marker = svg_pop.append('circle')
            .attr('r', 3)
            .attr('stroke', this.prey_color)
        preys_marker.append('animateMotion')
            .attr('repeatCount', 'indefinite')
            .attr('fill', 'freeze')
            .attr('calcMode','linear')
            .attr('dur', '10s')
            .attr('path', c_preys(d3.range(0, x_space.length, 1)));

        phase_curve = svg_pop_phase.append('path')
            .attr('stroke', this.phase_curve_color)
            .attr('stroke-width', 1)
            .attr('fill', 'none').attr('d', c_phase(d3.range(0, x_space.length, 1)));
        phase_marker = svg_pop_phase.append('circle')
            .attr('r', 3)
            .attr('stroke', this.phase_curve_color)
        phase_marker.append('animateMotion')
            .attr('repeatCount', 'indefinite')
            .attr('fill', 'freeze')
            .attr('calcMode','linear')
            .attr('dur', '10s')
            .attr('path', c_phase(d3.range(0, x_space.length, 1)));

        bottomAxis = svg_pop.append("g").attr("transform", "translate(0," + height + ")")
                .call(d3.axisBottom(xScale));
        bottomAxis.append("text")
                .attr("class", "axis-title")
                .attr("y", 25)
                .attr("dy", ".71em")
                .attr("x", (width+margin.left)/2)
                .style("text-anchor", "end")
                .attr("fill", "black")
                .text("Tiempo");

        leftAxis = svg_pop.append("g")
                .call(d3.axisLeft(yScale));
        leftAxis.append("text")
                .attr("class", "axis-title")
                .attr("transform", "rotate(-90)")
                .attr("y", -30)
                .attr("dy", ".71em")
                .attr("x", -(height-margin.bottom)/2)
                .style("text-anchor", "end")
                .attr("fill", "black")
                .text("Densidad");

        bottomAxis_phase = svg_pop_phase.append("g").attr("transform", "translate(0," + height + ")")
                .call(d3.axisBottom(xScale_phase));
        bottomAxis_phase.append("text")
                .attr("class", "axis-title")
                .attr("y", 25)
                .attr("dy", ".71em")
                .attr("x", (width+margin.left)/2)
                .style("text-anchor", "end")
                .attr("fill", "black")
                .text("Densidad presa");

        leftAxis_phase = svg_pop_phase.append("g")
                .call(d3.axisLeft(yScale_phase));
        leftAxis_phase.append("text")
                .attr("class", "axis-title")
                .attr("transform", "rotate(-90)")
                .attr("y", -35)
                .attr("dy", ".71em")
                .attr("x", -(height-margin.bottom)/2)
                .style("text-anchor", "end")
                .attr("fill", "black")
                .text("Densidad predador");
        diag_phase = svg_pop_phase.append('line')
            .attr('stroke', 'black')
            .attr('stroke-width', 1)
            .attr('stroke-dasharray', '5,5')
            .attr('x1', xScale_phase(yDomain[0]))
            .attr('y1', yScale_phase(yDomain[0]))
            .attr('x2', xScale_phase(yDomain[1]))
            .attr('y2', yScale_phase(yDomain[1]))
    }
}
prey_predator.draw_graph();
</script>

使用

keyTimes
keyPoints
进行了手动同步,这确实适用于第一个图表,但我不知道如何计算第二个图表的值。 考虑到 Firefox 的工作方式,这也有点麻烦。

        get_curve_points = function(curve, n) {
            var points = []
            for (var i = 0; i < n; i++) {
                points.push(curve.node().getPointAtLength(i * curve.node().getTotalLength() / n))
            }
            if (points.length > n) {
                points.pop()
            }
            return points;
        },
        n = 50
        points = get_curve_points(preys_curve, n)
        xs = points.map(p => p.x)
        var min_x = d3.min(xs);
        var max_x = d3.max(xs);
        xs = xs.map(x => (x - min_x) / (max_x - min_x));
        keyTimes = xs.reduce((acc, x) => acc + x + ';', '') + "1"
        keyPoints = d3.range(0,1,1/n).reduce((acc, x) => acc + x + ';', '') + "1"
        preys_marker.append('animateMotion')
            .attr('repeatCount', 'indefinite')
            .attr('fill', 'freeze')
            .attr('calcMode','linear')
            .attr('dur', '10s')
            .attr('keyTimes', keyTimes)
            .attr('keyPoints', keyPoints)
            .attr('path', c_preys(d3.range(0, x_space.length, 1)));


        predators_curve = svg_pop.append('path')
            .attr('stroke', this.predator_color)
            .attr('fill', 'none')
            .attr('stroke-width', 2).attr('d', c_predators(d3.range(0, x_space.length, 1)));

        predators_marker = svg_pop.append('circle')
            .attr('r', 3)
            .attr('stroke', this.predator_color)

        points = get_curve_points(predators_curve, n)
        xs = points.map(p => p.x)
        var min_x = d3.min(xs);
        var max_x = d3.max(xs);
        xs = xs.map(x => (x - min_x) / (max_x - min_x));
        keyTimes = xs.reduce((acc, x) => acc + x + ';', '') + "1"
        keyPoints = d3.range(0,1,1/n).reduce((acc, x) => acc + x + ';', '') + "1"
        predators_marker.append('animateMotion')
            .attr('repeatCount', 'indefinite')
            .attr('fill', 'freeze')
            .attr('calcMode','linear')
            .attr('dur', '10s')
            .attr('keyTimes', keyTimes)
            .attr('keyPoints', keyPoints)
            .attr('path', c_predators(d3.range(0, x_space.length, 1)));


        phase_curve = svg_pop_phase.append('path')
            .attr('stroke', this.phase_curve_color)
            .attr('stroke-width', 1)
            .attr('fill', 'none').attr('d', c_phase(d3.range(0, x_space.length, 1)));
        phase_marker = svg_pop_phase.append('circle')
            .attr('r', 3)
            .attr('stroke', this.phase_curve_color)
        

        phase_marker.append('animateMotion')
            .attr('repeatCount', 'indefinite')
            .attr('fill', 'freeze')
            .attr('calcMode','linear')
            .attr('dur', '10s')

javascript math d3.js svg-animate
1个回答
0
投票

修改了评论区Mark的解决方案,根据当前时间生成动画。

这样我就可以通过相对于 x 轴缩放当前时间来同时同步每个图表。

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>

<div style="min-width: 100px; max-width: 450px; width:100%">
    <div id="prey_predator_chart" style="width:100%;">
    </div>
    <div id="prey_predator_phase_chart" style="width:100%;">
    </div>
</div>
<script>
  let prey_predator = {
    prey_color: 'blue',
    predator_color: 'green',
    phase_curve_color: 'red',
    draw_graph: function () {
        // set the dimensions and margins of the graph
        var margin = { top: 0, right: 40, bottom: 40, left: 40 },
        width = 450 - margin.left - margin.right,
        height = 400 - margin.top - margin.bottom;

        var total_width = width + margin.left + margin.right;
        var total_height = height + margin.top + margin.bottom;

        // line graph
        var svg_pop = d3
        .select('#prey_predator_chart')
        .append('svg')
        .attr('viewBox', '0 0 ' + total_width + ' ' + total_height)
        .append('g')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

        // phase graph
        svg_pop_phase = d3
        .select('#prey_predator_phase_chart')
        .append('svg')
        .attr('viewBox', '0 0 ' + total_width + ' ' + total_height)
        .append('g')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

        var xDomain = [0, 40];
        var yDomain = [0, 30];

        var prey_c = { i_density: 10, growth: 1.1, death: 0.4 };
        var predator_c = { i_density: 10, growth: 0.1, death: 0.4 };

        var yScale = d3.scaleLinear().range([height, 0]).domain(yDomain);
        var xScale = d3.scaleLinear().range([0, width]).domain(xDomain);

        var yScale_phase = d3.scaleLinear().range([height, 0]).domain(yDomain);
        var xScale_phase = d3.scaleLinear().range([0, width]).domain(yDomain);

        var eps = 0.0005;
        var x_space = d3.range(xDomain[0], xDomain[1], eps);

        var prey_growth = function (current_prey, current_predator, eps) {
        return (
            prey_c.growth * eps * current_prey -
            prey_c.death * current_prey * eps * current_predator
        );
        };
        var predator_growth = function (current_prey, current_predator, eps) {
        return (
            predator_c.growth * current_prey * eps * current_predator -
            predator_c.death * eps * current_predator
        );
        };

        var preys = [];
        var predators = [];
        preys = [prey_c.i_density];
        predators = [predator_c.i_density];

        x_space.forEach((_, i) => {
        preys.push(preys[i] + prey_growth(preys[i], predators[i], eps));
        predators.push(
            predators[i] + predator_growth(preys[i], predators[i], eps)
        );
        });

        var c_preys = d3
        .line()
        .x(function (i) {
            return xScale(x_space[i]);
        })
        .y(function (i) {
            return yScale(preys[i]);
        });

        var c_predators = d3
        .line()
        .x(function (i) {
            return xScale(x_space[i]);
        })
        .y(function (i) {
            return yScale(predators[i]);
        });

        var c_phase = d3
        .line()
        .x(function (i) {
            return xScale_phase(preys[i]);
        })
        .y(function (i) {
            return yScale_phase(predators[i]);
        });

        predators_curve = svg_pop
        .append('path')
        .attr('stroke', this.predator_color)
        .attr('fill', 'none')
        .attr('stroke-width', 2)
        .attr('d', c_predators(d3.range(0, x_space.length, 1)));

        predators_marker = svg_pop
        .append('circle')
        .attr('r', 3)
        .attr('stroke', this.predator_color);

        preys_curve = svg_pop
        .append('path')
        .attr('stroke', this.prey_color)
        .attr('fill', 'none')
        .attr('stroke-width', 1)
        .attr('d', c_preys(d3.range(0, x_space.length, 1)));

        preys_marker = svg_pop
        .append('circle')
        .attr('r', 3)
        .attr('stroke', this.prey_color);

        phase_curve = svg_pop_phase
        .append('path')
        .attr('stroke', this.phase_curve_color)
        .attr('stroke-width', 1)
        .attr('fill', 'none')
        .attr('d', c_phase(d3.range(0, x_space.length, 1)));
        phase_marker = svg_pop_phase
            .append('circle')
            .attr('r', 3)
            .attr('stroke', this.phase_curve_color);
            
        const dur = 10000
        svg_pop
        .transition()
        .ease(d3.easeLinear)
        .duration(dur)
        .tween(null, function () {
            return function (t) {
                const i = Math.round(t * x_space.length - 1);
                const x = xScale(x_space[i]);
                
                predators_marker.attr('cx', x);
                predators_marker.attr('cy', yScale(predators[i]));
                preys_marker.attr('cx', x);
                preys_marker.attr('cy', yScale(preys[i]));

                phase_marker.attr('cx', xScale_phase(preys[i]));
                phase_marker.attr('cy', yScale_phase(predators[i]));
            };
        });

      bottomAxis = svg_pop
        .append('g')
        .attr('transform', 'translate(0,' + height + ')')
        .call(d3.axisBottom(xScale));
      bottomAxis
        .append('text')
        .attr('class', 'axis-title')
        .attr('y', 25)
        .attr('dy', '.71em')
        .attr('x', (width + margin.left) / 2)
        .style('text-anchor', 'end')
        .attr('fill', 'black')
        .text('Tiempo');

      leftAxis = svg_pop.append('g').call(d3.axisLeft(yScale));
      leftAxis
        .append('text')
        .attr('class', 'axis-title')
        .attr('transform', 'rotate(-90)')
        .attr('y', -30)
        .attr('dy', '.71em')
        .attr('x', -(height - margin.bottom) / 2)
        .style('text-anchor', 'end')
        .attr('fill', 'black')
        .text('Densidad');

      bottomAxis_phase = svg_pop_phase
        .append('g')
        .attr('transform', 'translate(0,' + height + ')')
        .call(d3.axisBottom(xScale_phase));
      bottomAxis_phase
        .append('text')
        .attr('class', 'axis-title')
        .attr('y', 25)
        .attr('dy', '.71em')
        .attr('x', (width + margin.left) / 2)
        .style('text-anchor', 'end')
        .attr('fill', 'black')
        .text('Densidad presa');

      leftAxis_phase = svg_pop_phase
        .append('g')
        .call(d3.axisLeft(yScale_phase));
      leftAxis_phase
        .append('text')
        .attr('class', 'axis-title')
        .attr('transform', 'rotate(-90)')
        .attr('y', -35)
        .attr('dy', '.71em')
        .attr('x', -(height - margin.bottom) / 2)
        .style('text-anchor', 'end')
        .attr('fill', 'black')
        .text('Densidad predador');
      diag_phase = svg_pop_phase
        .append('line')
        .attr('stroke', 'black')
        .attr('stroke-width', 1)
        .attr('stroke-dasharray', '5,5')
        .attr('x1', xScale_phase(yDomain[0]))
        .attr('y1', yScale_phase(yDomain[0]))
        .attr('x2', xScale_phase(yDomain[1]))
        .attr('y2', yScale_phase(yDomain[1]));
    },
  };
  prey_predator.draw_graph();
</script>

© www.soinside.com 2019 - 2024. All rights reserved.