【发布时间】:2019-02-19 00:32:42
【问题描述】:
我想制作一个折线图以与多个网页同步缩放/平移。
这些客户端具有相同的 Javascript 和 HTML 源代码。 用户在客户端 A 上进行缩放或平移,数据域的一天时间的消息被发送到另一个和发送者(上图中的蓝线),并且接收到的客户端的图形将同时更改。当然,其他客户端也可以这样做。 它类似于聊天应用程序。
缩放功能是:
function zoomed() {
let msg = [];
let t = d3.event.transform; //1)
msg[0] = t.rescaleX(x2).domain()[0].toString(); //2)
msg[1] = t.rescaleX(x2).domain()[1].toString(); //2)
sendMessage(msg); //3)
}
- d3.event.transform 捕获鼠标事件。
- 转换为日期时间和字符串。
- 向服务器发送新的规模域。
服务器将接收到的数据发送给所有客户端:
function passiveZoom(rcv){
let leftend;
let rightend;
leftend = new Date(rcv[0]);
rightend = new Date(rcv[1]);
x.domain([leftend, rightend]);
svg.select(".line").attr("d", valueline);
svg.select(".axis").call(xAxis);
}
- 从服务器接收到包含新一天时间的消息。
- 设置新域,
- 更新折线图。
使用它可以缩放|平移所有折线图。
但是,它不能按要求工作。
如果我在客户端 A 中缩放|平移,客户端 B 和客户端 C 将被更改。没关系。
接下来,我在客户端 C 上缩放|平移(上图中的橙色线),所有图形都更改为初始比例和位置。为什么!?
我假设鼠标坐标没有发送给客户端,但是发送鼠标位置坐标时应该如何处理呢?
Zoom|Pan 进程是从 mbostock 的块中派生出来的:Brush & Zoom。发件人还使用t.rescalex (x2).domain() 更改X2 域的范围。
由于图中没有用到X2,所以我把X改成了x2,但是只能放大,看不懂X2的意思。
请告诉我如何同步所有客户端? 什么是 x2?
此代码适用于从 Simple line graph with v4 派生的客户端。
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/* set the CSS */
body {
font: 12px Arial;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="socket.io.js"></script>
<script>
//--- Network----
let rcvT;
let socket = io.connect('http://localhost:3000');
//Recive event from server
socket.on("connect", function() {});
socket.on("disconnect", function(client) {});
socket.on("S_to_C_message", function(data) {
rcvT = data.value;
passiveZoom(rcvT);
});
socket.on("S_to_C_broadcast", function(data) {
console.log("Rcv broadcast " + data.value);
rcvT = data.value;
passiveZoom(rcvT);
});
function sendMessage(msg) {
socket.emit("C_to_S_message", { value: msg }); //send to server
}
function sendBroadcast(msg) {
socket.emit("C_to_S_broadcast", { value: msg }); // send to server
}
// --------------------
// Set the dimensions of the canvas / graph
var margin = { top: 30, right: 20, bottom: 30, left: 50 },
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.timeParse("%d-%b-%y");
// Set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleTime().range([height, 0]);
var x2 = d3.scaleTime().range([0, width]);
xAxis = d3.axisBottom(x)
.tickFormat(d3.timeFormat('%d-%b-%y'))
.ticks(5);
// var yAxis = d3.svg.axis().scale(y)
// .orient("left").ticks(5);
yAxis = d3.axisLeft(y);
// Define the line
var valueline = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// Adds the svg canvas
var svg = d3.select("body")
.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 + ")");
// Get the data
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
x2.domain(x.domain());
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// Add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
//follow is zoom method------------------
zoom = d3.zoom()
.scaleExtent([1, 45])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
function zoomed() {
let msg = [];
let t = d3.event.transform;
msg[0] = t.rescaleX(x2).domain()[0].toString();
msg[1] = t.rescaleX(x2).domain()[1].toString();
sendMessage(msg);
}
function passiveZoom(rcv){
let start;
let end;
start = new Date(rcv[0]);
end = new Date(rcv[1]);
x.domain([start, end]);
svg.select(".line").attr("d", valueline);
svg.select(".axis").call(xAxis);
}
</script>
</body>
如果你尝试这段代码,你应该在几个 bowser 窗口中执行,然后运行这个 node.js 脚本。
var http = require("http");
var socketio = require("socket.io");
var fs = require("fs");
console.log("reflector start");
var server = http.createServer(function(req, res) {
res.writeHead(200, {"Content-Type":"text/html"});
var output = fs.readFileSync("./index.html", "utf-8");
res.end(output);
}).listen(process.env.VMC_APP_PORT || 3000);
var io = socketio.listen(server);
io.sockets.on("connection", function (socket) {
// send message to all
socket.on("C_to_S_message", function (data) {
io.sockets.emit("S_to_C_message", {value:data.value});
console.log("MSG "+data.value);
});
// boradcast send to all without sender
socket.on("C_to_S_broadcast", function (data) {
socket.broadcast.emit("S_to_C_broadcast", {value:data.value});
});
// disconnection
socket.on("disconnect", function () {
console.log("disconnect");
});
});
【问题讨论】:
-
与您的问题无关:为什么您打算这样做?这些客户端机器是由真实的人类用户控制的吗?如果“是”,我认为这似乎提供了一个糟糕的用户体验......(无论如何,+1 是一个写得很好的问题)
-
对了,
x2是画笔使用的时间刻度,也就是那个图表的下半部分,叫做“上下文”。你可以看到x2域永远不会改变(但x域会)。 -
谢谢杰拉尔多。我想在一个网页中同时显示多个时间序列(日人口、日期 GDP 等)的数据。每个客户端都加载了
-
哎呀错过类型。我更正了代码。 “ msg[0] = t.rescaleX(x).domain()[0].toString();” -> " msg[0] = t.rescaleX(x2).domain()[0].toString();"如果是“rescaleX(x)”,交互只是放大。
-
进一步深入了解@Gerardo 启动的内容:为什么会出现服务器往返的麻烦?为什么不将其保留在客户端上,从而减少/避免延迟?不要误会我的意思,没有伤害的意思!只是想了解正在发生的事情,也许自己也学一点;-)
标签: javascript node.js d3.js zooming interaction