我的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()函数,但它为每个字段提供的值始终相同。
如何找到每个场的正确旋转角度?
谢谢大家!
答案 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已经打败了我,而且他们所说的也是完全我的方法......
<强>解决方案强>
基本上找到每个区域路径中最长的线段,然后将文本定向为与该线段对齐,同时尝试确保文本不会颠倒(!)
示例强>
在小提琴的$(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));
}
希望足够清楚,以帮助您入门。