svg - d3中力向图的语义缩放

标签 svg d3.js zoom force-layout

已经显示了许多cases用于通过SVG Geometric Zooming进行力导向图几何缩放。

在几何缩放中,我只需要在缩放功能中添加一个transform属性。但是,在语义缩放中,如果仅在node中添加一个transform属性,则链接将不会连接到node。因此,我想知道在d3中是否存在针对力导向图的几何缩放的解决方案。

这是我的example,其几何缩放遵循先前的情况。我有两个问题:


当我缩小然后拖动整个图时,该图会奇怪地消失。
使用相同的重绘功能



function zoom() {
  vis.attr("transform", transform);
}
function transform(d){
  return "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")";
}



这只会更新一个svg元素的“ transform”属性。但是如何使函数改变节点位置呢?

但是我想做的是semantic zooming。我试图修改缩放和变换功能,但不确定正确的方法。我尝试的是Here。我更改的功能:


function zoom() {
  node.call(transform);
  // update link position
  update();
}
function transform(d){
  // change node x, y position, not sure what function to put here.
}

最佳答案

我试图找到一个很好的教程来链接,但是找不到真正涵盖所有问题的内容,因此我将自己逐步编写它。

首先,您需要清楚地了解您要完成的工作。这对于两种缩放类型是不同的。我真的不喜欢Mike Bostock引入的术语(这与术语的非d3使用并不完全一致),但我们也可能坚持与其他d3示例保持一致。

在“几何缩放”中,您将缩放整个图像。圆和线越来越大,也越来越远。 SVG具有通过“ transform”属性完成此操作的简便方法。在SVG元素上设置transform="scale(2)"时,将其绘制为好像一切都大了两倍。对于一个圆,它的半径被绘制成两倍大,并且它的cxcy位置被绘制成距(0,0)点的距离的两倍。整个坐标系发生变化,因此现在一个单位等于屏幕上的两个像素,而不是一个。

同样,transform="translate(-50,100)"更改整个坐标系,以使坐标系的(0,0)点向左移动50个单位,并从左上角向下移动100个单位(这是默认的原点)。

如果同时翻译和缩放SVG元素,则顺序很重要。如果翻译未按比例缩放,则翻译将以原始单位进行。如果平移是按比例缩放,则平移将按比例缩放单位。

d3.zoom.behavior()方法创建一个侦听鼠标滚轮和拖动事件以及与缩放相关的触摸屏事件的函数。它将这些用户事件转换为自定义的“缩放”事件。

缩放事件有一个比例因子(一个数字)和一个转换因子(两个数字的数组),行为对象根据用户的移动来计算它们。您如何处理这些数字取决于您自己;他们不会直接改变任何东西。 (除了将缩放比例附加到缩放行为功能时,如后面所述。)

对于几何缩放,通常要做的是在包含要缩放内容的<g>元素上设置比例并转换transform属性。本示例在由均匀放置的网格线组成的简单SVG上实现了几何缩放方法:
http://jsfiddle.net/LYuta/2/

缩放代码很简单:

function zoom() {
    console.log("zoom", d3.event.translate, d3.event.scale);
    vis.attr("transform", 
             "translate(" + d3.event.translate + ")" 
                + " scale(" + d3.event.scale + ")"
             );
}


缩放是通过将transform属性设置为“ vis”来完成的,该属性是d3选择,其中包含<g>元素,该元素本身包含我们要缩放的所有内容。平移和缩放因子直接来自d3行为创建的缩放事件。

结果就是一切都会变大或变小-网格线的宽度以及它们之间的间距。这些行仍然具有stroke-width:1.5;,但是对于屏幕上等于1.5的内容的定义和更改后的<g>元素中的其他任何内容都已更改。

对于每个缩放事件,平移和缩放因子也会记录到控制台。看到这一点,您会注意到,如果缩小,比例将在0到1之间;如果放大,它将大于1。如果平移(拖动以移动)图形,则比例将完全不变。但是,转换数字在平移和缩放上都会改变。这是因为平移表示相对于SVG左上角的位置的图形中(0,0)点的位置。缩放时,(0,0)与图形上任何其他点之间的距离都会改变。因此,为了使鼠标或手指触摸下的内容保持在屏幕上的相同位置,必须移动(0,0)点的位置。

在该示例中,您还需要注意许多其他事项:


