svg/vectorFeature.js

  1. var inherit = require('../inherit');
  2. var registerFeature = require('../registry').registerFeature;
  3. var vectorFeature = require('../vectorFeature');
  4. /* These markers are available to all instances of the vectorFeature. */
  5. var markerConfigs = {
  6. arrow: {
  7. attrs: {
  8. class: 'geo-vector-arrow geo-vector-marker',
  9. viewBox: '0 0 10 10',
  10. refX: 1,
  11. refY: 5,
  12. markerHeight: 5,
  13. markerWidth: 5,
  14. orient: 'auto'
  15. },
  16. path: 'M 0 0 L 10 5 L 0 10 z'
  17. },
  18. point: {
  19. attrs: {
  20. class: 'geo-vector-point geo-vector-marker',
  21. viewBox: '0 0 12 12',
  22. refX: 6,
  23. refY: 6,
  24. markerHeight: 8,
  25. markerWidth: 8,
  26. orient: 'auto'
  27. },
  28. path: 'M 6 3 A 3 3 0 1 1 5.99999 3 Z'
  29. },
  30. bar: {
  31. attrs: {
  32. class: 'geo-vector-bar geo-vector-marker',
  33. viewBox: '0 0 10 10',
  34. refX: 0,
  35. refY: 5,
  36. markerHeight: 6,
  37. markerWidth: 6,
  38. orient: 'auto'
  39. },
  40. path: 'M 0 0 L 2 0 L 2 10 L 0 10 z'
  41. },
  42. wedge: {
  43. attrs: {
  44. class: 'geo-vector-wedge geo-vector-marker',
  45. viewBox: '0 0 10 10',
  46. refX: 10,
  47. refY: 5,
  48. markerHeight: 5,
  49. markerWidth: 5,
  50. orient: 'auto'
  51. },
  52. path: 'M 0 0 L 1 0 L 10 5 L 1 10 L 0 10 L 9 5 L 0 0'
  53. }
  54. };
  55. /**
  56. * Create a new instance of svg.vectorFeature.
  57. *
  58. * @class
  59. * @alias geo.svg.vectorFeature
  60. * @extends geo.vectorFeature
  61. * @extends geo.svg.object
  62. * @param {geo.vectorFeature.spec} arg Feature options.
  63. * @returns {geo.vectorFeature}
  64. */
  65. var svg_vectorFeature = function (arg) {
  66. 'use strict';
  67. if (!(this instanceof svg_vectorFeature)) {
  68. return new svg_vectorFeature(arg);
  69. }
  70. var object = require('./object');
  71. var timestamp = require('../timestamp');
  72. var d3 = require('./svgRenderer').d3;
  73. arg = arg || {};
  74. vectorFeature.call(this, arg);
  75. object.call(this);
  76. /**
  77. * @private
  78. */
  79. var m_this = this,
  80. s_exit = this._exit,
  81. s_update = this._update,
  82. m_buildTime = timestamp(),
  83. m_style = {};
  84. /**
  85. * Generate a unique ID for a marker definition.
  86. *
  87. * @param {object} d The marker datum (unused).
  88. * @param {number} i The marker index.
  89. * @param {string} position The marker's vector position (`'head'` or
  90. * `'tail'`).
  91. * @returns {string} The constructed ID.
  92. */
  93. function markerID(d, i, position) {
  94. return m_this._svgid() + '_marker_' + i + '_' + position;
  95. }
  96. /**
  97. * Add marker styles for vector arrows.
  98. *
  99. * @param {object[]} data The vector data array.
  100. * @param {function} stroke The stroke accessor.
  101. * @param {function} opacity The opacity accessor.
  102. * @param {function} originStyle The marker style for the vector head.
  103. * @param {function} endStyle The marker style for the vector tail.
  104. */
  105. function updateMarkers(data, stroke, opacity, originStyle, endStyle) {
  106. //this allows for multiple vectorFeature in a layer
  107. var markerGroup = m_this.renderer()._definitions()
  108. .selectAll('g.marker-group#' + m_this._svgid())
  109. .data(data.length ? [1] : []);
  110. markerGroup
  111. .enter()
  112. .append('g')
  113. .attr('id', m_this._svgid)
  114. .attr('class', 'marker-group');
  115. markerGroup.exit().remove();
  116. var markers = data.reduce(function (markers, d, i) {
  117. var head = markerConfigs[endStyle(d, i)];
  118. var tail = markerConfigs[originStyle(d, i)];
  119. if (head) {
  120. markers.push({
  121. data: d,
  122. dataIndex: i,
  123. head: true
  124. });
  125. }
  126. if (tail) {
  127. markers.push({
  128. data: d,
  129. dataIndex: i,
  130. head: false
  131. });
  132. }
  133. return markers;
  134. }, []);
  135. markerGroup = m_this.renderer()._definitions()
  136. .selectAll('g.marker-group#' + m_this._svgid());
  137. var sel = markerGroup
  138. .selectAll('marker.geo-vector-marker')
  139. .data(markers);
  140. var renderer = m_this.renderer();
  141. sel.enter()
  142. .append('marker')
  143. .each(function (d) {
  144. var marker = d3.select(this);
  145. var markerData = d.head ? markerConfigs[endStyle(d.data, d.dataIndex)] : markerConfigs[originStyle(d.data, d.dataIndex)];
  146. Object.keys(markerData.attrs).forEach(function (attrName) {
  147. marker.attr(attrName, markerData.attrs[attrName]);
  148. });
  149. })
  150. .attr('id', function (d) {
  151. return markerID(d.data, d.dataIndex, d.head ? 'head' : 'tail');
  152. })
  153. .style('stroke', function (d) {
  154. return renderer._convertColor(stroke)(d.data, d.dataIndex);
  155. })
  156. .style('fill', function (d) {
  157. return renderer._convertColor(stroke)(d.data, d.dataIndex);
  158. })
  159. .style('opacity', function (d) {
  160. return opacity(d.data, d.dataIndex);
  161. })
  162. .append('path')
  163. .attr('d', function (d) {
  164. return d.head ? markerConfigs[endStyle(d.data, d.dataIndex)].path : markerConfigs[originStyle(d.data, d.dataIndex)].path;
  165. });
  166. sel.exit().remove();
  167. }
  168. /**
  169. * Build.
  170. *
  171. * @returns {this}.
  172. */
  173. this._build = function () {
  174. var data = m_this.data(),
  175. s_style = m_this.style.get(),
  176. m_renderer = m_this.renderer(),
  177. orig_func = m_this.origin(),
  178. size_func = m_this.delta(),
  179. cache = [],
  180. scale = m_this.style('scale'),
  181. max = 0;
  182. // call super-method
  183. s_update.call(m_this);
  184. // default to empty data array
  185. if (!data) { data = []; }
  186. // cache the georeferencing
  187. cache = data.map(function (d, i) {
  188. var origin = m_this.featureGcsToDisplay(orig_func(d, i)),
  189. delta = size_func(d, i);
  190. max = Math.max(max, delta.x * delta.x + delta.y * delta.y);
  191. return {
  192. x1: origin.x,
  193. y1: origin.y,
  194. dx: delta.x,
  195. dy: -delta.y
  196. };
  197. });
  198. max = Math.sqrt(max);
  199. if (!scale) {
  200. scale = 75 / (max ? max : 1);
  201. }
  202. function getScale() {
  203. return scale / m_renderer.scaleFactor();
  204. }
  205. // fill in svg renderer style object defaults
  206. m_style.id = m_this._svgid();
  207. m_style.data = data;
  208. m_style.append = 'line';
  209. m_style.attributes = {
  210. x1: function (d, i) {
  211. return cache[i].x1;
  212. },
  213. y1: function (d, i) {
  214. return cache[i].y1;
  215. },
  216. x2: function (d, i) {
  217. return cache[i].x1 + getScale() * cache[i].dx;
  218. },
  219. y2: function (d, i) {
  220. return cache[i].y1 + getScale() * cache[i].dy;
  221. },
  222. 'marker-start': function (d, i) {
  223. return 'url(#' + markerID(d, i, 'tail') + ')';
  224. },
  225. 'marker-end': function (d, i) {
  226. return 'url(#' + markerID(d, i, 'head') + ')';
  227. }
  228. };
  229. m_style.style = {
  230. stroke: function () { return true; },
  231. strokeColor: s_style.strokeColor,
  232. strokeWidth: s_style.strokeWidth,
  233. strokeOpacity: s_style.strokeOpacity,
  234. originStyle: s_style.originStyle,
  235. endStyle: s_style.endStyle
  236. };
  237. m_style.classes = ['svgVectorFeature'];
  238. m_style.visible = m_this.visible;
  239. // Add markers to the definition list
  240. updateMarkers(data, s_style.strokeColor, s_style.strokeOpacity, s_style.originStyle, s_style.endStyle);
  241. // pass to renderer to draw
  242. m_this.renderer()._drawFeatures(m_style);
  243. // update time stamps
  244. m_buildTime.modified();
  245. m_this.updateTime().modified();
  246. return m_this;
  247. };
  248. /**
  249. * Update.
  250. *
  251. * @returns {this}
  252. */
  253. this._update = function () {
  254. s_update.call(m_this);
  255. if (m_this.timestamp() >= m_buildTime.timestamp()) {
  256. m_this._build();
  257. } else {
  258. updateMarkers(
  259. m_style.data,
  260. m_style.style.strokeColor,
  261. m_style.style.strokeOpacity,
  262. m_style.style.originStyle,
  263. m_style.style.endStyle
  264. );
  265. }
  266. return m_this;
  267. };
  268. /**
  269. * Exit. Remove markers.
  270. */
  271. this._exit = function () {
  272. s_exit.call(m_this);
  273. m_style = {};
  274. updateMarkers([], null, null, null, null);
  275. };
  276. this._init(arg);
  277. return this;
  278. };
  279. svg_vectorFeature.markerConfigs = markerConfigs;
  280. inherit(svg_vectorFeature, vectorFeature);
  281. // Now register it
  282. registerFeature('svg', 'vector', svg_vectorFeature);
  283. module.exports = svg_vectorFeature;