2 Scripts for creating SVG apps, converting clientX/Y to viewBox coordinates
3 and for displaying tooltips
5 Copyright (C) <2006> <Andreas Neumann>
6 Version 1.2, 2006-10-06
7 neumann@karto.baug.ethz.ch
9 http://www.carto.net/neumann/
12 * thanks to Kevin Lindsey for his many examples and providing the ViewBox class
16 Documentation: http://www.carto.net/papers/svg/gui/mapApp/
25 Was programmed earlier, but now documented
28 added properties this.innerWidth, this.innerHeight (wrapper around different behaviour of viewers), added method ".adjustViewBox()" to adjust the viewBox to the this.innerWidth and this.innerHeight of the UA's window
31 added two new constructor parameter "adjustVBonWindowResize" and "resizeCallbackFunction". If the first parameter is set to true, the viewBox of this mapApp will always adjust itself to the innerWidth and innerHeight of the browser window or frame containing the SVG application
32 the "resizeCallbackFunction" can be of type "function", later potentially also of type "object". This function is called every time the mapApp was resized (browser/UA window was resized). It isn't called the first time when the mapApp was initialized
33 added a new way to detect resize events in Firefox which didn't implement the SVGResize event so far
34 added several arrays to hold GUI references
39 This ECMA script library is free software; you can redistribute it and/or
40 modify it under the terms of the GNU Lesser General Public
41 License as published by the Free Software Foundation; either
42 version 2.1 of the License, or (at your option) any later version.
44 This library is distributed in the hope that it will be useful,
45 but WITHOUT ANY WARRANTY; without even the implied warranty of
46 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 Lesser General Public License for more details.
49 You should have received a copy of the GNU Lesser General Public
50 License along with this library (lesser_gpl.txt); if not, write to the Free Software
51 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
55 original document site: http://www.carto.net/papers/svg/gui/mapApp/
56 Please contact the author in case you want to use code or ideas commercially.
57 If you use this code, please include this copyright header, the included full
58 LGPL 2.1 text and read the terms provided in the LGPL 2.1 license
59 (http://www.gnu.org/copyleft/lesser.txt)
61 -------------------------------
63 Please report bugs and send improvements to neumann@karto.baug.ethz.ch
64 If you use this code, please link to the original (http://www.carto.net/papers/svg/gui/mapApp/)
65 somewhere in the source-code-comment or the "about" of your project and give credits, thanks!
69 //this mapApp object helps to convert clientX/clientY coordinates to the coordinates of the group where the element is within
70 //normally one can just use .getScreenCTM(), but ASV3 does not implement it, 95% of the code in this function is for ASV3!!!
71 //credits: Kevin Lindsey for his example at http://www.kevlindev.com/gui/utilities/viewbox/ViewBox.js
72 function mapApp(adjustVBonWindowResize
,resizeCallbackFunction
) {
73 this.adjustVBonWindowResize
= adjustVBonWindowResize
;
74 this.resizeCallbackFunction
= resizeCallbackFunction
;
75 this.initialized
= false;
76 if (!document
.documentElement
.getScreenCTM
) {
77 //add zoom and pan event event to document element
78 //this part is only required for viewers not supporting document.documentElement.getScreenCTM() (e.g. ASV3)
79 document
.documentElement
.addEventListener("SVGScroll",this,false);
80 document
.documentElement
.addEventListener("SVGZoom",this,false);
82 //add SVGResize event, note that because FF does not yet support the SVGResize event, there is a workaround
84 //browsers with native SVG support
85 window
.addEventListener("resize",this,false);
88 //SVG UAs, like Batik and ASV/Iex
89 document
.documentElement
.addEventListener("SVGResize",this,false);
91 //determine the browser main version
92 this.navigator
= "Batik";
93 if (window
.navigator
) {
94 if (window
.navigator
.appName
.match(/Adobe/gi)) {
95 this.navigator
= "Adobe";
97 if (window
.navigator
.appName
.match(/Netscape/gi)) {
98 this.navigator
= "Mozilla";
100 if (window
.navigator
.appName
.match(/Opera/gi)) {
101 this.navigator
= "Opera";
103 if (window
.navigator
.appName
.match(/Safari/gi)) {
104 this.navigator
= "Safari";
107 //we need to call this once to initialize this.innerWidth/this.innerHeight
109 //per default, tooltips are disabled
110 this.tooltipsEnabled
= false;
111 //create new arrays to hold GUI references
112 this.Windows
= new Array();
113 this.checkBoxes
= new Array();
114 this.radioButtonGroups
= new Array();
115 this.tabgroups
= new Array();
116 this.textboxes
= new Array();
117 this.buttons
= new Array();
118 this.selectionLists
= new Array();
119 this.comboboxes
= new Array();
120 this.sliders
= new Array();
121 this.scrollbars
= new Array();
122 this.colourPickers
= new Array();
125 mapApp
.prototype.handleEvent = function(evt
) {
126 if (evt
.type
== "SVGResize" || evt
.type
== "resize" || evt
.type
== "SVGScroll" || evt
.type
== "SVGZoom") {
129 if ((evt
.type
== "mouseover" || evt
.type
== "mouseout" || evt
.type
== "mousemove") && this.tooltipsEnabled
) {
130 this.displayTooltip(evt
);
134 mapApp
.prototype.resetFactors = function() {
135 //set inner width and height
136 if (window
.innerWidth
) {
137 this.innerWidth
= window
.innerWidth
;
138 this.innerHeight
= window
.innerHeight
;
141 var viewPort
= document
.documentElement
.viewport
;
142 this.innerWidth
= viewPort
.width
;
143 this.innerHeight
= viewPort
.height
;
145 if (this.adjustVBonWindowResize
) {
146 this.adjustViewBox();
148 //this code is for ASV3
149 if (!document
.documentElement
.getScreenCTM
) {
150 var svgroot
= document
.documentElement
;
151 this.viewBox
= new ViewBox(svgroot
);
152 var trans
= svgroot
.currentTranslate
;
153 var scale
= svgroot
.currentScale
;
154 this.m
= this.viewBox
.getTM();
155 //undo effects of zoom and pan
156 this.m
= this.m
.scale( 1/scale
);
157 this.m
= this.m
.translate(-trans
.x
, -trans
.y
);
159 if (this.resizeCallbackFunction
&& this.initialized
) {
160 if (typeof(this.resizeCallbackFunction
) == "function") {
161 this.resizeCallbackFunction();
164 this.initialized
= true;
167 //set viewBox of document.documentElement to innerWidth and innerHeight
168 mapApp
.prototype.adjustViewBox = function() {
169 document
.documentElement
.setAttributeNS(null,"viewBox","0 0 "+this.innerWidth
+" "+this.innerHeight
);
172 mapApp
.prototype.calcCoord = function(evt
,ctmNode
) {
173 var svgPoint
= document
.documentElement
.createSVGPoint();
174 svgPoint
.x
= evt
.clientX
;
175 svgPoint
.y
= evt
.clientY
;
176 if (!document
.documentElement
.getScreenCTM
) {
177 //undo the effect of transformations
179 var matrix
= getTransformToRootElement(ctmNode
);
182 var matrix
= getTransformToRootElement(evt
.target
);
184 svgPoint
= svgPoint
.matrixTransform(matrix
.inverse().multiply(this.m
));
187 //case getScreenCTM is available
189 var matrix
= ctmNode
.getScreenCTM();
192 var matrix
= evt
.target
.getScreenCTM();
194 svgPoint
= svgPoint
.matrixTransform(matrix
.inverse());
196 //undo the effect of viewBox and zoomin/scroll
200 mapApp
.prototype.calcInvCoord = function(svgPoint
) {
201 if (!document
.documentElement
.getScreenCTM
) {
202 var matrix
= getTransformToRootElement(document
.documentElement
);
205 var matrix
= document
.documentElement
.getScreenCTM();
207 svgPoint
= svgPoint
.matrixTransform(matrix
);
211 //initialize tootlips
212 mapApp
.prototype.initTooltips = function(groupId
,tooltipTextAttribs
,tooltipRectAttribs
,xOffset
,yOffset
,padding
) {
214 if (arguments
.length
== nrArguments
) {
215 this.toolTipGroup
= document
.getElementById(groupId
);
216 this.tooltipTextAttribs
= tooltipTextAttribs
;
217 if (!this.tooltipTextAttribs
["font-size"]) {
218 this.tooltipTextAttribs
["font-size"] = 12;
220 this.tooltipRectAttribs
= tooltipRectAttribs
;
221 this.xOffset
= xOffset
;
222 this.yOffset
= yOffset
;
223 this.padding
= padding
;
224 if (!this.toolTipGroup
) {
225 alert("Error: could not find tooltip group with id '"+groupId
+"'. Please specify a correct tooltip parent group id!");
228 //set tooltip group to invisible
229 this.toolTipGroup
.setAttributeNS(null,"visibility","hidden");
230 this.toolTipGroup
.setAttributeNS(null,"pointer-events","none");
231 this.tooltipsEnabled
= true;
232 //create tooltip text element
233 this.tooltipText
= document
.createElementNS(svgNS
,"text");
234 for (var attrib
in this.tooltipTextAttribs
) {
235 value
= this.tooltipTextAttribs
[attrib
];
236 if (attrib
== "font-size") {
239 this.tooltipText
.setAttributeNS(null,attrib
,value
);
242 var textNode
= document
.createTextNode("Tooltip");
243 this.tooltipText
.appendChild(textNode
);
244 this.toolTipGroup
.appendChild(this.tooltipText
);
245 var bbox
= this.tooltipText
.getBBox();
246 this.tooltipRect
= document
.createElementNS(svgNS
,"rect");
247 this.tooltipRect
.setAttributeNS(null,"x",bbox
.x
-this.padding
);
248 this.tooltipRect
.setAttributeNS(null,"y",bbox
.y
-this.padding
);
249 this.tooltipRect
.setAttributeNS(null,"width",bbox
.width
+this.padding
*2);
250 this.tooltipRect
.setAttributeNS(null,"height",bbox
.height
+this.padding
*2);
251 for (var attrib
in this.tooltipRectAttribs
) {
252 this.tooltipRect
.setAttributeNS(null,attrib
,this.tooltipRectAttribs
[attrib
]);
254 this.toolTipGroup
.insertBefore(this.tooltipRect
,this.tooltipText
);
258 alert("Error in method 'initTooltips': wrong nr of arguments! You have to pass over "+nrArguments
+" parameters.");
262 mapApp
.prototype.addTooltip = function(tooltipNode
,tooltipTextvalue
,followmouse
,checkForUpdates
,targetOrCurrentTarget
,childAttrib
) {
264 if (arguments
.length
== nrArguments
) {
266 if (typeof(tooltipNode
) == "string") {
267 tooltipNode
= document
.getElementById(tooltipNode
);
269 //check if tooltip attribute present or create one
270 if (!tooltipNode
.hasAttributeNS(attribNS
,"tooltip")) {
271 if (tooltipTextvalue
) {
272 tooltipNode
.setAttributeNS(attribNS
,"tooltip",tooltipTextvalue
);
275 tooltipNode
.setAttributeNS(attribNS
,"tooltip","Tooltip");
278 //see if we need updates
279 if (checkForUpdates
) {
280 tooltipNode
.setAttributeNS(attribNS
,"tooltipUpdates","true");
282 //see if we have to use evt.target
283 if (targetOrCurrentTarget
== "target") {
284 tooltipNode
.setAttributeNS(attribNS
,"tooltipParent","true");
288 tooltipNode
.setAttributeNS(attribNS
,"tooltipAttrib",childAttrib
);
290 //add event listeners
291 tooltipNode
.addEventListener("mouseover",this,false);
292 tooltipNode
.addEventListener("mouseout",this,false);
294 tooltipNode
.addEventListener("mousemove",this,false);
298 alert("Error in method 'addTooltip()': wrong nr of arguments! You have to pass over "+nrArguments
+" parameters.");
302 mapApp
.prototype.displayTooltip = function(evt
) {
303 var curEl
= evt
.currentTarget
;
304 var coords
= this.calcCoord(evt
,this.toolTipGroup
.parentNode
);
305 if (evt
.type
== "mouseover") {
306 this.toolTipGroup
.setAttributeNS(null,"visibility","visible");
307 this.toolTipGroup
.setAttributeNS(null,"transform","translate("+(coords
.x
+this.xOffset
)+","+(coords
.y
+this.yOffset
)+")");
308 this.updateTooltip(evt
);
310 if (evt
.type
== "mouseout") {
311 this.toolTipGroup
.setAttributeNS(null,"visibility","hidden");
313 if (evt
.type
== "mousemove") {
314 this.toolTipGroup
.setAttributeNS(null,"transform","translate("+(coords
.x
+this.xOffset
)+","+(coords
.y
+this.yOffset
)+")");
315 if (curEl
.hasAttributeNS(attribNS
,"tooltipUpdates")) {
316 this.updateTooltip(evt
);
321 mapApp
.prototype.updateTooltip = function(evt
) {
322 var el
= evt
.currentTarget
;
323 if (el
.hasAttributeNS(attribNS
,"tooltipParent")) {
324 var attribName
= "tooltip";
325 if (el
.hasAttributeNS(attribNS
,"tooltipAttrib")) {
326 attribName
= el
.getAttributeNS(attribNS
,"tooltipAttrib");
329 var myText
= el
.getAttributeNS(attribNS
,attribName
);
332 var myText
= el
.getAttributeNS(attribNS
,"tooltip");
334 var textArray
= myText
.split("\\n");
335 while(this.tooltipText
.hasChildNodes()) {
336 this.tooltipText
.removeChild(this.tooltipText
.lastChild
);
338 for (var i
=0;i
<textArray
.length
;i
++) {
339 var tspanEl
= document
.createElementNS(svgNS
,"tspan");
340 tspanEl
.setAttributeNS(null,"x",0);
341 var dy
= this.tooltipTextAttribs
["font-size"];
345 tspanEl
.setAttributeNS(null,"dy",dy
);
346 var textNode
= document
.createTextNode(textArray
[i
]);
347 tspanEl
.appendChild(textNode
);
348 this.tooltipText
.appendChild(tspanEl
);
350 // set text and rect attributes
351 var bbox
= this.tooltipText
.getBBox();
352 this.tooltipRect
.setAttributeNS(null,"x",bbox
.x
-this.padding
);
353 this.tooltipRect
.setAttributeNS(null,"y",bbox
.y
-this.padding
);
354 this.tooltipRect
.setAttributeNS(null,"width",bbox
.width
+this.padding
*2);
355 this.tooltipRect
.setAttributeNS(null,"height",bbox
.height
+this.padding
*2);
358 mapApp
.prototype.enableTooltips = function() {
359 this.tooltipsEnabled
= true;
362 mapApp
.prototype.disableTooltips = function() {
363 this.tooltipsEnabled
= false;
364 this.toolTipGroup
.setAttributeNS(null,"visibility","hidden");
367 /*************************************************************************/
373 * copyright 2002, Kevin Lindsey
377 ViewBox
.VERSION
= "1.0";
385 function ViewBox(svgNode
) {
386 if ( arguments
.length
> 0 ) {
397 ViewBox
.prototype.init = function(svgNode
) {
398 var viewBox
= svgNode
.getAttributeNS(null, "viewBox");
399 var preserveAspectRatio
= svgNode
.getAttributeNS(null, "preserveAspectRatio");
401 if ( viewBox
!= "" ) {
402 var params
= viewBox
.split(/\s*,\s*|\s+/);
404 this.x
= parseFloat( params
[0] );
405 this.y
= parseFloat( params
[1] );
406 this.width
= parseFloat( params
[2] );
407 this.height
= parseFloat( params
[3] );
411 this.width
= innerWidth
;
412 this.height
= innerHeight
;
415 this.setPAR(preserveAspectRatio
);
416 var dummy
= this.getTM(); //to initialize this.windowWidth/this.windowHeight
425 ViewBox
.prototype.getTM = function() {
426 var svgRoot
= document
.documentElement
;
427 var matrix
= document
.documentElement
.createSVGMatrix();
428 //case width/height contains percent
429 this.windowWidth
= svgRoot
.getAttributeNS(null,"width");
430 if (this.windowWidth
.match(/%/) || this.windowWidth
== null) {
431 if (this.windowWidth
== null) {
432 if (window
.innerWidth
) {
433 this.windowWidth
= window
.innerWidth
;
436 this.windowWidth
= svgRoot
.viewport
.width
;
440 var factor
= parseFloat(this.windowWidth
.replace(/%/,""))/100;
441 if (window
.innerWidth
) {
442 this.windowWidth
= window
.innerWidth
* factor
;
445 this.windowWidth
= svgRoot
.viewport
.width
* factor
;
450 this.windowWidth
= parseFloat(this.windowWidth
);
452 this.windowHeight
= svgRoot
.getAttributeNS(null,"height");
453 if (this.windowHeight
.match(/%/) || this.windowHeight
== null) {
454 if (this.windowHeight
== null) {
455 if (window
.innerHeight
) {
456 this.windowHeight
= window
.innerHeight
;
459 this.windowHeight
= svgRoot
.viewport
.height
;
463 var factor
= parseFloat(this.windowHeight
.replace(/%/,""))/100;
464 if (window
.innerHeight
) {
465 this.windowHeight
= window
.innerHeight
* factor
;
468 this.windowHeight
= svgRoot
.viewport
.height
* factor
;
473 this.windowHeight
= parseFloat(this.windowHeight
);
475 var x_ratio
= this.width
/ this.windowWidth
;
476 var y_ratio
= this.height
/ this.windowHeight
;
478 matrix
= matrix
.translate(this.x
, this.y
);
479 if ( this.alignX
== "none" ) {
480 matrix
= matrix
.scaleNonUniform( x_ratio
, y_ratio
);
482 if ( x_ratio
< y_ratio
&& this.meetOrSlice
== "meet" ||
483 x_ratio
> y_ratio
&& this.meetOrSlice
== "slice" )
486 var x_diff
= this.windowWidth
*y_ratio
- this.width
;
488 if ( this.alignX
== "Mid" )
490 else if ( this.alignX
== "Max" )
493 matrix
= matrix
.translate(x_trans
, 0);
494 matrix
= matrix
.scale( y_ratio
);
496 else if ( x_ratio
> y_ratio
&& this.meetOrSlice
== "meet" ||
497 x_ratio
< y_ratio
&& this.meetOrSlice
== "slice" )
500 var y_diff
= this.windowHeight
*x_ratio
- this.height
;
502 if ( this.alignY
== "Mid" )
504 else if ( this.alignY
== "Max" )
507 matrix
= matrix
.translate(0, y_trans
);
508 matrix
= matrix
.scale( x_ratio
);
512 // x_ratio == y_ratio so, there is no need to translate
513 // We can scale by either value
514 matrix
= matrix
.scale( x_ratio
);
533 ViewBox
.prototype.setPAR = function(PAR
) {
534 // NOTE: This function needs to use default values when encountering
535 // unrecognized values
537 var params
= PAR
.split(/\s+/);
538 var align
= params
[0];
540 if ( align
== "none" ) {
541 this.alignX
= "none";
542 this.alignY
= "none";
544 this.alignX
= align
.substring(1,4);
545 this.alignY
= align
.substring(5,9);
548 if ( params
.length
== 2 ) {
549 this.meetOrSlice
= params
[1];
551 this.meetOrSlice
= "meet";
554 this.align
= "xMidYMid";
557 this.meetOrSlice
= "meet";