var inherit = require('../inherit');
var registerFeature = require('../registry').registerFeature;
var vectorFeature = require('../vectorFeature');
/* These markers are available to all instances of the vectorFeature. */
var markerConfigs = {
arrow: {
attrs: {
class: 'geo-vector-arrow geo-vector-marker',
viewBox: '0 0 10 10',
refX: 1,
refY: 5,
markerHeight: 5,
markerWidth: 5,
orient: 'auto'
},
path: 'M 0 0 L 10 5 L 0 10 z'
},
point: {
attrs: {
class: 'geo-vector-point geo-vector-marker',
viewBox: '0 0 12 12',
refX: 6,
refY: 6,
markerHeight: 8,
markerWidth: 8,
orient: 'auto'
},
path: 'M 6 3 A 3 3 0 1 1 5.99999 3 Z'
},
bar: {
attrs: {
class: 'geo-vector-bar geo-vector-marker',
viewBox: '0 0 10 10',
refX: 0,
refY: 5,
markerHeight: 6,
markerWidth: 6,
orient: 'auto'
},
path: 'M 0 0 L 2 0 L 2 10 L 0 10 z'
},
wedge: {
attrs: {
class: 'geo-vector-wedge geo-vector-marker',
viewBox: '0 0 10 10',
refX: 10,
refY: 5,
markerHeight: 5,
markerWidth: 5,
orient: 'auto'
},
path: 'M 0 0 L 1 0 L 10 5 L 1 10 L 0 10 L 9 5 L 0 0'
}
};
/**
* Create a new instance of svg.vectorFeature.
*
* @class
* @alias geo.svg.vectorFeature
* @extends geo.vectorFeature
* @extends geo.svg.object
* @param {geo.vectorFeature.spec} arg Feature options.
* @returns {geo.vectorFeature}
*/
var svg_vectorFeature = function (arg) {
'use strict';
if (!(this instanceof svg_vectorFeature)) {
return new svg_vectorFeature(arg);
}
var object = require('./object');
var timestamp = require('../timestamp');
var d3 = require('./svgRenderer').d3;
arg = arg || {};
vectorFeature.call(this, arg);
object.call(this);
/**
* @private
*/
var m_this = this,
s_exit = this._exit,
s_update = this._update,
m_buildTime = timestamp(),
m_style = {};
/**
* Generate a unique ID for a marker definition.
*
* @param {object} d The marker datum (unused).
* @param {number} i The marker index.
* @param {string} position The marker's vector position (`'head'` or
* `'tail'`).
* @returns {string} The constructed ID.
*/
function markerID(d, i, position) {
return m_this._svgid() + '_marker_' + i + '_' + position;
}
/**
* Add marker styles for vector arrows.
*
* @param {object[]} data The vector data array.
* @param {function} stroke The stroke accessor.
* @param {function} opacity The opacity accessor.
* @param {function} originStyle The marker style for the vector head.
* @param {function} endStyle The marker style for the vector tail.
*/
function updateMarkers(data, stroke, opacity, originStyle, endStyle) {
//this allows for multiple vectorFeature in a layer
var markerGroup = m_this.renderer()._definitions()
.selectAll('g.marker-group#' + m_this._svgid())
.data(data.length ? [1] : []);
markerGroup
.enter()
.append('g')
.attr('id', m_this._svgid)
.attr('class', 'marker-group');
markerGroup.exit().remove();
var markers = data.reduce(function (markers, d, i) {
var head = markerConfigs[endStyle(d, i)];
var tail = markerConfigs[originStyle(d, i)];
if (head) {
markers.push({
data: d,
dataIndex: i,
head: true
});
}
if (tail) {
markers.push({
data: d,
dataIndex: i,
head: false
});
}
return markers;
}, []);
markerGroup = m_this.renderer()._definitions()
.selectAll('g.marker-group#' + m_this._svgid());
var sel = markerGroup
.selectAll('marker.geo-vector-marker')
.data(markers);
var renderer = m_this.renderer();
sel.enter()
.append('marker')
.each(function (d) {
var marker = d3.select(this);
var markerData = d.head ? markerConfigs[endStyle(d.data, d.dataIndex)] : markerConfigs[originStyle(d.data, d.dataIndex)];
Object.keys(markerData.attrs).forEach(function (attrName) {
marker.attr(attrName, markerData.attrs[attrName]);
});
})
.attr('id', function (d) {
return markerID(d.data, d.dataIndex, d.head ? 'head' : 'tail');
})
.style('stroke', function (d) {
return renderer._convertColor(stroke)(d.data, d.dataIndex);
})
.style('fill', function (d) {
return renderer._convertColor(stroke)(d.data, d.dataIndex);
})
.style('opacity', function (d) {
return opacity(d.data, d.dataIndex);
})
.append('path')
.attr('d', function (d) {
return d.head ? markerConfigs[endStyle(d.data, d.dataIndex)].path : markerConfigs[originStyle(d.data, d.dataIndex)].path;
});
sel.exit().remove();
}
/**
* Build.
*
* @returns {this}.
*/
this._build = function () {
var data = m_this.data(),
s_style = m_this.style.get(),
m_renderer = m_this.renderer(),
orig_func = m_this.origin(),
size_func = m_this.delta(),
cache = [],
scale = m_this.style('scale'),
max = 0;
// call super-method
s_update.call(m_this);
// default to empty data array
if (!data) { data = []; }
// cache the georeferencing
cache = data.map(function (d, i) {
var origin = m_this.featureGcsToDisplay(orig_func(d, i)),
delta = size_func(d, i);
max = Math.max(max, delta.x * delta.x + delta.y * delta.y);
return {
x1: origin.x,
y1: origin.y,
dx: delta.x,
dy: -delta.y
};
});
max = Math.sqrt(max);
if (!scale) {
scale = 75 / (max ? max : 1);
}
function getScale() {
return scale / m_renderer.scaleFactor();
}
// fill in svg renderer style object defaults
m_style.id = m_this._svgid();
m_style.data = data;
m_style.append = 'line';
m_style.attributes = {
x1: function (d, i) {
return cache[i].x1;
},
y1: function (d, i) {
return cache[i].y1;
},
x2: function (d, i) {
return cache[i].x1 + getScale() * cache[i].dx;
},
y2: function (d, i) {
return cache[i].y1 + getScale() * cache[i].dy;
},
'marker-start': function (d, i) {
return 'url(#' + markerID(d, i, 'tail') + ')';
},
'marker-end': function (d, i) {
return 'url(#' + markerID(d, i, 'head') + ')';
}
};
m_style.style = {
stroke: function () { return true; },
strokeColor: s_style.strokeColor,
strokeWidth: s_style.strokeWidth,
strokeOpacity: s_style.strokeOpacity,
originStyle: s_style.originStyle,
endStyle: s_style.endStyle
};
m_style.classes = ['svgVectorFeature'];
m_style.visible = m_this.visible;
// Add markers to the definition list
updateMarkers(data, s_style.strokeColor, s_style.strokeOpacity, s_style.originStyle, s_style.endStyle);
// pass to renderer to draw
m_this.renderer()._drawFeatures(m_style);
// update time stamps
m_buildTime.modified();
m_this.updateTime().modified();
return m_this;
};
/**
* Update.
*
* @returns {this}
*/
this._update = function () {
s_update.call(m_this);
if (m_this.timestamp() >= m_buildTime.timestamp()) {
m_this._build();
} else {
updateMarkers(
m_style.data,
m_style.style.strokeColor,
m_style.style.strokeOpacity,
m_style.style.originStyle,
m_style.style.endStyle
);
}
return m_this;
};
/**
* Exit. Remove markers.
*/
this._exit = function () {
s_exit.call(m_this);
m_style = {};
updateMarkers([], null, null, null, null);
};
this._init(arg);
return this;
};
svg_vectorFeature.markerConfigs = markerConfigs;
inherit(svg_vectorFeature, vectorFeature);
// Now register it
registerFeature('svg', 'vector', svg_vectorFeature);
module.exports = svg_vectorFeature;