我已经使用.scaleExtent([min,max])方法修改了缩放行为对象。这将限制行为将在缩放事件中使用的比例值,无论用户旋转了多少旋钮。
转换位于<g>元素上,而不是<svg>本身。这是因为SVG元素作为一个整体被视为HTML元素,并且具有不同的转换语法和属性。
缩放行为附加到另一个<g>元素,该元素包含主<g>和背景矩形。此处有背景矩形,即使鼠标或触摸不在一行上,也可以观察到鼠标和触摸事件。 <g>元素本身没有任何高度或宽度,因此无法直接响应用户事件,它仅接收其子级的事件。我将矩形保留为黑色,以便您可以知道它的位置,但是只要将其样式设置为fill:none;,就可以将其样式设置为pointer-events:all;。矩形不能位于要转换的<g>内,因为当您缩小时,响应缩放事件的区域也会缩小,并且可能会超出SVG边缘的视线。
您可以通过将缩放行为直接附加到SVG对象来跳过矩形和第二个<g>元素,如this version of the fiddle所示。但是,您通常不希望整个SVG区域上的事件触发缩放,因此最好知道如何以及为什么使用背景矩形选项。


这是相同的几何缩放方法,适用于力布局的简化版本:
http://jsfiddle.net/cSn6w/5/

我减少了节点和链接的数量,并取消了节点拖动行为和节点展开/折叠行为,因此您可以专注于缩放。我还更改了“摩擦”参数,以使图形停止移动需要更长的时间;在它仍在移动时对其进行缩放,您会看到一切都将像以前一样保持移动。

图像的“几何缩放”非常简单,只需很少的代码即可实现,并且浏览器可以快速,平滑地进行更改。但是,通常要放大图的原因通常是因为数据点太靠近且重叠。在这种情况下,仅使所有内容变大都无济于事。您想在更大的空间上延伸元素,同时保持各个点的大小相同。这就是“语义缩放”的地方。

Mike Bostock uses the term的意义上,图的“语义缩放”是在不缩放单个元素的情况下缩放图的布局。 (请注意,对于其他上下文,还有“语义缩放”的其他解释。)

这是通过更改元素位置的计算方式以及连接对象的任何线或路径的长度来完成的,而无需更改用于设置线宽或形状或文字的大小。

您可以根据以下公式使用平移和缩放值来定位对象,自己进行这些计算:

zoomedPositionX = d3.event.translate[0] + d3.event.scale * dataPositionX 

zoomedPositionY = d3.event.translate[1] + d3.event.scale * dataPositionY


我在此版本的gridlines示例中使用了这种方法来实现语义缩放:
http://jsfiddle.net/LYuta/4/

对于垂直线,它们最初的定位是这样的

vLines.attr("x1", function(d){return d;})
    .attr("y1", 0)
    .attr("x2", function(d){return d;})
    .attr("y2", h);


在缩放功能中,将其更改为

vLines.attr("x1", function(d){
        return d3.event.translate[0] + d*d3.event.scale;
    })
    .attr("y1", d3.event.translate[1])
    .attr("x2", function(d){
        return d3.event.translate[0] + d*d3.event.scale;
    })
    .attr("y2", d3.event.translate[1] + h*d3.event.scale);


水平线的变化类似。结果?线条的位置和长度在缩放时会发生变化,而线条不会变粗或变细。

当我们尝试对力布局进行相同操作时,它会变得有些复杂。这是因为在每个“滴答”事件之后,力布局图中的对象也会重新定位。为了将它们定位在正确的缩放位置,刻度线定位方法将必须使用缩放位置公式。意思就是:


比例和平移必须保存在一个刻度函数可以访问的变量中。和,
如果用户尚未缩放任何内容,则需要使用刻度功能的默认比例和平移值。


默认比例将为1,默认平移将为[0,0],表示正常比例且无平移。

这是简化的力布局上的语义缩放的外观:
http://jsfiddle.net/cSn6w/6/

变焦功能现在

function zoom() {
    console.log("zoom", d3.event.translate, d3.event.scale);
    scaleFactor = d3.event.scale;
    translation = d3.event.translate;
    tick(); //update positions
}


它设置scaleFactor和平移变量,然后调用tick函数。滴答功能会进行所有定位:在初始化时,强制布局滴答事件之后以及缩放事件之后。看起来像

