2014-12-18 4 views
2

У меня проблема с моей SVG-картой. Я использую jVectorMap для создания пользовательской карты, и мне нужно написать имя каждого поля в центре поля. Примером является: JSFiddle Example (увеличение в правой части, чтобы увидеть текст)SVG найти угол поворота пути

я могу найти центр каждого поля с помощью этой функции:

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

Похоже squeamish ossifrage избил меня к этому, и то, что они сказали бы точно мой подход тоже ...

Решение

по существу найти самый длинный отрезок, в EAC h, а затем сориентируйте текст, чтобы выровнять его с этим сегментом линии, пытаясь обеспечить, чтобы текст не перевернулся вверх (!)

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 горизонтальным, так что если в регионе нет сегментов линии, вы, по крайней мере, получите горизонтальный текст.

Как только у нас самая длинная линия, я вычисляю ее угол относительно оси x с помощью Math.atan2 и преобразую его из радианов в градусы для SVG с (angle/Math.PI) * 180. Конечный трюк заключается в том, чтобы определить, будет ли угол поворачивать текст вверх дном, и если да, поверните еще 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() метод только рассматривать самый длинный прямой line в пути, если он создан с помощью команды 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)); 
} 

Надеюсь, что это достаточно ясно, чтобы помочь вам начать работу.

+0

Решение проблем, когда центр тяжести находится за пределами центра региона, вероятно, является самой сложной проблемой. Если вы относитесь ко всем изогнутым линиям как прямые, он выполняет «хорошо» работу по выравниванию текста даже в областях с изогнутыми краями. Как уже упоминалось, внесение некоторых изменений в ваши данные карты (если возможно), вероятно, является самым простым решением для регионов, которые не работают хорошо. –

+0

У меня есть правильная функция для решения проблем на каком-то пути SVG. Проблема в том, что некоторые пути выражаются в относительном значении, поэтому я добавил функцию из этого ответа в StackOverflow: [convert-svg-path-to-absolute-commands] (http://stackoverflow.com/questions/ 9677885/convert-svg-path-to-absolute-commands) , а затем я добавил строки, чтобы проверить, является ли путь относительным и в конечном итоге вызывает функцию convertToAbsolute(): if (svgPath.getAttribute ("d") .search ("l")> -1) { convertToAbsolute (svgPath); } Пожалуйста, добавьте это изменение на будущее. Еще раз спасибо! –

+0

Это правильный пример на JSFiddle http://jsfiddle.net/Lee53kwr/27/ –

2

Запрос getCTM() не поможет. Все, что дает вам, - это матрица преобразования для системы координат фигуры (которая, как вы обнаружили, одинакова для каждой формы). Чтобы получить координаты вершин формы, вам нужно будет изучить содержимое region.element.shape.pathSegList.

Это может стать беспорядочным. Хотя многие фигуры рисуются с использованием простых команд «move-to» и «line-to» с абсолютными координатами, некоторые используют относительные координаты и другие типы линий. Я заметил хотя бы одну кубическую кривую. Возможно, стоит искать библиотеку манипуляций с вершинами 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); 
Смежные вопросы