TL; DR:我写了基于情节的数学钟摆javascript模拟。它工作得很慢。我正在寻找有关如何优化它的想法。目前正在尝试"裸露" d3.js并且从SVG坐标和我自己的逻辑坐标之间的坐标转换问题中苦苦挣扎。
我正在编写关于常微分方程的网络教科书,并希望包括交互式模拟和数学摆的可视化。可视化应包含摆锤本身,其潜在能量图和全能量等值线图。然后用户可以通过单击能量等值线图选择初始条件,然后动画应该开始显示点在相空间中的移动方式。
我写了一个这样模拟的例子:
https://jsfiddle.net/ischurov/p1krqnt6/
它以情节为基础。我创建了三个轴并放置了必要的图表。表示系统当前状态的点也是图中的图形(即只有一个点的散点图)。
动画的工作原理如下:我获取相空间中点的当前坐标,一段时间后计算该点的新位置,然后根据这个新位置更新我的图形。相应的代码:
var div = document.getElementById('myDiv');
function updateState(phi, v) {
var update = {x: [[phi], [phi], [0, Math.sin(phi)]], y: [[v],
[PotentialEnergy(phi)], [0, -Math.cos(phi)]]};
Plotly.restyle(div, update, [phaseDotIndex, 3, 4]);
}
myPlot.on('plotly_click', function(data){
if(data.points[0].data.type == 'contour'){
updateState(data.points[0].x, data.points[0].y);
}
});
var animate = null;
$('.animate_button').click(function(){
var div = document.getElementById('myDiv');
if(animate === null) {
var phi = div.data[phaseDotIndex].x[0],
v = div.data[phaseDotIndex].y[0],
E = FullEnergy(phi, v);
animate = setInterval(function() {
var phi = div.data[phaseDotIndex].x[0],
v = div.data[phaseDotIndex].y[0],
step = 0.1, newphi, newv, update;
newphi = phi + v * step;
newv = v + step * Force(phi);
/* skip some tweaks here */
updateState(phi, v);
},
100)
}
else
{
clearInterval(animate);
animate = null;
}
}
此代码几乎按预期工作,但实际上很慢且不流畅 - 至少在Firefox下(如果我减少更新间隔,它会更糟糕)。
我正在寻找优化方法。
我认为性能问题是由于剧情的更新过程:为了移动一个点,它必须重新计算整个画面而且速度很慢。所以我正在寻找以不同方式做到这一点的方法。
有什么想法吗?
我找了一些直接的d3.js方法,可以更快。我在这里看到以下步骤:
要继续执行第1步,我可以使用第三方d3.js库(如conrec)获取等高线图和/或优秀的maurizzzio's function plot,或者甚至可以使用情节本身(但我不会去使用plotly来更新图表)。第2步似乎是可行的,但我还没有尝试过。现在最困难的是第3步和第4步,因为我不了解如何将SVG坐标转换为我的图形坐标(用某些库绘制),反之亦然。
或许有更简单的方法可以做到这一点?
答案 0 :(得分:3)
我是构建于d3之上的function plot的作者,幸运的是d3具有在d3-scale中执行映射的方法,所以假设你有一个width x height
维的画布应该线性地映射到2D欧几里德空间中的矩形[xMin, yMin] x [xMax, yMax]
,您需要创建两个线性比例
var xScale = d3.scale.linear()
.domain([xMin, xMax])
.range([0, width])
var yScale = d3.scale.linear()
.domain([yMin, yMax])
.range([height, 0])
请注意,在SVG中,y轴被翻转,因此yScale
范围也被翻转,然后任何2D欧氏点转换为SVG坐标,如下所示
var xCanvas = xScale(point.x)
var yCanvas = yScale(point.y)
逆变换由
给出var xLogical = xScale.invert(point.x)
var xLogical = yScale.invert(point.y)
我使用上述内容写给您的问题的可能解决方案是
var instance = functionPlot({
target: '#demo',
disableZoom: true,
data: [{
fn: 'sin(10*(-cos(x) + y^2/2-1))',
fnType: 'implicit'
}]
})
var xScale = instance.meta.xScale
var yScale = instance.meta.yScale
var canvas = instance.canvas
var circle = canvas.append('circle')
.attr("r", 5)
.style("fill", "purple")
var start = Date.now()
function animate() {
// move the point along the circle of radius 1
var t = (Date.now() - start) * 0.003
var xLogical = Math.cos(t)
var yLogical = Math.sin(t)
var xCanvas = xScale(xLogical)
var yCanvas = yScale(yLogical)
circle
.attr('cx', xCanvas)
.attr('cy', yCanvas)
requestAnimationFrame(animate)
}
animate()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://maurizzzio.github.io/function-plot/js/function-plot.js"></script>
<div id="demo"></div>
答案 1 :(得分:0)
我意识到这并不是您想要的,但它确实证明您的代码可以紧凑并且仍然可以满足您的需求。
以下是一些代码,演示了一些动画摆动的数学,从我的一个javascript小部件中提取,但数学逻辑仍应在你自己的项目中使用。
创建一个图像对象 - pendulumSet,一个漂亮的圆球会做。
// variables set for the pendulum
var gravity = -0.0110808; // tuned to -0.110808 as it approximates a 1 second interval
var acceleration = 0.1; //0.1
var velocity = 0.18; //.18
var angle = 8; // 8 (.4 radians = 22.91 degrees)
创建间隔为0.01秒的计时器
把它放在计时器中:
acceleration = gravity * angle;
velocity += acceleration;
angle += velocity;
pendulumSet.rotation = angle +180; // rotation as per a widget engine
那是......
您可能必须使用以下内容来实现本机javascript中的旋转:
pendulumSet.style.transform = "rotate(90deg)"; // change the 90deg
上面的代码将以更加紧凑的方式模拟钟摆。这是1/100秒的图形动画和数学计算所以需要一些cpu,这是不可避免的。然而,代码紧凑,影响极小。根据引擎解释代码,CPU使用率约为。从2009年开始,2.5ghz core2duo的20-25%,更容易处理更现代,更快的cpu。在Firefox中运行类似的代码可能会明显变慢,因为根据我的经验,Firefox似乎执行类似的动画效率较低。你只需要尝试并看看。
代码取自此示例javascript小部件: A steampunk clock for the desktop
因此,您可以在操作中看到完全相同的代码。