function tick() {
    linkLines.attr("x1", function (d) {
            return translation[0] + scaleFactor*d.source.x;
        })
        .attr("y1", function (d) {
            return translation[1] + scaleFactor*d.source.y;
        })
        .attr("x2", function (d) {
            return translation[0] + scaleFactor*d.target.x;
        })
        .attr("y2", function (d) {
            return translation[1] + scaleFactor*d.target.y;
        });

    nodeCircles.attr("cx", function (d) {
            return translation[0] + scaleFactor*d.x;
        })
        .attr("cy", function (d) {
            return translation[1] + scaleFactor*d.y;
        });
}


圆和链接的每个位置值都可以通过平移和比例因子进行调整。如果这对您有意义,那么这对于您的项目应该足够了,您不需要使用比例尺。只要确保始终使用此公式在数据坐标(d.x和d.y)与用于定位对象的显示坐标(cx,cy,x1,x2等)之间转换即可。

如果您需要从显示坐标到数据坐标的反向转换,这会变得很复杂。如果希望用户能够拖动单个节点,则需要执行此操作-您需要根据所拖动节点的屏幕位置设置数据坐标。 (请注意,这在您的两个示例中均无法正常工作)。

对于几何缩放,可以使用d3.mouse()下移屏幕位置和数据位置之间的转换。使用d3.mouse(SVGElement)计算该SVGElement所使用的坐标系中鼠标的位置。因此,如果我们传入表示转换后的可视化效果的元素,它将返回可直接用于设置对象位置的坐标。

可拖动的几何缩放力布局如下所示:
http://jsfiddle.net/cSn6w/7/

拖动功能为:

function dragged(d){
    if (d.fixed) return; //root is fixed

    //get mouse coordinates relative to the visualization
    //coordinate system:    
    var mouse = d3.mouse(vis.node());
    d.x = mouse[0]; 
    d.y = mouse[1];
    tick();//re-position this node and any links
}


但是,对于语义缩放,d3.mouse()返回的SVG坐标不再直接对应于数据坐标。您必须考虑比例和翻译。您可以通过重新排列上面给出的公式来做到这一点:

zoomedPositionX = d3.event.translate[0] + d3.event.scale * dataPositionX 

zoomedPositionY = d3.event.translate[1] + d3.event.scale * dataPositionY


变成

dataPositionX = (zoomedPositionX - d3.event.translate[0]) / d3.event.scale

dataPositionY = (zoomedPositionY - d3.event.translate[1]) / d3.event.scale


因此,语义缩放示例的拖动功能为

function dragged(d){
    if (d.fixed) return; //root is fixed

    //get mouse coordinates relative to the visualization
    //coordinate system:
    var mouse = d3.mouse(vis.node());
    d.x = (mouse[0] - translation[0])/scaleFactor; 
    d.y = (mouse[1] - translation[1])/scaleFactor; 
    tick();//re-position this node and any links
}


此可拖动的语义缩放强制布局在此处实现:
http://jsfiddle.net/cSn6w/8/

那应该足以让您重回正轨。稍后我会再来,并解释比例,以及它们如何使所有这些计算变得更加容易。

...然后我回来了:

查看上面所有的数据到显示转换函数,这是否会让您认为“每次都有一个函数来执行此操作会更容易吗?”这就是d3 scales的用途:将数据值转换为显示值。

在力布局示例中通常不会看到比例,因为力布局对象允许您直接设置宽度和高度,然后在该范围内创建d.x和d.y数据值。将布局的宽度和高度设置为可视化的宽度和高度,然后可以直接使用数据值在显示中定位对象。

但是,放大图形时,您会从显示整个数据范围切换到仅显示一部分。因此,数据值不再直接与定位值相对应,我们需要在它们之间进行转换。缩放功能将使操作变得容易得多。

在D3术语中,期望的数据值是域,期望的输出/显示值是范围。因此,比例尺的初始范围将由布局中的预期最大值和最小值决定,而初始范围将是可视化视图中的最大值和最小值。

缩放时,域和范围之间的关系会发生变化,因此这些值之一必须在比例上发生变化。幸运的是,我们不必自己弄清楚这些公式,因为D3缩放行为会为我们计算公式-如果我们使用其.x().y()方法将缩放对象附加到缩放行为对象。

结果,如果我们更改绘图方法以使用比例尺,则在zoom方法中要做的就是调用绘图函数。

这是使用比例尺实现的网格示例的语义缩放:
http://jsfiddle.net/LYuta/5/

关键代码:

