强制布局拖动行为,缩放时有过渡但拖动时没有过渡
Force layout drag behaviour with transition on zoom but no transition on drag
我觉得这个问题很简单。我有这个站点供您演示我的意思:http://arda-maps.org/familytree/ 因此,如果您在屏幕上添加一些人,您可以 拖动 和 缩放 视图。
变焦完全没问题。变焦的持续时间很棒。但是我不喜欢拖动时的持续时间,并且想在那里禁用它。基本上这是代码:
g.transition().duration(450).attr("transform", "translate(" + zoombuttonTranslate + ")scale(" + zoombuttonScale + ")");
所以问题是如何在拖动事件中禁用 transition/duration?这甚至有可能吗?
第一阶段
我认为这很接近...
只需要验证它会很好地处理节点上的拖动行为。
策略
- 使用
d3.event.sourceEvent.type
检查mousemove
- 使用
d3.transform
增强当前变换状态
- transition translate and scale 用于鼠标滚轮事件,无 transition 用于鼠标按钮事件
工作示例
var width = 600, height = 200-16,
margin = {top: 25, right: 5, bottom: 5, left: 5},
w = width - margin.left - margin.right,
h = height - margin.top - margin.bottom,
zoom = d3.behavior.zoom().scaleExtent([0.4, 4])
.on("zoom", zoomed),
svg = d3.select("#viz").attr({width: width, height: height})
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom),
transText = svg.append("text")
.text("transform = translate ( margin.left , margin.top )")
.style("fill", "#5c5c5c")
.attr("dy", "-.35em")
surface = svg.append("rect")
.attr({width: w, height: h})
.style({"pointer-events": "all", fill: "#ccc", "stroke-width": 3, "stroke": "#fff"}),
surfaceText = svg.append("text")
.text("pointer-events: all")
.style("fill", "#5c5c5c")
.attr({"dy": "1em", "dx": ".2em"})
content = svg.append("g").attr("id", "content")
.attr("transform", "translate(0,0)"),
contentText = content.append("text")
.text("transform = translate ( d3.event.translate ) scale ( d3.event.scale )")
.style("fill", "#5c5c5c")
.attr({"dy": 50, "dx": 20})
content.selectAll("rect")
.data([[20,60],[140,60]])
.enter().append("rect")
.attr({height: 50, width: 50})
.style({"stroke-width": 3, "stroke": "#ccc"})
.each(function(d){
d3.select(this).attr({x: d[0], y: d[1]});
});
function zoomStart(){
}
function zoomed(){
return d3.event.sourceEvent.buttons ? zoomDrag.call(this) : zoomScale.call(this)
}
function zoomDrag(){
var t = d3.transform(content.attr("transform"));
t.translate = d3.event.translate;
content.attr("transform", t.toString());
}
function zoomScale(){
var t = d3.transform(content.attr("transform"));
t.translate = d3.event.translate; t.scale = d3.event.scale;
content.transition().duration(450).attr("transform", t.toString());
}
svg {
outline: 1px solid #282f51;
pointer-events: all;
}
g {
outline: 1px solid red;
shape-rednering: "geometricPrecision";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg id="viz"></svg>
第二阶段
合并 FDG
由于 FDG 必须在 canvas 容器内,因此有必要阻止节点级事件传播到 canvas。这是在 OP 代码中通过使用自定义拖动行为完成的,在 dragstart 上停止传播并添加一些 force.drag
行为(加上设置 d.fixed
= true ). This is great if you don't mind losing some of the
force.dragfeatures like sticking nodes on mouseover. This is nice for capturing small, energetic nodes though. So, in order to get the best of both worlds, you can hook the
force.drag` 行为。
策略
- 应用与第一阶段相同的原则,但对鼠标滚轮事件进行跨浏览器测试。
- 向节点添加标准
force.drag
- 挂钩
force.drag
以添加自定义行为
- 仅修复 shift-drag (或 shift-dragend)[= 上的节点78=]
- 对于触摸设备,如果在 dragstart
时触摸 > 1,也会修复节点
最后两点允许在需要时轻松释放固定节点。
force.drag勾
//hook force.drag behaviour
var stdDragStart = force.drag().on("dragstart.force");
force.drag()
.on("dragstart", function(d){
//prevent dragging on the nodes from dragging the canvas
d3.event.sourceEvent.stopPropagation();
stdDragStart.call(this, d);
});
工作示例
//debug panel/////////////////////////////////////////////////////////////////////////////
var alpha = d3.select("#alpha").text("waiting..."),
cog = d3.select("#wrapAlpha").insert("i", "#fdg").classed("fa fa-cog fa-spin", true).datum({instID: null}),
fdgInst = d3.select("#fdg");
elapsedTime = ElapsedTime("#panel", {margin: 0, padding: 0})
.message(function (id) {
return 'fps : ' + d3.format(" >8.3f")(1/this.aveLap())
});
elapsedTime.consoleOn = true;
alpha.log = function(e, instID) {
elapsedTime.mark().timestamp();
alpha.text(d3.format(" >8.4f")(e.alpha));
fdgInst.text("fdg instance: " + instID);
};
d3.select("#update").on("click", (function() {
var dataSet = false;
return function() {
//fdg.force.stop();
fdg(dataSets[(dataSet = !dataSet, +dataSet)])
}
})());
//////////////////////////////////////////////////////////////////////////////////////////
var dataSets = [{
"nodes" : [
{"name": "node1", "r": 10},
{"name": "node2", "r": 10},
{"name": "node3", "r": 30},
{"name": "node4", "r": 15}
],
"edges": [
{"source": 2, "target": 0},
{"source": 2, "target": 1},
{"source": 2, "target": 3}
]
},
{
"nodes":[
{"name": "node1", "r": 20},
{"name": "node2", "r": 10},
{"name": "node3", "r": 30},
{"name": "node4", "r": 15},
{"name": "node5", "r": 10},
{"name": "node6", "r": 10}
],
"edges":[
{"source": 2, "target": 0},
{"source": 2, "target": 1},
{"source": 2, "target": 3},
{"source": 2, "target": 4},
{"source": 2, "target": 5}
]
}
],
svg = SVG({width: 600, height: 200-34, margin: {top: 25, right: 5, bottom: 5, left: 5}}, "#viz"),
fdg = FDG(svg, alpha.log);
fdg(dataSets[0]);
function SVG (size, selector){
//delivers an svg background with zoom/drag context in the selector element
//if height or width is NaN, assume it is a valid length but ignore margin
var margin = size.margin || {top: 0, right: 0, bottom: 0, left: 0},
unitW = isNaN(size.width), unitH = isNaN(size.height),
w = unitW ? size.width : size.width - margin.left - margin.right,
h = unitH ? size.height : size.height - margin.top - margin.bottom,
zoomed = function(){return this},
zoom = d3.behavior.zoom().scaleExtent([0.4, 4])
.on("zoom", function(d, i, j){
zoomed.call(this, d, i, j);
}),
svg = d3.select(selector).selectAll("svg").data([["transform root"]]);
svg.enter().append("svg");
svg.attr({width: size.width, height: size.height});
var g = svg.selectAll("#zoom").data(id),
gEnter = g.enter().append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom)
.attr({class: "outline", id: "zoom"}),
zoomText = gEnter.append("text")
.text("transform = translate ( margin.left , margin.top )")
.style("fill", "#5c5c5c")
.attr("dy", "-.35em"),
surface = gEnter.append("rect")
.attr({width: w, height: h})
.style({"pointer-events": "all", fill: "#ccc", "stroke-width": 3, "stroke": "#fff"}),
surfaceText = gEnter.append("text")
.text("pointer-events: none")
.style("fill", "#5c5c5c")
.attr({"dy": "1em", "dx": ".2em"});
g.h = h;
g.w = w;
g.onZoom = function(cb){zoomed = cb;};
return g;
}
function FDG (svg, tickLog) {
var instID = Date.now();
force = d3.layout.force()
.size([svg.w, svg.h])
.charge(-1000)
.linkDistance(50)
.on("end", function(){
// manage dead instances of force
// only stop if this instance is the current owner
if(cog.datum().instID != instID) return true;
cog.classed("fa-spin", false);
elapsedTime.stop();
})
.on("start", function(){
// mark as active and brand the insID to establish ownership
cog.classed("fa-spin", true).datum().instID = instID;
elapsedTime.start();
});
function fdg(data) {
force
.nodes(data.nodes)
.links(data.edges)
.on("tick", (function(instID) {
return function(e) {
if(tickLog) tickLog.call(this, e, instID);
lines.attr("x1", function(d) {
return d.source.x;
}).attr("y1", function(d) {
return d.source.y;
}).attr("x2", function(d) {
return d.target.x;
}).attr("y2", function(d) {
return d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")"
});
}
})(instID))
.start();
svg.onZoom(zoomed);
hookDrag(force.drag(), "dragstart.force", function(d) {
// prevent dragging on the nodes from dragging the canvas
var e = d3.event.sourceEvent;
e.stopPropagation();
d.fixed = e.shiftKey || e.touches && (e.touches.length > 1);
});
hookDrag(force.drag(), "dragend.force", function(d) {
// prevent dragging on the nodes from dragging the canvas
var e = d3.event.sourceEvent;
d.fixed = e.shiftKey || d.fixed;
});
var content = svg.selectAll("g#fdg").data([data]);
content.enter().append("g").attr({"id": "fdg", class: "outline"});
var contentText = content.selectAll(".contentText")
.data(["transform = translate ( d3.event.translate ) scale ( d3.event.scale )"])
.enter().append("text").classed("contentText", true)
.text(id)
.style("fill", "#5c5c5c")
.attr({"dy": 20, "dx": 20});
var lines = content.selectAll(".links")
.data(linksData),
linesEnter = lines.enter()
.insert("line", d3.select("#nodes") ? "#nodes" : null)
.attr("class", "links")
.attr({stroke: "steelblue", "stroke-width": 3});
var nodes = content.selectAll("#nodes")
.data(nodesData),
nodesEnter = nodes.enter().append("g")
.attr("id", "nodes"),
node = nodes.selectAll(".node")
.data(id),
newNode = node.enter().append("g")
.attr("class", "node")
.call(force.drag),
circles = newNode.append("circle")
.attr({class: "content"})
.attr("r", function(d) {return d.r})
.style({"fill": "red", opacity: 0.8});
lines.exit().remove();
node.exit().remove();
function nodesData(d) {
return [d.nodes];
}
function linksData(d) {
return d.edges;
}
function hookDrag(target, event, hook) {
//hook force.drag behaviour
var stdDragStart = target.on(event);
target.on(event, function(d) {
hook.call(this, d);
stdDragStart.call(this, d);
});
}
function zoomed(){
var e = d3.event.sourceEvent,
isWheel = e && ((e.type == "mousewheel") || (e.type == "wheel"));
force.alpha(0.01);
return isWheel ? zoomWheel.call(this) : zoomInst.call(this)
}
function zoomInst(){
var t = d3.transform(content.attr("transform"));
t.translate = d3.event.translate; t.scale = d3.event.scale;
content.attr("transform", t.toString());
}
function zoomWheel(){
var t = d3.transform(content.attr("transform"));
t.translate = d3.event.translate; t.scale = d3.event.scale;
content.transition().duration(450).attr("transform", t.toString());
}
fdg.force = force;
};
return fdg
}
function id(d){return d;}
svg {
outline: 1px solid #282f51;
pointer-events: all;
overflow: visible;
}
g.outline {
outline: 1px solid red;
}
#panel div {
display: inline-block;
margin: 0 .25em 3px 0;
}
#panel div div {
white-space: pre;
}
div#inputDiv {
white-space: normal;
display: inline-block;
}
.node {
cursor: default;
}
text {
font-size: 8px;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/40e550b1507cca31b0bb/raw/b83ceb0f8b4a2b6256f079f5887fc5243baedd4f/elapsed%2520time%25201.0.js"></script>
<div id="panel">
<div id="inputDiv">
<input id="update" type="button" value="update">
</div>
<div id="wrapAlpha">alpha:
<div id="alpha"></div>
</div>
<div id="fdg">
</div>
<div id="viz"></div>
我觉得这个问题很简单。我有这个站点供您演示我的意思:http://arda-maps.org/familytree/ 因此,如果您在屏幕上添加一些人,您可以 拖动 和 缩放 视图。
变焦完全没问题。变焦的持续时间很棒。但是我不喜欢拖动时的持续时间,并且想在那里禁用它。基本上这是代码:
g.transition().duration(450).attr("transform", "translate(" + zoombuttonTranslate + ")scale(" + zoombuttonScale + ")");
所以问题是如何在拖动事件中禁用 transition/duration?这甚至有可能吗?
第一阶段
我认为这很接近... 只需要验证它会很好地处理节点上的拖动行为。
策略
- 使用
d3.event.sourceEvent.type
检查mousemove - 使用
d3.transform
增强当前变换状态
- transition translate and scale 用于鼠标滚轮事件,无 transition 用于鼠标按钮事件
工作示例
var width = 600, height = 200-16,
margin = {top: 25, right: 5, bottom: 5, left: 5},
w = width - margin.left - margin.right,
h = height - margin.top - margin.bottom,
zoom = d3.behavior.zoom().scaleExtent([0.4, 4])
.on("zoom", zoomed),
svg = d3.select("#viz").attr({width: width, height: height})
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom),
transText = svg.append("text")
.text("transform = translate ( margin.left , margin.top )")
.style("fill", "#5c5c5c")
.attr("dy", "-.35em")
surface = svg.append("rect")
.attr({width: w, height: h})
.style({"pointer-events": "all", fill: "#ccc", "stroke-width": 3, "stroke": "#fff"}),
surfaceText = svg.append("text")
.text("pointer-events: all")
.style("fill", "#5c5c5c")
.attr({"dy": "1em", "dx": ".2em"})
content = svg.append("g").attr("id", "content")
.attr("transform", "translate(0,0)"),
contentText = content.append("text")
.text("transform = translate ( d3.event.translate ) scale ( d3.event.scale )")
.style("fill", "#5c5c5c")
.attr({"dy": 50, "dx": 20})
content.selectAll("rect")
.data([[20,60],[140,60]])
.enter().append("rect")
.attr({height: 50, width: 50})
.style({"stroke-width": 3, "stroke": "#ccc"})
.each(function(d){
d3.select(this).attr({x: d[0], y: d[1]});
});
function zoomStart(){
}
function zoomed(){
return d3.event.sourceEvent.buttons ? zoomDrag.call(this) : zoomScale.call(this)
}
function zoomDrag(){
var t = d3.transform(content.attr("transform"));
t.translate = d3.event.translate;
content.attr("transform", t.toString());
}
function zoomScale(){
var t = d3.transform(content.attr("transform"));
t.translate = d3.event.translate; t.scale = d3.event.scale;
content.transition().duration(450).attr("transform", t.toString());
}
svg {
outline: 1px solid #282f51;
pointer-events: all;
}
g {
outline: 1px solid red;
shape-rednering: "geometricPrecision";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg id="viz"></svg>
第二阶段
合并 FDG
由于 FDG 必须在 canvas 容器内,因此有必要阻止节点级事件传播到 canvas。这是在 OP 代码中通过使用自定义拖动行为完成的,在 dragstart 上停止传播并添加一些 force.drag
行为(加上设置 d.fixed
= true ). This is great if you don't mind losing some of the
force.dragfeatures like sticking nodes on mouseover. This is nice for capturing small, energetic nodes though. So, in order to get the best of both worlds, you can hook the
force.drag` 行为。
策略
- 应用与第一阶段相同的原则,但对鼠标滚轮事件进行跨浏览器测试。
- 向节点添加标准
force.drag
- 挂钩
force.drag
以添加自定义行为 - 仅修复 shift-drag (或 shift-dragend)[= 上的节点78=]
- 对于触摸设备,如果在 dragstart 时触摸 > 1,也会修复节点
最后两点允许在需要时轻松释放固定节点。
force.drag勾
//hook force.drag behaviour
var stdDragStart = force.drag().on("dragstart.force");
force.drag()
.on("dragstart", function(d){
//prevent dragging on the nodes from dragging the canvas
d3.event.sourceEvent.stopPropagation();
stdDragStart.call(this, d);
});
工作示例
//debug panel/////////////////////////////////////////////////////////////////////////////
var alpha = d3.select("#alpha").text("waiting..."),
cog = d3.select("#wrapAlpha").insert("i", "#fdg").classed("fa fa-cog fa-spin", true).datum({instID: null}),
fdgInst = d3.select("#fdg");
elapsedTime = ElapsedTime("#panel", {margin: 0, padding: 0})
.message(function (id) {
return 'fps : ' + d3.format(" >8.3f")(1/this.aveLap())
});
elapsedTime.consoleOn = true;
alpha.log = function(e, instID) {
elapsedTime.mark().timestamp();
alpha.text(d3.format(" >8.4f")(e.alpha));
fdgInst.text("fdg instance: " + instID);
};
d3.select("#update").on("click", (function() {
var dataSet = false;
return function() {
//fdg.force.stop();
fdg(dataSets[(dataSet = !dataSet, +dataSet)])
}
})());
//////////////////////////////////////////////////////////////////////////////////////////
var dataSets = [{
"nodes" : [
{"name": "node1", "r": 10},
{"name": "node2", "r": 10},
{"name": "node3", "r": 30},
{"name": "node4", "r": 15}
],
"edges": [
{"source": 2, "target": 0},
{"source": 2, "target": 1},
{"source": 2, "target": 3}
]
},
{
"nodes":[
{"name": "node1", "r": 20},
{"name": "node2", "r": 10},
{"name": "node3", "r": 30},
{"name": "node4", "r": 15},
{"name": "node5", "r": 10},
{"name": "node6", "r": 10}
],
"edges":[
{"source": 2, "target": 0},
{"source": 2, "target": 1},
{"source": 2, "target": 3},
{"source": 2, "target": 4},
{"source": 2, "target": 5}
]
}
],
svg = SVG({width: 600, height: 200-34, margin: {top: 25, right: 5, bottom: 5, left: 5}}, "#viz"),
fdg = FDG(svg, alpha.log);
fdg(dataSets[0]);
function SVG (size, selector){
//delivers an svg background with zoom/drag context in the selector element
//if height or width is NaN, assume it is a valid length but ignore margin
var margin = size.margin || {top: 0, right: 0, bottom: 0, left: 0},
unitW = isNaN(size.width), unitH = isNaN(size.height),
w = unitW ? size.width : size.width - margin.left - margin.right,
h = unitH ? size.height : size.height - margin.top - margin.bottom,
zoomed = function(){return this},
zoom = d3.behavior.zoom().scaleExtent([0.4, 4])
.on("zoom", function(d, i, j){
zoomed.call(this, d, i, j);
}),
svg = d3.select(selector).selectAll("svg").data([["transform root"]]);
svg.enter().append("svg");
svg.attr({width: size.width, height: size.height});
var g = svg.selectAll("#zoom").data(id),
gEnter = g.enter().append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom)
.attr({class: "outline", id: "zoom"}),
zoomText = gEnter.append("text")
.text("transform = translate ( margin.left , margin.top )")
.style("fill", "#5c5c5c")
.attr("dy", "-.35em"),
surface = gEnter.append("rect")
.attr({width: w, height: h})
.style({"pointer-events": "all", fill: "#ccc", "stroke-width": 3, "stroke": "#fff"}),
surfaceText = gEnter.append("text")
.text("pointer-events: none")
.style("fill", "#5c5c5c")
.attr({"dy": "1em", "dx": ".2em"});
g.h = h;
g.w = w;
g.onZoom = function(cb){zoomed = cb;};
return g;
}
function FDG (svg, tickLog) {
var instID = Date.now();
force = d3.layout.force()
.size([svg.w, svg.h])
.charge(-1000)
.linkDistance(50)
.on("end", function(){
// manage dead instances of force
// only stop if this instance is the current owner
if(cog.datum().instID != instID) return true;
cog.classed("fa-spin", false);
elapsedTime.stop();
})
.on("start", function(){
// mark as active and brand the insID to establish ownership
cog.classed("fa-spin", true).datum().instID = instID;
elapsedTime.start();
});
function fdg(data) {
force
.nodes(data.nodes)
.links(data.edges)
.on("tick", (function(instID) {
return function(e) {
if(tickLog) tickLog.call(this, e, instID);
lines.attr("x1", function(d) {
return d.source.x;
}).attr("y1", function(d) {
return d.source.y;
}).attr("x2", function(d) {
return d.target.x;
}).attr("y2", function(d) {
return d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")"
});
}
})(instID))
.start();
svg.onZoom(zoomed);
hookDrag(force.drag(), "dragstart.force", function(d) {
// prevent dragging on the nodes from dragging the canvas
var e = d3.event.sourceEvent;
e.stopPropagation();
d.fixed = e.shiftKey || e.touches && (e.touches.length > 1);
});
hookDrag(force.drag(), "dragend.force", function(d) {
// prevent dragging on the nodes from dragging the canvas
var e = d3.event.sourceEvent;
d.fixed = e.shiftKey || d.fixed;
});
var content = svg.selectAll("g#fdg").data([data]);
content.enter().append("g").attr({"id": "fdg", class: "outline"});
var contentText = content.selectAll(".contentText")
.data(["transform = translate ( d3.event.translate ) scale ( d3.event.scale )"])
.enter().append("text").classed("contentText", true)
.text(id)
.style("fill", "#5c5c5c")
.attr({"dy": 20, "dx": 20});
var lines = content.selectAll(".links")
.data(linksData),
linesEnter = lines.enter()
.insert("line", d3.select("#nodes") ? "#nodes" : null)
.attr("class", "links")
.attr({stroke: "steelblue", "stroke-width": 3});
var nodes = content.selectAll("#nodes")
.data(nodesData),
nodesEnter = nodes.enter().append("g")
.attr("id", "nodes"),
node = nodes.selectAll(".node")
.data(id),
newNode = node.enter().append("g")
.attr("class", "node")
.call(force.drag),
circles = newNode.append("circle")
.attr({class: "content"})
.attr("r", function(d) {return d.r})
.style({"fill": "red", opacity: 0.8});
lines.exit().remove();
node.exit().remove();
function nodesData(d) {
return [d.nodes];
}
function linksData(d) {
return d.edges;
}
function hookDrag(target, event, hook) {
//hook force.drag behaviour
var stdDragStart = target.on(event);
target.on(event, function(d) {
hook.call(this, d);
stdDragStart.call(this, d);
});
}
function zoomed(){
var e = d3.event.sourceEvent,
isWheel = e && ((e.type == "mousewheel") || (e.type == "wheel"));
force.alpha(0.01);
return isWheel ? zoomWheel.call(this) : zoomInst.call(this)
}
function zoomInst(){
var t = d3.transform(content.attr("transform"));
t.translate = d3.event.translate; t.scale = d3.event.scale;
content.attr("transform", t.toString());
}
function zoomWheel(){
var t = d3.transform(content.attr("transform"));
t.translate = d3.event.translate; t.scale = d3.event.scale;
content.transition().duration(450).attr("transform", t.toString());
}
fdg.force = force;
};
return fdg
}
function id(d){return d;}
svg {
outline: 1px solid #282f51;
pointer-events: all;
overflow: visible;
}
g.outline {
outline: 1px solid red;
}
#panel div {
display: inline-block;
margin: 0 .25em 3px 0;
}
#panel div div {
white-space: pre;
}
div#inputDiv {
white-space: normal;
display: inline-block;
}
.node {
cursor: default;
}
text {
font-size: 8px;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/40e550b1507cca31b0bb/raw/b83ceb0f8b4a2b6256f079f5887fc5243baedd4f/elapsed%2520time%25201.0.js"></script>
<div id="panel">
<div id="inputDiv">
<input id="update" type="button" value="update">
</div>
<div id="wrapAlpha">alpha:
<div id="alpha"></div>
</div>
<div id="fdg">
</div>
<div id="viz"></div>