Если я понимаю проблему,
(первая) проблема заключается в том, что вы не обновляете (*) zoom
.
Где используется d3.zoom
, часто это простоотслеживает текущее состояние масштабирования, а не применяет преобразование непосредственно к контейнеру.В примере с кистью и масштабированием масштабирование применяется путем повторного масштабирования данных, а не путем применения SVG-преобразования к контейнеру.Используя этот пример, мы можем видеть, что когда мы чистим, мы также вызываем:
svg.select(".zoom").call(zoom.transform, someZoomTransform);
This:
- обновляет состояние / идентичность масштабирования в соответствии с переменной
zoom
- испускает событие масштабирования, которое вызывает функцию масштабирования (которая в примере с кистью и масштабированием игнорируется, если ее запускает кисть)
Если мы удаляем эту строку, изменяется масштабсостояние, созданное кистью, не обновляет масштаб.Перейдите на очень маленький домен, затем увеличьте масштаб и увидите здесь .
Это тот случай, когда вы обновляете диаграмму с помощью функции zoomed
и d3.event.transform
не обновляет состояние масштабирования.Вы обновляете весы, но zoom
не обновляется.
Ниже я продемонстрирую использование одного зума для обновления другого. Примечание: если каждая увеличенная функция вызывает другие, мы войдем в бесконечный цикл.С помощью кисти и масштабирования мы могли видеть, был ли триггер кистью, чтобы увидеть, нужна ли функция масштабирования, ниже я использую d3.event.sourceEvent.target, чтобы увидеть, нужно ли другим масштабированным функциям распространять масштабирование :
var svg = d3.select("svg");
var size = 100;
var zoom1 = d3.zoom().scaleExtent([0.25,4]).on("zoom", zoomed1);
var zoom2 = d3.zoom().scaleExtent([0.25,4]).on("zoom", zoomed2);
var rect1 = svg.append("rect")
.attr("width", size)
.attr("height", size)
.attr("x", 10)
.attr("y", 10)
.call(zoom1);
var rect2 = svg.append("rect")
.attr("width", size)
.attr("height", size)
.attr("x", 300)
.attr("y", 10)
.call(zoom2);
function zoomed1() {
var t = d3.event.transform;
var k = Math.sqrt(t.k);
rect1.attr("width",size/k).attr("height",size*k);
if(d3.event.sourceEvent.target == this) {
rect2.call(zoom2.transform,t);
}
}
function zoomed2() {
var t = d3.event.transform;
var k = Math.sqrt(t.k);
rect2.attr("width",size/k).attr("height",size*k);
if(d3.event.sourceEvent.target == this) {
rect1.call(zoom2.transform,t);
}
}
rect {
cursor: pointer;
stroke: #ccc;
stroke-width: 10;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Zoom on one rectangle to update the other.
<svg width="600" height="300"></svg>
Вас может удивить, почему я жестко запрограммировал размер, почему бы мне просто не изменить текущий размер, а не исходный.Ответ заключается в том, что масштаб трансфокации - это масштаб относительно исходного состояния, а не последнее состояние.Например, если масштаб удваивается при каждом увеличении, а мы увеличиваем в 2 раза, масштаб изменяется следующим образом: k = 1 → k = 2 → k = 4.Если мы умножим текущий размер фигуры на новый масштаб, мы получим size = 1 → size = 2 → size = 8, это неправильно (и при уменьшении до k = 2 мы удвоим величину, которую мы увеличиваемв, а не в сторону уменьшения).Преобразование является кумулятивным уже , мы не хотим применять его к значению, к которому применено преобразование.
Применение преобразования к преобразованному значению вместоИсходное значение может привести к увеличению масштаба даже при уменьшении масштаба - возможно, именно поэтому у вас возникли проблемы с уменьшением
Итак, это подводит меня ко второй проблеме, x2
.x2
является ссылкой, исходное значение.Да, как отмечает Герардо, это также масштаб кисти в вашем примере, но, что более важно, он утверждает, что этот масштаб не меняется.Из-за этого x2
хорошо подходит для использования в качестве эталонной шкалы, к которой мы можем преобразовать x
с учетом состояния масштабирования:
x.domain(t.rescaleX(x2).domain());
Что здесь происходит?transform.rescaleX(x2)
не изменяет x2
, оно "возвращает копию непрерывной шкалы x, чья область преобразована [с учетом преобразования масштаба]. ( docs )".Мы берем домен копии и присваиваем его шкале x
(диапазон, конечно, остается неизменным), и, таким образом, применяем преобразование к шкале x
. Это по сути то же самое, что и мой фрагмент выше с квадратами / прямоугольниками, где я сохраняю контрольное значение для начального размера фигур и применяю преобразование к этому значению.
Давайте посмотримэто в действии с базовым графиком / графиком с масштабами, а не с простыми формами:
var svg = d3.select("svg");
var data = [[0,300],[1,20],[2,300]];
// Area generators:
var leftArea = d3.area().curve(d3.curveBasis)
.x(function(d) { return leftX(d[0]); })
var rightArea = d3.area().curve(d3.curveBasis)
.x(function(d) { return rightX(d[0]); })
// Scales
var leftX = d3.scaleLinear().domain([0,2]).range([0,250]);
var rightX = d3.scaleLinear().domain([0,2]).range([300,550]);
var leftX2 = leftX.copy();
var rightX2 = rightX.copy();
// Zooms
var leftZoom = d3.zoom().scaleExtent([0.25,4]).on("zoom", leftZoomed);
var rightZoom = d3.zoom().scaleExtent([0.25,4]).on("zoom", rightZoomed);
// Graphs
var leftGraph = svg.append("path")
.attr("d", leftArea(data))
.call(leftZoom);
var rightGraph = svg.append("path")
.attr("d", rightArea(data))
.call(rightZoom);
function leftZoomed() {
var t = d3.event.transform;
leftX.domain(t.rescaleX(leftX2).domain());
leftGraph.attr("d",leftArea(data));
if(d3.event.sourceEvent.target == this) {
rightGraph.call(rightZoom.transform,t);
}
}
function rightZoomed() {
var t = d3.event.transform;
rightX.domain(t.rescaleX(rightX2).domain());
rightGraph.attr("d",rightArea(data));
if(d3.event.sourceEvent.target == this) {
leftGraph.call(leftZoom.transform,t);
}
}
path {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Zoom on one plot to update the other (zoom on the path area itself)
<svg width="600" height="300"></svg>
Проще говоря, чтобы синхронизировать несколько масштабируемых масштабируемых графиков на одной странице или между клиентами, вы должны быть:
- обновляя каждое увеличение с помощью
selection.call(zoom.transform,transform)
- , изменяя масштаб каждого масштаба с использованием текущего преобразования и эталонного масштаба.
Я не пытался попробовать это с несколькими клиентами и сокетами.Но вышесказанное должно помочь объяснить, как подойти к проблеме.Однако, с несколькими клиентами, вам может понадобиться изменить способ остановки бесконечного цикла событий масштабирования, поэтому использование или установка свойства в объекте преобразования может быть самым простым.Кроме того, как отмечает rioV8, вам, вероятно, следует передавать параметры масштабирования (или, что еще лучше, сам d3.event), а не домен, хотя возможен вариант только для домена.
С сокетами у меня действительно было несколькопроблема с отправкой объектов - я не знаком с socket.io и не потратил много времени на поиск, но я получил это для работы с функциями масштабирования и пассивного увеличения следующим образом:
function zoomed() {
let t = d3.event.transform;
// 1. update the scale, same as in brush and zoom:
x.domain(t.rescaleX(x2).domain());
// 2. redraw the graph and axis, same as in brush and zoom:
path.attr("d", area); // where path is the graph
svg.select(".xaxis").call(xAxis);
// 3. Send the transform, if needed:
if(t.alreadySent == undefined) {
t.alreadySent = true; // custom property.
sendMessage([t.k,t.x,t.y,t.alreadySent]);
}
}
function passiveZoom(rcv){
// build a transform object (since I was unable to successfully transmit the transform)
var t = d3.zoomIdentity;
t.k = rcv[0];
t.x = rcv[1];
t.y = rcv[2];
t.alreadySent = rcv[3];
//trigger a zoom event (invoke zoomed function with new transform data).
rect.call(zoom.transform,t); // where rect is the selection that zoom is called on.
}
Скореечем отправка события, я отправляю параметры преобразования (только) вместе с флагом, чтобы отметить, что событие масштабирования, вызываемое функцией пассивного масштабирования, не нужно снова передавать дальше.Это в принципе основано именно на приведенных выше фрагментах.
Нет изменений в сценарии на стороне сервера.Вот клиентская сторона , которую я использовал - она более простая, чем ваш код, так как я удалил шкалы y, ось y, источник данных csv и т. Д.