/*** Configure zoom behaviour ***/
var zoomer = d3.behavior.zoom()
                .scaleExtent([0.1,10])
        //allow 10 times zoom in or out
                .on("zoom", zoom)
        //define the event handler function
                .x(xScale)
                .y(yScale);
        //attach the scales so their domains
        //will be updated automatically

function zoom() {
    console.log("zoom", d3.event.translate, d3.event.scale);

    //the zoom behaviour has already changed
    //the domain of the x and y scales
    //so we just have to redraw using them
    drawLines();
}
function drawLines() {
    //put positioning in a separate function
    //that can be called at initialization as well
    vLines.attr("x1", function(d){
            return xScale(d);
        })
        .attr("y1", yScale(0) )
        .attr("x2", function(d){
            return xScale(d);
        })
        /* etc. */


d3缩放行为对象通过更改比例来修改比例。通过更改比例范围可以得到类似的效果,因为重要的部分是更改域和范围之间的关系。但是,该范围还有另一个重要含义:代表显示中使用的最大值和最小值。通过仅通过缩放行为更改刻度的范围侧,该范围仍表示有效的显示值。当用户调整显示尺寸时,这允许我们实现不同类型的缩放。通过让SVG根据窗口大小更改大小,然后根据SVG大小设置缩放范围,图形可以响应不同的窗口/设备大小。

这是语义缩放网格示例,可以对比例进行响应:
http://jsfiddle.net/LYuta/9/

我已经在CSS中提供了基于SVG百分比的高度和宽度属性,它将覆盖属性的高度和宽度值。在脚本中,我已将所有与显示高度和宽度有关的行移到一个函数中,该函数检查实际svg元素的当前高度和宽度。最后,我添加了一个窗口大小调整侦听器来调用此方法(它还会触发重新绘制)。

关键代码:

/* Set the display size based on the SVG size and re-draw */
function setSize() {
    var svgStyles = window.getComputedStyle(svg.node());
    var svgW = parseInt(svgStyles["width"]);
    var svgH = parseInt(svgStyles["height"]);

    //Set the output range of the scales
    xScale.range([0, svgW]);
    yScale.range([0, svgH]);

    //re-attach the scales to the zoom behaviour
    zoomer.x(xScale)
          .y(yScale);

    //resize the background
    rect.attr("width", svgW)
            .attr("height", svgH);

    //console.log(xScale.range(), yScale.range());
    drawLines();
}

//adapt size to window changes:
window.addEventListener("resize", setSize, false)

setSize(); //initialize width and height


相同的想法-使用比例尺来布置图形,缩放区域发生变化,窗口调整大小事件发生范围变化-当然可以应用于强制布局。但是,我们仍然必须处理上面讨论的复杂问题:在处理节点拖动事件时如何将数据值转换为显示值。 d3线性标度对此也有一个方便的方法:scale.invert()。如果为w = scale(x),则为x = scale.invert(w)

因此,在node-drag事件中,使用scale的代码为:

function dragged(d){
    if (d.fixed) return; //root is fixed

    //get mouse coordinates relative to the visualization
    //coordinate system:
    var mouse = d3.mouse(vis.node());
    d.x = xScale.invert(mouse[0]); 
    d.y = yScale.invert(mouse[1]); 
    tick();//re-position this node and any links
}


其余的语义缩放力布局示例,通过比例进行响应,如下所示:
http://jsfiddle.net/cSn6w/10/



我敢肯定,这次讨论比您期望的要长得多,但是我希望它不仅可以帮助您了解需要做的事情,而且还可以帮助您了解为什么要做。当我看到代码实际上显然是从一个实际上并不了解代码功能的人从多个示例中剪切并粘贴在一起时,我感到非常沮丧。如果您了解该代码,则可以轻松地将其适应您的需求。希望这将为其他试图弄清楚如何完成类似任务的人提供很好的参考。

关于svg - d3中力向图的语义缩放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21344340/

相关文章:

css - SVG 差距 : wont completely fill the outer container

javascript - 使用 d3.js (或类似的)创建交互式平面图

r - 我可以浏览,放大和缩小R图吗?

css - Microsoft Edge/IE SVG 变换缩放动画不起作用

JavaScript SVG 世界地图库可以离线工作吗?

svg - 如何在 SVG 中使用我自己的混合公式?

d3.js - D3 : Convert scale level of the time scale to time period, 就像 : day, 周或月?

d3.js - 如何使用 d3 选择特定的父级?

ios - MKTileOverlay缩放级别大于21

flutter - Flutter:如何使以下图像小部件在堆栈中起作用