如何在SVG中创建此形状?

3

目前,我使用椭圆形阴影样式来设置我的条形基础:http://jsfiddle.net/rdesai/MjFgK/55/

如何更改样式,使基础看起来像下面的图表?

enter image description here

编辑

这个问题可以简化为创建一个如下图所示的形状:
enter image description here
我可以使用这个形状来替换椭圆形。

如何修改下面的代码,将椭圆形转换为上述形状?

svg.selectAll(".ellipse")
.data(data)
.enter()
.append("ellipse")
.attr("cx", function(d) { return x(d.episode) + 17; })
.attr("cy", function(d) { return y(0); })
.attr("rx", 20)
.attr("ry", 5)
.style("fill", function(d) { return d.shadow_color; })

你从哪里获取这些样式化的图像?如果您不介意,我可以得到链接吗? - Unknown User
@UnknownUser 这是我公司设计团队的人发来的。 - Rahul Desai
1
啊,这是每个网络程序员都喜欢听到老板说的话:“这是一个专业设计的静态图形。让它看起来一样,但在线上可更新和交互。” - AmeliaBR
1个回答

9
你需要使用自定义路径数据生成函数。如果你不熟悉路径数据属性的工作原理,你可以从这个教程开始学习(后续内容在此在此)。由于这并不需要更多的工作,我建议将整个条形图作为自定义路径进行绘制,而不是有一个单独的基础部分。如果你真的想要单独的元素,那么很容易进行适应。你的条形图代码如下:
svg.selectAll(".bar")
    .data(data)
    .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return x(d.episode); })
    .attr("width", x.rangeBand())
    .attr("y", function(d) { return y(d.donations); })
    .attr("height", function(d) { return height - y(d.donations); })
    .style("fill", function(d) { return d.bar_color; })
    .attr("rx", 10)
    .attr("ry", 10)
    .on("mouseover", function(d) {      
        tooltip.transition().duration(200).style("opacity", .9);      
        tooltip.html("NGO: " + d.ngo)  
        .style("left", (d3.event.pageX) + "px")     
        .style("top", (d3.event.pageY - 28) + "px");    
    })                  
    .on("mouseout", function(d) {       
        tooltip.transition().duration(500).style("opacity", 0);   
    });

鼠标悬停的工具提示功能不会改变,所以我将在讨论的其余部分中略过它们。填充样式也不会改变,类名也不会改变。然而,我们要把<rect>元素转换为<path>元素,并用路径数据"d"属性替换所有其他属性(x/y/width/height/rx/ry):

