A Google Map marker can take a complex svg path as its icon as in something like:
var baseSvg = {
x1 : "m 0,0 l45,0 l 190,225 l -45,0 l -190,-225 z",
x2 : "m 225,0 l -45,0 l -190,225 l 45,0 l 190,-225 z"
};
var baseIcon = {
path: "M0,0 " + baseSvg["x1"] + baseSvg["x2"],
fillColor: "#000000",
fillOpacity: 1,
scale: .2,
strokeColor: "black",
strokeWeight: 0,
rotation: 15
};
which is then fed into a marker:
var marker = new google.maps.Marker({
position: new google.maps.LatLng(somelat, somelng),
icon: baseIcon
});
All good. But it only draws in a single colour (black in this example). So, can they only have a single colour or is there a way to have multi-coloured symbols? Using the example code, x1 would be red and x2 would be green.
Note that this construct is borrowed from here: How do I indicate transparency in an SVG path in Google Maps API v3? and it works nicely.
I just did the same thing and I think you have to draw two markers with essentially identical data, but with the path and in this case fillColor properties changed:
var baseSvg = {
x1 : "m 0,0 l45,0 l 190,225 l -45,0 l -190,-225 z",
x2 : "m 225,0 l -45,0 l -190,225 l 45,0 l 190,-225 z"
},
baseIcon = {
fillOpacity: 1,
scale: .2,
strokeColor: "black",
strokeWeight: 0,
rotation: 15
},
markerPos = new google.maps.LatLng(somelat, somelng),
// psuedo code for cloning an object since that
// is out of scope for this question
greenIcon = Object.clone(baseIcon),
redIcon = Object.clone(baseIcon);
greenIcon.path = baseSvg.x1;
greenIcon.fillColor = "#0f0";
redIcon.path = baseSvg.x2;
redIcon.fillColor = "#f00";
var marker1 = new google.maps.Marker({
position: markerPos,
map: map,
icon: greenIcon
});
var marker2 = new google.maps.Marker({
position: markerPos,
map: map,
icon: redIcon
});
obviously not optimized js, but you get the idea.
When it comes to opacity, strokeOpacity is your guy. Check the maps.Symbol class for more information on symbol properties.
Related
I want to create an svg like that:
So, a circumference divided into two parts by a chord. The two sections must have different colors.
I want to draw this object in SVG. I think I need to use the PATH tag, right? Or is there another way to do it?
What points do I need to draw the object? I'm a bit confused..
Yes. It is a <path> element that you will need.
Paths always start with an M (move) command. You'll also need an A (arc) command, and probably an L line command for the line that bisects the circle.
For the arc command, you just need to know the X and Y coordinates of the start and end points (B and C). Plus the radius of the circle. It is important to have accurate coordinates for the start and end points of an arc command. Small discrepancies can cause the position of the circle to move around quite a bit.
In the following demo, I have chosen to calculate the B and C positions based on their angle from the centre. Plus setting the path description attribute from code, allows me to document for you what each of the parameters are for.
// Radius of the circle
var radius = 80;
// Centre coordinate of the circle
var Ox = 100;
var Oy = 100;
// Angles of each point from which we calculate their X and Y coordinates.
// Here, 0 degrees is East, and angle increases in a clockwise direction.
var angleB = 285; // degrees
var angleC = 35;
var B = angleToCoords(angleB, Ox, Oy, radius);
var C = angleToCoords(angleC, Ox, Oy, radius);
// Get the "major segment" path element
var majorPath = document.getElementById("major");
// Set the path description for the "major segment"
majorPath.setAttribute("d", ['M', B.x, B.y, // Move to point B
'L', C.x, C.y, // Line to point C
'A', radius, radius, // X radius and Y radius of the arc
0, // ellipse angle
1, // large arc flag (1 indicates we want the larger of the two possible arcs between the points
1, // clockwise direction flag
B.x, B.y, // arc end point is back at point B
'Z'].join(" ")); // Z command closes the path
// Get the "minor segment" path element
var minorPath = document.getElementById("minor");
// Set the path description for the "minor segment"
minorPath.setAttribute("d", ['M', B.x, B.y, // Move to point B
'A', radius, radius, // X radius and Y radius of the arc
0, // ellipse angle
0, // large arc flag (0 indicates we want the smaller of the two possible arcs between the points
1, // clockwise direction flag
C.x, C.y, // arc end point is at point C
'L', B.x, B.y, // Line to point B
'Z'].join(" ")); // Z command closes the path
// Function to convert from an angle to an X and Y position
function angleToCoords(angleInDegrees, centreX, centreY, radius)
{
var angleInRadians = angleInDegrees * Math.PI / 180;
return {
'x': centreX + (radius * Math.cos(angleInRadians)),
'y': centreY + (radius * Math.sin(angleInRadians))
}
}
path {
stroke: black;
stroke-width: 1;
}
#major {
fill: #78dcdc;
}
#minor {
fill: #aaffaa;
}
<svg width="200" height="200">
<path id="major" d="" />
<path id="minor" d="" />
</svg>
After profiling the code it looks like bbox function is being called repeatedly. I cannot remove marker-source, marker-target and connection-wrap because I need those features. Is there a way to improve performance?
Try to replace marker-source and marker-target with SVG Markers. It's relatively easy to implement if your application does not require different sizes and colors of markers. For instance:
Define you own marker arrow.
var arrowMarker = V('marker', {
viewBox: "0 0 10 10",
refX: 9,
refY: 5,
markerWidth: 6,
markerHeight: 6,
orient: "auto"
}, [
V('path', {
'd': "M 0 0 L 10 5 L 0 10 z",
'fill': 'green'
})
]);
Add the marker to paper's SVG Defs so it can be reused later for multiple times.
V(paper.defs).append(arrowMarker);
Finally put the marker on a link. Use the marker-end or marker-start property to define the target resp. source marker.
var link = new joint.dia.Link({
markup: '<path class="connection"/><path class="connection-wrap"/>',
attrs: {
'.connection': {
'stroke': 'green',
'stroke-width': 2,
'marker-end': 'url(#' + arrowMarker.attr('id') + ')'
}
}
});
There is a JSFiddle with an example and other useful performance tips.
Given the next information:
white point is the center of the circle.
blue, green point are points in the border of the circle.
orientation: clockwise, anticlockwise
Using any point (blue, green) with white point, then I can get the radius and I can draw a circle. However I don't want to draw a circle but just the arc of that circle between blue point and green point
Using arc with SVG (A ellipses), I get 2 options with large_arc_flag
In this example: second option is the one that I want: large_arc_flag=1, but sometimes I want large_arc_flag=0. In fact, I want just the arc that belongs to that circle. Using path with "A", I got 2 arcs to chose due to the intersection of ellipses.
How can I solve that?
Thanks!
Given a random circle centre (the white point), one random edge point (green) and a second mirrored edge point (blue), you can calculate the svg arc as follows.
Set the horizontal and vertical ellipse radii to the distance between the green and white points
Set the x-axis-rotation to zero (as rotating a circle makes no visual difference)
Set the large-arc-flag to one if the green point is above the white point, otherwise zero (I think this was the key thing you were asking)
Set the sweep flag to zero because the yellow path is arbitrarily being drawn from the left image border to the right image border, requiring counter-clockwise rotation of the arc path trajectory
Set the final point to the blue point coordinates
Run the code snippet below repeatedly for different arc configurations based on random centre and edge points.
var xmlns = "http://www.w3.org/2000/svg"; // svg namespace
var doc = document; // common abbreviation
var spc = " "; // space
var com = ","; // comma
var wd = 200; // svg width
var ht = 200; // svg height
var svg = doc.querySelector("svg"); // retrieve svg root element
setAttributes(svg, {width: wd, height: ht}); // set the svg dimensions
var cenX = wd / 2; // centre the circle horizontally
var cenY = Math.random() * (ht / 2) + (ht / 4); // pick a random circle centre height
var x1 = Math.random() * (wd / 3) + (wd / 7); // pick a random green point position
var y1 = Math.random() * (ht / 2) + (ht / 4);
var x2 = wd - x1; // mirror the blue point on the green point
var y2 = y1;
showPt(cenX, cenY, "white"); // show the white circle centre
showPt(x1, y1, "green"); // show the coloured edge points
showPt(x2, y2, "blue");
var path = doc.createElementNS(xmlns, "path"); // create the yellow path element
setAttributes(path, { // give it colour and width but no fill
stroke: "yellow",
"stroke-width": 3,
fill: "none"
});
svg.appendChild(path); // add it to the picture
var rad = Math.sqrt((x1-cenX)*(x1-cenX) + (y1-cenY)*(y1-cenY)); // calculate the circle radius
var lgArcFlag = (y1 < cenY ? 1 : 0); // the arc will be large if the edge points are above the circle centre
setAttributes(path, { // create the trajectory of the yellow path
d:
"M" +
"0," + y1 + // start it at the left border at the height of the green edge point
"L" +
x1 + com + y1 + spc + // draw it to the green edge point
"A" + // make an arc
rad + spc + rad + spc + // using the circle radius
0 + spc + // with no rotation of the ellipse/circle
lgArcFlag + spc + 0 + spc + // using the large-arc-flag
x2 + com + y2 + spc + // drawn to the blue edge point
"L" +
wd + com + y1 // and drawn straight out to the right border
});
function showPt(x, y, fill) {
var pt = doc.createElementNS(xmlns, "circle");
setAttributes(pt, {
cx: x,
cy: y,
r: 8,
fill: fill
});
svg.appendChild(pt);
return pt;
}
function setAttributes(el, attrs) {
var recursiveSet = function(at, set) {
for (var prop in at) {
var a = at[prop];
if (typeof a === 'object' && a.dataset === undefined && a[0] === undefined) {
recursiveSet(a, set [prop]);
} else {
set.setAttribute(prop, a);
}
}
}
recursiveSet(attrs, el);
}
<svg>
<rect id="bkgd" fill="black" x="0" y="0" width="300" height="300" />
</svg>
See this fiddle. The arc is visible with fabric 1.4.0 but not when the library is changed to 1.5.0.
https://jsfiddle.net/ex5s9mx9/2/
The relevant code is
function makeArcPath(path, lineColor)
{
var p = new fabric.Path(path,
{
stroke: lineColor,
strokeWidth: 1,
fill: false
});
return p;
};
function circularArcPath(x, y, radius, startAngle, endAngle)
{
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var anglediff = startAngle - endAngle;
var arcSweep = ((anglediff > 0 && anglediff < 180) || (anglediff < -180)) ? 0 : 1;
var d = [["M", start.x, start.y], ["A", radius, radius, 0, 0, arcSweep, end.x, end.y]];
return d;
}
I have looked at the arc object with the debugger in both cases, and all fields are the same.
I'd be very grateful for any pointers.
Thanks.
Edit: If I append ["M", 0, 0] to the end of the path, then the arc does appear!
Edit 2: In the fiddle, when I first make my arc it has radius 0, but this is updated when it is moved by calling
c.arc.set('path', circularArcPath(c.left, c.top, 30, a1, a2));
However, if I use a non-zero radius right from the start, the problem disappears, so I think it must be something about the initial zero radius.
I have a g element that contains one or more path elements. As I mentioned in another question, I scale and translate the g element by computing a transform attribute so that it fits on a grid in another part of the canvas.
The calculation is done using the difference between two rectangles, the getBBox() from the g element and the rectangle around the grid.
Here is the question -- after I do the transform, I update the contents of the g element and call getBBox() again, without removing the transform. The resulting rectangle appears to be calculated without considering the transform. I would have expected it to reflect the change. Is this behavior consistent with the SVG specification? How do I get the bounding box of the transformed rectangle?
This, BTW, is in an HTML 5 document running in Firefox 4, if that makes any difference.
Update: Apparently this behavior seems pretty clearly in violation of the specification. From the text here at w3c:
SVGRect getBBox()
Returns the tight bounding box in current user space (i.e., after application of the ‘transform’ attribute, if any) on the geometry of all contained graphics elements, exclusive of stroking, clipping, masking and filter effects). Note that getBBox must return the actual bounding box at the time the method was called, even in case the element has not yet been rendered.
Am I reading this correctly? If so this seems to be an errata in the SVG implementation Firefox uses; I haven't had a chance to try any other. I would file a bug report if someone could point me to where.
People often get confused by the behavioral difference of getBBox and getBoundingClientRect.
getBBox is a SVG Element's native method as equivalent to find the offset/clientwidth of HTML DOM element. The width and height is never going to change even when the element is rotated. It cannot be used for HTML DOM Elements.
getBoundingClientRect is common to both HTML and SVG elements. The bounded rectangle width and height will change when the element is rotated or when more elements are grouped.
The behaviour you see is correct, and consistent with the spec.
The transform gets applied, then the bbox is calculated in "current user units", i.e. the current user space. So if you want to see the result of a transform on the element you'd need to look at the bbox of a parent node or similar.
It's a bit confusing, but explained a lot better in the SVG Tiny 1.2 spec for SVGLocatable
That contains a number of examples that clarify what it's supposed to do.
there are at least 2 easy but somewhat hacky ways to do what you ask... if there are nicer (less hacky) ways, i haven't found them yet
EASY HACKy #1:
a) set up a rect that matches the "untransformed" bbox that group.getBBox() is returning
b) apply the group's "unapplied transform" to that rect
c) rect.getBBox() should now return the bbox you're looking for
EASY HACKY #2: (only tested in chrome)
a) use element.getBoundingClientRect(), which returns enough info for you to construct the bbox you're looking for
Apparently getBBox() doesn't take the transformations into consideration.
I can point you here, unfortunately I wasn't able to make it working: http://tech.groups.yahoo.com/group/svg-developers/message/22891
SVG groups have nasty practice - not to accumulate all transformations made. I have my way to cope with this issue. I'm using my own attributes to store current transformation data which I include in any further transformation. Use XML compatible attributes like alttext, value, name....or just x and y for storing accumulated value as atribute.
Example:
<g id="group" x="20" y="100" transform="translate(20, 100)">
<g id="subgroup" alttext="45" transform="rotate(45)">
<line...etc...
Therefore when I'm making transformations I'm taking those handmade attribute values, and when writing it back, I'm writing both transform and same value with attributes I made just for keeping all accumulated values.
Example for rotation:
function symbRot(evt) {
evt.target.ondblclick = function () {
stopBlur();
var ptx=symbG.parentNode.lastChild.getAttribute("cx");
var pty=symbG.parentNode.lastChild.getAttribute("cy");
var currRot=symbG.getAttributeNS(null, "alttext");
var rotAng;
if (currRot == 0) {
rotAng = 90
} else if (currRot == 90) {
rotAng = 180
} else if (currRot == 180) {
rotAng = 270
} else if (currRot == 270) {
rotAng = 0
};
symbG.setAttributeNS(null, "transform", "rotate(" + rotAng + "," + ptx + ", " + pty + ")");
symbG.setAttributeNS(null, "alttext", rotAng );
};
}
The following code takes into account the transformations (matrix or otherwise) from parents, itself, as well as children. So, it will work on a <g> element for example.
You will normally want to pass the parent <svg> as the third argument—toElement—as to return the computed bounding box in the coordinate space of the <svg> (which is generally the coordinate space we care about).
/**
* #param {SVGElement} element - Element to get the bounding box for
* #param {boolean} [withoutTransforms=false] - If true, transforms will not be calculated
* #param {SVGElement} [toElement] - Element to calculate bounding box relative to
* #returns {SVGRect} Coordinates and dimensions of the real bounding box
*/
function getBBox(element, withoutTransforms, toElement) {
var svg = element.ownerSVGElement;
if (!svg) {
return { x: 0, y: 0, cx: 0, cy: 0, width: 0, height: 0 };
}
var r = element.getBBox();
if (withoutTransforms) {
return {
x: r.x,
y: r.y,
width: r.width,
height: r.height,
cx: r.x + r.width / 2,
cy: r.y + r.height / 2
};
}
var p = svg.createSVGPoint();
var matrix = (toElement || svg).getScreenCTM().inverse().multiply(element.getScreenCTM());
p.x = r.x;
p.y = r.y;
var a = p.matrixTransform(matrix);
p.x = r.x + r.width;
p.y = r.y;
var b = p.matrixTransform(matrix);
p.x = r.x + r.width;
p.y = r.y + r.height;
var c = p.matrixTransform(matrix);
p.x = r.x;
p.y = r.y + r.height;
var d = p.matrixTransform(matrix);
var minX = Math.min(a.x, b.x, c.x, d.x);
var maxX = Math.max(a.x, b.x, c.x, d.x);
var minY = Math.min(a.y, b.y, c.y, d.y);
var maxY = Math.max(a.y, b.y, c.y, d.y);
var width = maxX - minX;
var height = maxY - minY;
return {
x: minX,
y: minY,
width: width,
height: height,
cx: minX + width / 2,
cy: minY + height / 2
};
}
I made a helper function, which returns various metrics of svg element (also bbox of transformed element).
The code is here:
SVGElement.prototype.getTransformToElement =
SVGElement.prototype.getTransformToElement || function(elem) {
return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
function get_metrics(el) {
function pointToLineDist(A, B, P) {
var nL = Math.sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y));
return Math.abs((P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x)) / nL;
}
function dist(point1, point2) {
var xs = 0,
ys = 0;
xs = point2.x - point1.x;
xs = xs * xs;
ys = point2.y - point1.y;
ys = ys * ys;
return Math.sqrt(xs + ys);
}
var b = el.getBBox(),
objDOM = el,
svgDOM = objDOM.ownerSVGElement;
// Get the local to global matrix
var matrix = svgDOM.getTransformToElement(objDOM).inverse(),
oldp = [[b.x, b.y], [b.x + b.width, b.y], [b.x + b.width, b.y + b.height], [b.x, b.y + b.height]],
pt, newp = [],
obj = {},
i, pos = Number.POSITIVE_INFINITY,
neg = Number.NEGATIVE_INFINITY,
minX = pos,
minY = pos,
maxX = neg,
maxY = neg;
for (i = 0; i < 4; i++) {
pt = svgDOM.createSVGPoint();
pt.x = oldp[i][0];
pt.y = oldp[i][1];
newp[i] = pt.matrixTransform(matrix);
if (newp[i].x < minX) minX = newp[i].x;
if (newp[i].y < minY) minY = newp[i].y;
if (newp[i].x > maxX) maxX = newp[i].x;
if (newp[i].y > maxY) maxY = newp[i].y;
}
// The next refers to the transformed object itself, not bbox
// newp[0] - newp[3] are the transformed object's corner
// points in clockwise order starting from top left corner
obj.newp = newp; // array of corner points
obj.width = pointToLineDist(newp[1], newp[2], newp[0]) || 0;
obj.height = pointToLineDist(newp[2], newp[3], newp[0]) || 0;
obj.toplen = dist(newp[0], newp[1]);
obj.rightlen = dist(newp[1], newp[2]);
obj.bottomlen = dist(newp[2], newp[3]);
obj.leftlen = dist(newp[3], newp[0]);
// The next refers to the transformed object's bounding box
obj.BBx = minX;
obj.BBy = minY;
obj.BBx2 = maxX;
obj.BBy2 = maxY;
obj.BBwidth = maxX - minX;
obj.BBheight = maxY - minY;
return obj;
}
and full functional example is here:
http://jsbin.com/acowaq/1