javascript - SVG 查找路径的旋转 Angular

标签 javascript svg jvectormap

我的 SVG map 有问题。 我使用 jVectorMap 创建自定义 map ,我需要在字段中心写入每个字段的名称。 示例为: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() 函数,但它总是为每个字段提供相同的值。

如何找到每个字段的正确旋转 Angular ?

谢谢大家!

最佳答案

看起来像squeamish ossifrage在这方面打败了我,他们所说的也正是我的方法......

解决方案

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

enter image description here

示例

这是一个sample jsfiddle

在 fiddle 的 $(document).ready() 函数中,我向所有区域添加标签,但您会注意到某些区域的质心不在区域或非直边会导致问题 - 稍微修改 map 可能是最简单的解决方法。

说明

以下是我编写的用于演示原理的 3 个函数:

  • addOrientatedLabel(regionName) - 向 map 的指定区域添加标签。
  • getAngleInDegreesFromRegion(regionName) - 获取区域最长边缘的 Angular
  • getLengthSquared(startPt,endPt) - 获取线段长度的平方(比获取长度更有效)。

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

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

被解释为先旋转,然后平移。这个顺序很重要!

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

getAngleInDegreesFromRegion() 是完成所有工作的地方。它使用选择器获取区域的 SVG 路径,然后循环路径中的每个点。每当发现一个点是线段的一部分(而不是移动到或其他指令)时,它就会计算线段的平方长度。如果线段的平方长度是迄今为止最长的,则它会存储其详细信息。我使用平方长度,因为这样可以节省执行平方根运算(它仅用于比较目的,因此平方长度就可以了)。

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

一旦我们有了最长的线,我就用 Math.atan2 计算它相对于 x 轴的 Angular ,并用 (angle/Math.PI) 将其从弧度转换为 SVG 的度数)* 180。最后一个技巧是确定该 Angular 是否会将文本上下旋转,如果是,则再旋转 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);    
}

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

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; 
}

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

if (pt1.pathSegType != SVGPathSeg.PATHSEG_MOVETO_ABS ) 

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

最后,这是为了完整性而必须采用的平方距离方法:

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

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

关于javascript - SVG 查找路径的旋转 Angular ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27557040/

相关文章:

javascript - 在 d3 中为每个数据元素添加多个矩形

javascript - d3 获取具有特殊字符的属性

javascript - 如何从 D3 按钮调用 javascript 函数

css - SVG 图案和渐变在一起

javascript - 将 svg map 转换为 jvectormap

JVectorMap - 使用按钮选择区域

javascript - 尝试渲染从 elastic-search 收到的 JSON 字典,但出现 [object Promise] 错误

JavaScript 转义序列

javascript - 移动设备上的 jVectorMap 需要点击 2 次

javascript - 在路由中导航时保留参数