SVG找到路径的旋转角度

时间:2014-12-18 22:46:17

标签: javascript svg jvectormap

我的SVG地图存在问题。 我使用jVectorMap创建自定义地图,我需要在字段中心写下每个字段的名称。 示例是:JSFiddle Example(放大右侧以查看文字)

我可以使用此功能找到每个字段的中心:

jvm.Map.prototype.getRegionCentroid = function(region){

    if(typeof region == "string")
        region = this.regions[region.toUpperCase()];

    var bbox = region.element.shape.getBBox(),
    xcoord = (bbox.x + bbox.width/2),
    ycoord = (bbox.y + bbox.height/2);

    return [xcoord, ycoord];
};

但我的问题是我想旋转文本以使其与相对字段的顶行对齐。 我已尝试使用getCTM()函数,但它为每个字段提供的值始终相同。

如何找到每个场的正确旋转角度?

谢谢大家!

2 个答案:

答案 0 :(得分:2)

查询getCTM()无济于事。所有这些都是形状坐标系的变换矩阵(正如您所发现的,对于每个形状都是相同的)。要获得形状的顶点坐标,您必须检查region.element.shape.pathSegList的内容。

这可能会变得混乱。尽管使用具有绝对坐标的简单“移动到”和“行到”命令绘制了许多形状,但是一些使用相对坐标和其他类型的线。我注意到至少有一条立方曲线。可能值得寻找一个SVG顶点操作库,以使生活更轻松。

但总的来说,您需要做的是获取每个形状的坐标列表(在必要时将相对坐标转换为绝对坐标),并找到长度最长的段。请注意,这可能是该行的两个端点之间的段。您可以从Math.atan2(y_end-y_start,x_end-x_start)轻松找到此细分的方向。

旋转文字时,使用带有<g>属性的transform=translate()元素将坐标原点移动到文本所需的位置,让自己更轻松。然后,当您向其添加transform=rotate()属性时,文本不会射入距离。另外,使用text-anchor="middle"dominant-baseline="middle"将文本置于您想要的位置。

您的代码最终应该看起来像这样:

var svg = document.getElementsByTagName('g')[0]; //Get svg element
var shape_angle = get_orientation_of_longest_segment(svg.pathSegList); //Write this function
var newGroup = document.createElementNS("http://www.w3.org/2000/svg","g");
var newText = document.createElementNS("http://www.w3.org/2000/svg","text");
newGroup.setAttribute("transform", "translate("+coords[0]+","+coords[1]+")"); 
newText.setAttribute("font-size","4");
newText.setAttribute("text-anchor","middle");
newText.setAttribute("dominant-baseline","middle");
newText.setAttribute("transform","rotate("+shape_angle+")");
newText.setAttribute('font-family', 'MyriadPro-It');
var textNode = document.createTextNode("C1902");
newText.appendChild(textNode);
newGroup.appendChild(newText);
svg.appendChild(newGroup);

答案 1 :(得分:2)

看起来squeamish ossifrage已经打败了我,而且他们所说的也是完全我的方法......

<强>解决方案

基本上找到每个区域路径中最长的线段,然后将文本定向为与该线段对齐,同时尝试确保文本不会颠倒(!)

enter image description here

示例

这里是sample jsfiddle

在小提琴的$(document).ready()功能中,我向所有区域添加标签,但您会注意到某些区域的质心不在该区域内或非直边缘导致问题 - 稍微修改地图可能是最容易修复的问题。

<强>解释

以下是我为证明原则而编写的3个函数:

  • addOrientatedLabel(regionName) - 为地图的指定区域添加标签。
  • getAngleInDegreesFromRegion(regionName) - 获取区域最长边的角度
  • getLengthSquared(startPt,endPt) - 获取行seg的长度平方(比获取长度更有效)。

addOrientatedLabel()使用平移变换将标签放置在质心处,并将文本旋转到与区域中最长线段相同的角度。在SVG中,转换从右到左解析,所以:

transform="translate(x,y) rotate(45)"

首先被解释为旋转,然后翻译。这种排序很重要!

它还使用了text-anchor="middle"dominant-baseline="middle",正如squeamish ossifrage所解释的那样。如果不这样做,将导致文本在其区域内错位。