svg.selectAll(".bar")
    .data(data)
    .enter().append("path")
    .attr("class", "bar")
    .style("fill", function(d) { return d.bar_color; })
    .attr("d", function(d) { 
         return /* a path data string that completely describes this shape */; 
    })
    .on("mouseover",
        /* etc */

现在,让我们分析一下那个形状。从左下角开始,您需要以下方向来绘制它:

  • 移动到酒吧之间填充物中间的轴线上的膨胀基底的起点。
  • 弯曲到距轴线等距离的条的左侧边缘,与膨胀半径相同。
  • 几乎直线到达栏杆的顶部,最高点是条宽的一半的顶部曲率半径。
  • 半圆(或两个曲线)一直上升到完整的栏杆高度,然后回到另一侧。
  • 几乎沿轴线向下的直线。
  • 创建另一个膨胀的基底,向外弯曲到填充物的一半。
  • 关闭路径以连接回开始。

对于移动命令和线路命令,您只需要提供目标(x,y)点。对于关闭命令,您根本不需要提供任何点。对于曲线,情况会变得有点复杂。您可以使用弧形(请参阅我的第二个教程的语法),但是如果您使用二次曲线,控制点是您要围绕的角落位置,那么它会变得稍微简单一些,并且看起来几乎相同。也就是说,从(0,0)到(1,1)的二次曲线,控制点为(0,1),几乎是以(1,0)为中心的弧线。

(0,0)
  @ —  *  << control point (0,1)
      \
       |     ...something like this, but prettier!
       @
     (1,1)

有了这个攻击计划,并确定从作为参数传递给x.rangeRoundBands()的paddingProportion中得出的flare的宽度,您可以获得以下结果:

.attr("d", function(d) { 
    var barWidth = x.rangeBand();
    var paddingWidth = barWidth*(paddingProportion)/(1-paddingProportion);
    var flareRadius = paddingWidth/2;
    var topRadius = barWidth/2;

    var xPos =  x(d.episode);
    var yPos =  y(d.donations);

    return [ "M", [ (xPos - flareRadius), height], //start at bottom left of base
             "Q", [xPos, height], //control point for flare curve
                    [xPos, (height-flareRadius)], //end point of flare curve
             "L", [xPos, (yPos + topRadius)], //line to start of top curve
             "Q", [xPos, yPos], //control point for left top curve
                    [(xPos + topRadius), yPos],  //end point for left top curve
             "Q", [(xPos + barWidth), yPos],  //control point for right top curve
                    [(xPos + barWidth), (yPos + topRadius)], //end point for right top
             "L", [(xPos + barWidth), (height-flareRadius)], //line to start of right flare
             "Q", [(xPos + barWidth), height], //control for right flare
                    [(xPos + barWidth + flareRadius), height], //end of right flare
             "Z" //close the path
            ].join(" ");

})

最终返回的值是一个字符串,我将它组合成字母数组和每个(x,y)点的两个元素数组,然后用空格将它们全部连接在一起。这比使用 + 更快,并保持数据处于有组织的结构中。最终返回的字符串看起来像

http://jsfiddle.net/rdesai/MjFgK/62/

M 47.75,195 Q 52,195 52,190.75 L 52,26.056240786240778 Q 52,9.056240786240778 69,9.056240786240778 Q 86,9.056240786240778 86,26.056240786240778 L 86,190.75 Q 86,195 90.25,195 Z

现在,这基本上是您想要的形状,但在最短的条形图上会有一些混乱,其中顶部曲线的半径大于条形的高度。进行一些快速的最大/最小值检查,以确保点的y值永远不会小于yPos或大于height,可以使事情变得清晰:

.attr("d", function(d) { 
    var barWidth = x.rangeBand();
    var paddingWidth = barWidth*(paddingProportion)/(1-paddingProportion);
    var flareRadius = paddingWidth/2;
    var topRadius = barWidth/2;

    var xPos =  x(d.episode);
    var yPos =  y(d.donations);

    return [ "M", [ (xPos - flareRadius), height], //start at bottom left of base
             "Q", [xPos, height], 
                    [xPos, Math.max((height-flareRadius), yPos)], 
             "L", [xPos,  Math.min((yPos + topRadius), height)],
             "Q", [xPos, yPos], 
                    [(xPos + topRadius), yPos],
             "Q", [(xPos + barWidth), yPos], 
                    [(xPos + barWidth), Math.min((yPos + topRadius), height)],
             "L", [(xPos + barWidth), Math.max((height-flareRadius), yPos)],
             "Q", [(xPos + barWidth), height], 
                    [(xPos + barWidth + flareRadius), height],
             "Z"
            ].join(" ");

})

http://jsfiddle.net/MjFgK/63/

这是很多内容,建议你花些时间去尝试一下,试着变换形状,尝试使用弧线替代二次曲线,试着重新创建样本图像中第一个和最后一个条的形状。或者回到最初的计划,将基础形状作为单独的元素创建。


你需要让这个函数依赖于 i (柱子的索引),如果 i 为零,则将起点改为在柱内而不是柱外(也许可以改变第一条曲线的半径,使用与柱顶相同的半径)。同样,如果 i 等于数据长度,则移动最后一条曲线的点。或者作为另一种选择,您可以向整个图添加一个带有圆角的矩形的剪辑路径...那可能更容易些! - AmeliaBR
对于轴:首先绘制一个默认的d3轴,然后重新选择所有“g.tick”元素,并在每个元素内添加一个圆。您可以选择选择并删除刻度线,或者只将其设置为零长度。有关自定义d3轴的更多信息,请单击此处。这是一个快速示例,展示如何使用圆形刻度,该示例改编自上述链接中的示例:http://fiddle.jshell.net/zUj3E/6/ - AmeliaBR
1
已更新修复了Firefox的问题(如果SVG没有明确声明为块或内联块元素,则会出现.getComputedStyle()错误),并更改了水平轴(您需要设置轴的.orient()属性,x和y只是标签/类名)。http://fiddle.jshell.net/zUj3E/7/ - AmeliaBR
它之前是可以工作的,我只是忘记更改我的范围,它仍然基于SVG高度。而且,虽然Chrome的SVG默认高度填充窗口,但Firefox的默认高度是5em。抱歉,我应该实际测试一下... http://fiddle.jshell.net/zUj3E/9/ - AmeliaBR
我在IE11中尝试过,包括IE9和10的兼容模式,最后的fiddle看起来很好。但是很少有d3示例使用响应式设计的原因是--处理不同浏览器大小SVG的方式相当麻烦。 - AmeliaBR
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接