getAngleInDegreesFromRegion()是完成所有工作的地方。它使用选择器获取区域的SVG路径,然后循环遍历路径中的每个点。只要找到一个点是线段的一部分(而不是 Move-To 或其他指令),它就会计算线段的平方长度。如果线段的平方长度到目前为止最长,则存储其细节。我使用平方长度,因为这节省了执行平方根操作(它仅用于比较目的,因此平方长度很好)。

请注意,我将longestLine数据初始化为水平数据,以便如果该区域根本没有线段,则至少会获得水平文本。

一旦我们得到最长的线,我用Math.atan2计算它相对于x轴的角度,并用(angle / Math.PI) * 180将其从弧度转换为SVG的度数。最后一招是确定角度是否会将文本上下颠倒,如果是,则再旋转180度。

注意

之前我没有使用过SVG,因此我的SVG代码可能不是最佳的,但它已经过测试,适用于主要由直线段组成的所有区域 - 您需要添加错误检查当然是生产应用!

代码

function addOrientatedLabel(regionName) {

    var angleInDegrees = getAngleInDegreesFromRegion(regionName);

    var map = $('#world-map').vectorMap('get', 'mapObject');
    var coords = map.getRegionCentroid(regionName);

    var svg = document.getElementsByTagName('g')[0]; //Get svg element
    var newText = document.createElementNS("http://www.w3.org/2000/svg","text");
    newText.setAttribute("font-size","4");
    newText.setAttribute("text-anchor","middle");
    newText.setAttribute("dominant-baseline","middle");
    newText.setAttribute('font-family', 'MyriadPro-It');
    newText.setAttribute('transform', 'translate(' + coords[0] + ',' + coords[1] + ') rotate(' + angleInDegrees + ')');
    var textNode = document.createTextNode(regionName);
    newText.appendChild(textNode);

    svg.appendChild(newText);    
}

这是我在给定地图区域路径中找到最长线段的方法:

function getAngleInDegreesFromRegion(regionName) {

    var svgPath = document.getElementById(regionName);

    /* longest edge will default to a horizontal line */
    /* (in case the shape is degenerate): */
    var longestLine = { startPt: {x:0, y:0}, endPt: {x:100,y:0}, lengthSquared : 0 };

    /* loop through all the points looking for the longest line segment: */
    for (var i = 0 ; i < svgPath.pathSegList.numberOfItems-1; i++) {
        var pt0 = svgPath.pathSegList.getItem(i);
        var pt1 = svgPath.pathSegList.getItem(i+1);
        if (pt1.pathSegType == SVGPathSeg.PATHSEG_LINETO_ABS) {
            var lengthSquared = getLengthSquared(pt0, pt1);
            if( lengthSquared > longestLine.lengthSquared ) {
                longestLine = { startPt:pt0, endPt:pt1, lengthSquared:lengthSquared};                 
            }            
        }/* end if dealing with line segment */                   
    }/* end loop through all pts in svg path */    

    /* determine angle of longest line segement relative to x axis */
    var dY = longestLine.startPt.y - longestLine.endPt.y;
    var dX = longestLine.startPt.x - longestLine.endPt.x;
    var angleInDegrees = ( Math.atan2(dY,dX) / Math.PI * 180.0);

    /* if text would be upside down, rotate through 180 degrees: */
    if( (angleInDegrees > 90 && angleInDegrees < 270) || (angleInDegrees < -90 && angleInDegrees > -270)) {
        angleInDegrees += 180;
        angleInDegrees %= 360;
    }    

    return angleInDegrees; 
}

请注意,如果使用getAngleInDegreesFromRegion() SVG命令创建路径,我的PATHSEG_LINETO_ABS方法将考虑路径中最长的直线...您将...需要更多功能来处理不包含直线的区域。您可以通过将曲线视为直线来近似:

if (pt1.pathSegType != SVGPathSeg.PATHSEG_MOVETO_ABS ) 

但是会出现一些极端情况,因此修改地图数据可能是最简单的方法。

最后,这里是完整性的强制平方距离方法:

function getLengthSquared(startPt, endPt ) {
    return ((startPt.x - endPt.x) * (startPt.x - endPt.x)) + ((startPt.y - endPt.y) * (startPt.y - endPt.y));
}

希望足够清楚,以帮助您入门。