details: http://www.bx.psu.edu/hg/galaxy/rev/c2d042ba8cd0 changeset: 2884:c2d042ba8cd0 user: Ross Lazarus <ross.lazarus@gmail.com> date: Wed Oct 14 15:47:58 2009 -0400 description: adding three LGPL ECMA script helpers for rgGRR interactive SVG. 4 file(s) affected in this change: lib/galaxy/datatypes/genetics.py static/scripts/checkbox_and_radiobutton.js static/scripts/helper_functions.js static/scripts/timer.js diffs (1724 lines): diff -r 878ca31a4995 -r c2d042ba8cd0 lib/galaxy/datatypes/genetics.py --- a/lib/galaxy/datatypes/genetics.py Wed Oct 14 09:33:56 2009 -0400 +++ b/lib/galaxy/datatypes/genetics.py Wed Oct 14 15:47:58 2009 -0400 @@ -1,3 +1,463 @@ +<<<<<<< local +""" +rgenetics datatypes +Use at your peril +Ross Lazarus +for the rgenetics and galaxy projects + +genome graphs datatypes derived from Interval datatypes +genome graphs datasets have a header row with appropriate columnames +The first column is always the marker - eg columname = rs, first row= rs12345 if the rows are snps +subsequent row values are all numeric ! Will fail if any non numeric (eg '+' or 'NA') values +ross lazarus for rgenetics +august 20 2007 +""" + +import logging, os, sys, time, tempfile, shutil +import data +from galaxy import util +from cgi import escape +import urllib +from galaxy.web import url_for +from galaxy.datatypes import metadata +from galaxy.datatypes.metadata import MetadataElement +from galaxy.datatypes.data import Text +from galaxy.datatypes.tabular import Tabular +from galaxy.datatypes.images import Html + +log = logging.getLogger(__name__) + + + +class GenomeGraphs( Tabular ): + """Tab delimited data containing a marker id and any number of numeric values""" + + """Add metadata elements""" + MetadataElement( name="markerCol", default=1, desc="Marker ID column", param=metadata.ColumnParameter ) + MetadataElement( name="columns", default=3, desc="Number of columns", readonly=True ) + MetadataElement( name="column_types", default=[], desc="Column types", readonly=True, visible=False ) + file_ext = 'gg' + + def __init__(self, **kwd): + """Initialize gg datatype, by adding UCSC display apps""" + Tabular.__init__(self, **kwd) + self.add_display_app ( 'ucsc', 'Genome Graph', 'as_ucsc_display_file', 'ucsc_links' ) + + def set_peek( self, dataset ): + """Set the peek and blurb text""" + if not dataset.dataset.purged: + dataset.peek = data.get_file_peek( dataset.file_name ) + dataset.blurb = util.commaify( str( data.get_line_count( dataset.file_name ) ) ) + " rows" + #i don't think set_meta should not be called here, it should be called separately + self.set_meta( dataset ) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + + def get_estimated_display_viewport( self, dataset ): + """Return a chrom, start, stop tuple for viewing a file.""" + raise notImplemented + + def as_ucsc_display_file( self, dataset, **kwd ): + """Returns file""" + return file(dataset.file_name,'r') + + def ucsc_links( self, dataset, type, app, base_url ): + """ from the ever-helpful angie hinrichs angie@soe.ucsc.edu + a genome graphs call looks like this + http://genome.ucsc.edu/cgi-bin/hgGenome?clade=mammal&org=Human&db=hg18&hgGenome_dataSetName=dname + &hgGenome_dataSetDescription=test&hgGenome_formatType=best%20guess&hgGenome_markerType=best%20guess + &hgGenome_columnLabels=best%20guess&hgGenome_maxVal=&hgGenome_labelVals= + &hgGenome_maxGapToFill=25000000&hgGenome_uploadFile=http://galaxy.esphealth.org/datasets/333/display/index + &hgGenome_doSubmitUpload=submit + Galaxy gives this for an interval file + http://genome.ucsc.edu/cgi-bin/hgTracks?db=hg18&position=chr1:1-1000&hgt.customText= + http%3A%2F%2Fgalaxy.esphealth.org%2Fdisplay_as%3Fid%3D339%26display_app%3Ducsc + """ + ret_val = [] + ggtail = '&hgGenome_doSubmitUpload=submit' + if not dataset.dbkey: + dataset.dbkey = 'hg18' # punt! + if dataset.has_data: + for site_name, site_url in util.get_ucsc_by_build(dataset.dbkey): + if site_name in app.config.ucsc_display_sites: + site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs + display_url = urllib.quote_plus( "%s%s/display_as?id=%i&display_app=%s" % (base_url, url_for( controller='root' ), dataset.id, type)) + sl = ["%sdb=%s" % (site_url,dataset.dbkey ),] + sl.append("&hgGenome_dataSetName=%s&hgGenome_dataSetDescription=%s" % (dataset.name, 'GalaxyGG_data')) + sl.append("&hgGenome_formatType=best%20guess&hgGenome_markerType=best%20guess") + sl.append("&hgGenome_columnLabels=first%20row&hgGenome_maxVal=&hgGenome_labelVals=") + sl.append("&hgGenome_maxGapToFill=25000000&hgGenome_uploadFile=%%s") + sl.append(ggtail) + s = urllib.quote_plus( ''.join(sl) ) + link = '%s?redirect_url=%s&display_url=%s' % ( internal_url, s, display_url ) + ret_val.append( (site_name, link) ) + return ret_val + + def validate( self, dataset ): + """Validate a gg file - all numeric after header row""" + errors = list() + infile = open(dataset.file_name, "r") + header= infile.next() # header + for i,row in enumerate(infile): + ll = row.strip().split('\t') + badvals = [] + for j,x in enumerate(ll): + try: + x = float(x) + except: + badval.append('col%d:%s' % (j+1,x)) + if len(badvals) > 0: + errors.append('row %d, %s' % (' '.join(badvals))) + return errors + + def repair_methods( self, dataset ): + """Return options for removing errors along with a description""" + return [("lines","Remove erroneous lines")] + + +class rgTabList(Tabular): + """ for sampleid and for featureid lists of exclusions or inclusions in the clean tool + featureid subsets on statistical criteria -> specialized display such as gg + """ + file_ext = "rgTList" + + + def __init__(self, **kwd): + """Initialize featurelistt datatype""" + Tabular.__init__( self, **kwd ) + self.column_names = [] + + def make_html_table( self, dataset, skipchars=[] ): + """Create HTML table, used for displaying peek""" + out = ['<table cellspacing="0" cellpadding="3">'] + comments = [] + try: + # Generate column header + out.append( '<tr>' ) + for i, name in enumerate( self.column_names ): + out.append( '<th>%s.%s</th>' % ( str( i+1 ), name ) ) + if dataset.metadata.columns - len( self.column_names ) > 0: + for i in range( len( self.column_names ), dataset.metadata.columns ): + out.append( '<th>%s</th>' % str( i+1 ) ) + out.append( '</tr>' ) + out.append( self.make_html_peek_rows( dataset, skipchars=skipchars ) ) + out.append( '</table>' ) + out = "".join( out ) + except Exception, exc: + out = "Can't create peek %s" % exc + return out + +class rgSampleList(rgTabList): + """ for sampleid exclusions or inclusions in the clean tool + output from QC eg excess het, gender error, ibd pair member,eigen outlier,excess mendel errors,... + since they can be uploaded, should be flexible + but they are persistent at least + same infrastructure for expression? + """ + file_ext = "rgSList" + + def __init__(self, **kwd): + """Initialize samplelist datatype""" + rgTabList.__init__( self, **kwd ) + self.column_names[0] = 'FID' + self.column_names[1] = 'IID' + # this is what Plink wants as at 2009 + + +class rgFeatureList( rgTabList ): + """ for featureid lists of exclusions or inclusions in the clean tool + output from QC eg low maf, high missingness, bad hwe in controls, excess mendel errors,... + featureid subsets on statistical criteria -> specialized display such as gg + same infrastructure for expression? + """ + file_ext = "rgFList" + + def __init__(self, **kwd): + """Initialize featurelist datatype""" + rgTabList.__init__( self, **kwd ) + for i,s in enumerate(['#FeatureId', 'Chr', 'Genpos', 'Mappos']): + self.column_names[i] = s + + + +class Rgenetics(Html): + """class to use for rgenetics""" + """Add metadata elements""" + MetadataElement( name="base_name", desc="base name for all transformed versions of this genetic dataset", default="galaxy", readonly=True, set_in_upload=True) + + file_ext="html" + composite_type = 'auto_primary_file' + allow_datatype_change = False + + def missing_meta( self, dataset=None, **kwargs): + """Checks for empty meta values""" + for key, value in dataset.metadata.items(): + if not value: + return True + return False + + def generate_primary_file( self, dataset = None ): + rval = ['<html><head><title>Files for Composite Dataset (%s)</title></head><p/>This composite dataset is composed of the following files:<p/><ul>' % ( self.file_ext ) ] + for composite_name, composite_file in self.get_composite_files( dataset = dataset ).iteritems(): + opt_text = '' + if composite_file.optional: + opt_text = ' (optional)' + rval.append( '<li><a href="%s">%s</a>%s' % ( composite_name, composite_name, opt_text ) ) + rval.append( '</ul></html>' ) + return "\n".join( rval ) + +class SNPMatrix(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="snpmatrix" + + def set_peek( self, dataset ): + if not dataset.dataset.purged: + dataset.peek = "Binary RGenetics file" + dataset.blurb = data.nice_size( dataset.get_size() ) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + + +class Lped(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="lped" + + def __init__( self, **kwd ): + Rgenetics.__init__( self, **kwd ) + self.add_composite_file( '%s.ped', description = 'Pedigree File', substitute_name_with_metadata = 'base_name', is_binary = True ) + self.add_composite_file( '%s.map', description = 'Map File', substitute_name_with_metadata = 'base_name', is_binary = True ) + + +class Pphe(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="pphe" + + def __init__( self, **kwd ): + Rgenetics.__init__( self, **kwd ) + self.add_composite_file( '%s.pphe', description = 'Plink Phenotype File', substitute_name_with_metadata = 'base_name' ) + + +class Lmap(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="lmap" + +class Fphe(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="fphe" + + def __init__( self, **kwd ): + Rgenetics.__init__( self, **kwd ) + self.add_composite_file( '%s.fphe', description = 'FBAT Phenotype File', substitute_name_with_metadata = 'base_name' ) + +class Phe(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="phe" + + def __init__( self, **kwd ): + Rgenetics.__init__( self, **kwd ) + self.add_composite_file( '%s.phe', description = 'Phenotype File', substitute_name_with_metadata = 'base_name' ) + + + +class Fped(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="fped" + + def __init__( self, **kwd ): + Rgenetics.__init__( self, **kwd ) + self.add_composite_file( '%s.fped', description = 'FBAT format pedfile', substitute_name_with_metadata = 'base_name' ) + + +class Pbed(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="pbed" + + def __init__( self, **kwd ): + Rgenetics.__init__( self, **kwd ) + self.add_composite_file( '%s.bim', substitute_name_with_metadata = 'base_name', is_binary = True ) + self.add_composite_file( '%s.bed', substitute_name_with_metadata = 'base_name', is_binary = True ) + self.add_composite_file( '%s.fam', substitute_name_with_metadata = 'base_name', is_binary = True ) + +class Eigenstratgeno(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="eigenstratgeno" + + def __init__( self, **kwd ): + Rgenetics.__init__( self, **kwd ) + self.add_composite_file( '%s.eigenstratgeno', substitute_name_with_metadata = 'base_name', is_binary = True ) + self.add_composite_file( '%s.ind', substitute_name_with_metadata = 'base_name', is_binary = True ) + self.add_composite_file( '%s.map', substitute_name_with_metadata = 'base_name', is_binary = True ) + + + +class Eigenstratpca(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="eigenstratpca" + +class Snptest(Rgenetics): + """fake class to distinguish different species of Rgenetics data collections + """ + file_ext="snptest" + +class RexpBase( Html ): + """base class for BioC data structures in Galaxy + must be constructed with the pheno data in place since that + goes into the metadata for each instance""" + + """Add metadata elements""" + MetadataElement( name="columns", default=0, desc="Number of columns", readonly=True, visible=False ) + MetadataElement( name="column_names", default=[], desc="Column names", readonly=True,visible=True ) + MetadataElement( name="base_name", + desc="base name for all transformed versions of this genetic dataset", readonly=True, default='galaxy', set_in_upload=True) + ### Do we really need these below? can we rely on dataset.extra_files_path: os.path.join( dataset.extra_files_path, '%s.phenodata' % dataset.metadata.base_name ) ? + ### Do these have a different purpose? Ross will need to clarify + ### Uploading these datatypes will not work until this is sorted out (set_peek fails)... + MetadataElement( name="pheno_path", + desc="Path to phenotype data for this experiment", readonly=True) + MetadataElement( name="pheno", + desc="Phenotype data for this experiment", readonly=True) + + file_ext = None + + is_binary = True + + allow_datatype_change = False + + composite_type = 'basic' + + def __init__( self, **kwd ): + Html.__init__( self, **kwd ) + self.add_composite_file( '%s.phenodata', substitute_name_with_metadata = 'base_name', is_binary = True ) + self.metadata.pheno_path = '%s.phenodata' % (self.metadata.base_name) + + def missing_meta( self, dataset=None, **kwargs): + """Checks for empty meta values""" + for key, value in dataset.metadata.items(): + if not value: + return True + return False + + def get_pheno(self,dataset): + """expects a .pheno file in the extra_files_dir - ugh + note that R is wierd and does not include the row.name in + the header. why?""" + p = file(dataset.metadata.pheno_path,'r').readlines() #this fails + head = p[0].strip().split('\t') + head.insert(0,'ChipFileName') # fix R write.table b0rken-ness + p[0] = '\t'.join(head) + p = '\n'.join(p) + return p + + def set_peek( self, dataset ): + """expects a .pheno file in the extra_files_dir - ugh + note that R is wierd and does not include the row.name in + the header. why?""" + p = self.get_pheno(dataset) + dataset.peek = p[:20] + dataset.info = p[0] + dataset.blurb = 'R loadable BioC expression object for the Rexpression Galaxy toolkit' + + # stolen from Tabular + # class Tabular( data.Text ): + """Tab delimited data""" + + """Add metadata elements""" + def init_meta( self, dataset, copy_from=None ): + if copy_from: + dataset.metadata = copy_from.metadata + + def set_readonly_meta( self, dataset, **kwd ): + """Resets the values of readonly metadata elements.""" + RexpBase.set_meta( self, dataset ) + + def set_meta( self, dataset, **kwd ): + + """ + NOTE we apply the tabular machinary to the phenodata extracted + from a BioC eSet or affybatch. + + """ + if not dataset.peek: + dataset.set_peek() + pk = dataset.get_pheno # read the basename.phenodata in the extra_files_path + ###this is probably not the best source, can we just access the raw data directly? + if pk: + p = pk.split('\n') + h = p[0].strip().split('\t') # hope is header + h = [escape(x) for x in h] + dataset.metadata.column_names = h + dataset.metadata.columns = len(h) + else: + dataset.metadata.column_names = [] + dataset.metadata.columns = 0 + + def make_html_table( self, dataset): + """Create HTML table, used for displaying peek""" + out = ['<table cellspacing="0" cellpadding="3">',] + try: + # Generate column header + pk = dataset.peek + p = pk.split('\n') + for i,row in enumerate(p): + lrow = row.strip().split('\t') + if i == 0: + orow = ['<th>%s</th>' % escape(x) for x in lrow] + orow.insert(0,'<tr>') + orow.append('</tr>') + else: + orow = ['<td>%s</td>' % escape(x) for x in lrow] + orow.insert(0,'<tr>') + orow.append('</tr>') + out.append(''.join(orow)) + out.append( '</table>' ) + out = "\n".join( out ) + except Exception, exc: + out = "Can't create peek %s" % str( exc ) + return out + + def display_peek( self, dataset ): + """Returns formatted html of peek""" + if not dataset.peek: + dataset.set_peek() + return self.make_html_table( dataset ) + + def get_mime(self): + """Returns the mime type of the datatype""" + return 'application/gzip' + + +class AffyBatch( RexpBase ): + """derived class for BioC data structures in Galaxy """ + file_ext = "affybatch" + + +class ESet( RexpBase ): + """derived class for BioC data structures in Galaxy """ + file_ext = "eset" + + +class MAList( RexpBase ): + """derived class for BioC data structures in Galaxy """ + file_ext = "malist" + + +if __name__ == '__main__': + import doctest, sys + doctest.testmod(sys.modules[__name__]) + +======= """ rgenetics datatypes Use at your peril @@ -665,3 +1125,4 @@ doctest.testmod(sys.modules[__name__]) +>>>>>>> other diff -r 878ca31a4995 -r c2d042ba8cd0 static/scripts/checkbox_and_radiobutton.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/scripts/checkbox_and_radiobutton.js Wed Oct 14 15:47:58 2009 -0400 @@ -0,0 +1,347 @@ +/* +Scripts to create interactive checkboxes and radio buttons in SVG using ECMA script +Copyright (C) <2007> <Andreas Neumann> +Version 1.1.3, 2007-08-09 +neumann@karto.baug.ethz.ch +http://www.carto.net/ +http://www.carto.net/neumann/ + +Credits: +* Guy Morton for providing a fix to let users toggle checkboxes by clicking on text labels +* Bruce Rindahl for providing the bugfix described in version 1.1.2 +* Simon Shutter for providing a fix for the ASV in IE crash when reloading the SVG file after calling the .remove() method on a checkbox + +---- + +Documentation: http://www.carto.net/papers/svg/gui/checkbox_and_radiobutton/ + +---- + +current version: 1.1.3 + +version history: +1.0 (2006-03-13) +initial version + +1.1 (2006-07-11) +text labels are now clickable (thanks to Guy Morton) +added method .moveTo() to move checkbox to a different location +introduced new constructor parameter labelYOffset to allow more flexible placement of the text label + +1.1.1 (2007-02-06) +added cursor pointer to the text label and use element representing the checkBox + +1.1.2 (2007-04-19) +bug fix: this.selectedIndex was not correctly initialized in method addCheckBox of the radioButtonGroup object + +1.1.3 (2007-08-09) +bug fix: the method .remove() was slightly modified (using removeEventListener) for avoiding a crash related to the method after reloading the SVG file + +------- + + +This ECMA script library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library (lesser_gpl.txt); if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +---- + +original document site: http://www.carto.net/papers/svg/gui/checkbox_and_radiobutton/ +Please contact the author in case you want to use code or ideas commercially. +If you use this code, please include this copyright header, the included full +LGPL 2.1 text and read the terms provided in the LGPL 2.1 license +(http://www.gnu.org/copyleft/lesser.txt) + +------------------------------- + +Please report bugs and send improvements to neumann@karto.baug.ethz.ch +If you use this control, please link to the original (http://www.carto.net/papers/svg/gui/checkbox_and_radiobutton/) +somewhere in the source-code-comment or the "about" of your project and give credits, thanks! + +*/ + +function checkBox(id,parentNode,x,y,checkboxId,checkcrossId,checkedStatus,labelText,textStyles,labelDistance,labelYOffset,radioButtonGroup,functionToCall) { + var nrArguments = 13; + var createCheckbox= true; + if (arguments.length == nrArguments) { + this.id = id; //an internal id, this id is not used in the SVG Dom tree + this.parentNode = parentNode; //the parentNode, string or nodeReference + this.x = x; //the center of the checkBox + this.y = y; //the center of the checkBox + this.checkboxId = checkboxId; //the id of the checkbox symbol (background) + this.checkcrossId = checkcrossId; //the id of the checkbox symbol (foreground), pointer-events should be set to "none" + this.checkedStatus = checkedStatus; //a status variable (true|false), indicates if checkbox is on or off + this.labelText = labelText; //the text of the checkbox label to be displayed, use undefined or empty string if you don't need a label text + this.textStyles = textStyles; //an array of literals containing the text settings + if (!this.textStyles["font-size"]) { + this.textStyles["font-size"] = 12; + } + this.labelDistance = labelDistance; //a distance defined from the center of the checkbox to the left of the text of the label + this.labelYOffset = labelYOffset; //a y offset value for the text label in relation to the checkbox symbol center + this.radioButtonGroup = radioButtonGroup; //a reference to a radio button group, if this is a standalone checkBox, just use the parameter undefined + this.functionToCall = functionToCall; //the function to call after triggering checkBox + this.exists = true; //status that indicates if checkbox exists or not, is set to false after method .remove() was called + this.label = undefined; //later a reference to the label text node + } + else { + createCheckbox = false; + alert("Error in checkbox ("+id+"): wrong nr of arguments! You have to pass over "+nrArguments+" parameters."); + } + if (createCheckbox) { + //timer stuff + this.timer = new Timer(this); //a Timer instance for calling the functionToCall + if (this.radioButtonGroup) { + this.timerMs = 0; + } + else { + this.timerMs = 200; //a constant of this object that is used in conjunction with the timer - functionToCall is called after 200 ms + } + //create checkbox + this.createCheckBox(); + } + else { + alert("Could not create checkbox with id '"+id+"' due to errors in the constructor parameters"); + } +} + +//this method creates all necessary checkbox geometry +checkBox.prototype.createCheckBox = function() { + if (typeof(this.parentNode) == "string") { + this.parentNode = document.getElementById(this.parentNode); + } + //create checkbox + this.checkBox = document.createElementNS(svgNS,"use"); + this.checkBox.setAttributeNS(null,"x",this.x); + this.checkBox.setAttributeNS(null,"y",this.y); + this.checkBox.setAttributeNS(xlinkNS,"href","#"+this.checkboxId); + this.checkBox.addEventListener("click",this,false); + this.checkBox.setAttributeNS(null,"cursor","pointer"); + this.parentNode.appendChild(this.checkBox); + //create checkcross + this.checkCross = document.createElementNS(svgNS,"use"); + this.checkCross.setAttributeNS(null,"x",this.x); + this.checkCross.setAttributeNS(null,"y",this.y); + this.checkCross.setAttributeNS(xlinkNS,"href","#"+this.checkcrossId); + this.parentNode.appendChild(this.checkCross); + if (this.checkedStatus == false) { + this.checkCross.setAttributeNS(null,"display","none"); + } + //create label, if any + if (this.labelText) { + if (this.labelText.length > 0) { + this.label = document.createElementNS(svgNS,"text"); + for (var attrib in this.textStyles) { + var value = this.textStyles[attrib]; + if (attrib == "font-size") { + value += "px"; + } + this.label.setAttributeNS(null,attrib,value); + } + this.label.setAttributeNS(null,"x",(this.x + this.labelDistance)); + this.label.setAttributeNS(null,"y",(this.y + this.labelYOffset)); + this.label.setAttributeNS(null,"cursor","pointer"); + var labelTextNode = document.createTextNode(this.labelText); + this.label.appendChild(labelTextNode); + this.label.setAttributeNS(null,"pointer-events","all"); + this.label.addEventListener("click",this,false); + this.parentNode.appendChild(this.label); + } + } + if (this.radioButtonGroup) { + this.radioButtonGroup.addCheckBox(this); + } +} + +checkBox.prototype.handleEvent = function(evt) { + if (evt.type == "click") { + if (this.checkedStatus == true) { + this.checkCross.setAttributeNS(null,"display","none"); + this.checkedStatus = false; + } + else { + this.checkCross.setAttributeNS(null,"display","inline"); + this.checkedStatus = true; + } + } + this.timer.setTimeout("fireFunction",this.timerMs); +} + +checkBox.prototype.fireFunction = function() { + if (this.radioButtonGroup) { + this.radioButtonGroup.selectById(this.id,true); + } + else { + if (typeof(this.functionToCall) == "function") { + this.functionToCall(this.id,this.checkedStatus,this.labelText); + } + if (typeof(this.functionToCall) == "object") { + this.functionToCall.checkBoxChanged(this.id,this.checkedStatus,this.labelText); + } + if (typeof(this.functionToCall) == undefined) { + return; + } + } +} + +checkBox.prototype.check = function(FireFunction) { + this.checkCross.setAttributeNS(null,"display","inherit"); + this.checkedStatus = true; + if (FireFunction) { + this.timer.setTimeout("fireFunction",this.timerMs); + } +} + +checkBox.prototype.uncheck = function(FireFunction) { + this.checkCross.setAttributeNS(null,"display","none"); + this.checkedStatus = false; + if (FireFunction) { + this.timer.setTimeout("fireFunction",this.timerMs); + } +} + +//move checkbox to a different position +checkBox.prototype.moveTo = function(moveX,moveY) { + this.x = moveX; + this.y = moveY; + //move checkbox + this.checkBox.setAttributeNS(null,"x",this.x); + this.checkBox.setAttributeNS(null,"y",this.y); + //move checkcross + this.checkCross.setAttributeNS(null,"x",this.x); + this.checkCross.setAttributeNS(null,"y",this.y); + //move text label + if (this.labelText) { + this.label.setAttributeNS(null,"x",(this.x + this.labelDistance)); + this.label.setAttributeNS(null,"y",(this.y + this.labelYOffset)); + } +} + +checkBox.prototype.remove = function(FireFunction) { + this.checkBox.removeEventListener("click",this,false); + this.parentNode.removeChild(this.checkBox); + this.parentNode.removeChild(this.checkCross); + if (this.label) { + this.parentNode.removeChild(this.label); + } + this.exists = false; +} + +checkBox.prototype.setLabelText = function(labelText) { + this.labelText = labelText + if (this.label) { + this.label.firstChild.nodeValue = labelText; + } + else { + if (this.labelText.length > 0) { + this.label = document.createElementNS(svgNS,"text"); + for (var attrib in this.textStyles) { + value = this.textStyles[attrib]; + if (attrib == "font-size") { + value += "px"; + } + this.label.setAttributeNS(null,attrib,value); + } + this.label.setAttributeNS(null,"x",(this.x + this.labelDistance)); + this.label.setAttributeNS(null,"y",(this.y + this.textStyles["font-size"] * 0.3)); + var labelTextNode = document.createTextNode(this.labelText); + this.label.appendChild(labelTextNode); + this.parentNode.appendChild(this.label); + } + } +} + +/* start of the radioButtonGroup object */ + +function radioButtonGroup(id,functionToCall) { + var nrArguments = 2; + if (arguments.length == nrArguments) { + this.id = id; + if (typeof(functionToCall) == "function" || typeof(functionToCall) == "object" || typeof(functionToCall) == undefined) { + this.functionToCall = functionToCall; + } + else { + alert("Error in radiobutton with ("+id+"): argument functionToCall is not of type 'function', 'object' or undefined!"); + } + this.checkBoxes = new Array(); //this array will hold checkbox objects + this.selectedId = undefined; //holds the id of the active radio button + this.selectedIndex = undefined; //holds the index of the active radio button + //timer stuff + this.timer = new Timer(this); //a Timer instance for calling the functionToCall + this.timerMs = 200; //a constant of this object that is used in conjunction with the timer - functionToCall is called after 200 ms + } + else { + alert("Error in radiobutton with ("+id+"): wrong nr of arguments! You have to pass over "+nrArguments+" parameters."); + } +} + +radioButtonGroup.prototype.addCheckBox = function(checkBoxObj) { + this.checkBoxes.push(checkBoxObj); + if (checkBoxObj.checkedStatus) { + this.selectedId = checkBoxObj.id; + this.selectedIndex = this.checkBoxes.length - 1; + } +} + +//change radio button selection by id +radioButtonGroup.prototype.selectById = function(cbId,fireFunction) { + var found = false; + for (var i=0;i<this.checkBoxes.length;i++) { + if (this.checkBoxes[i].id == cbId) { + this.selectedId = cbId; + this.selectedIndex = i; + if (this.checkBoxes[i].checkedStatus == false) { + this.checkBoxes[i].check(false); + } + found = true; + } + else { + this.checkBoxes[i].uncheck(false); + } + } + if (found) { + if (fireFunction) { + this.timer.setTimeout("fireFunction",this.timerMs); + } + } + else { + alert("Error in radiobutton with ("+this.id+"): could not find checkbox with id '"+cbId+"'"); + } +} + +//change radio button selection by label name +radioButtonGroup.prototype.selectByLabelname = function(labelName,fireFunction) { + var id = -1; + for (var i=0;i<this.checkBoxes.length;i++) { + if (this.checkBoxes[i].labelText == labelName) { + id = this.checkBoxes[i].id; + } + } + if (id == -1) { + alert("Error in radiobutton with ("+this.id+"): could not find checkbox with label '"+labelName+"'"); + } + else { + this.selectById(id,fireFunction); + } +} + +radioButtonGroup.prototype.fireFunction = function() { + if (typeof(this.functionToCall) == "function") { + this.functionToCall(this.id,this.selectedId,this.checkBoxes[this.selectedIndex].labelText); + } + if (typeof(this.functionToCall) == "object") { + this.functionToCall.radioButtonChanged(this.id,this.selectedId,this.checkBoxes[this.selectedIndex].labelText); + } + if (typeof(this.functionToCall) == undefined) { + return; + } +} \ No newline at end of file diff -r 878ca31a4995 -r c2d042ba8cd0 static/scripts/helper_functions.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/scripts/helper_functions.js Wed Oct 14 15:47:58 2009 -0400 @@ -0,0 +1,817 @@ +/** + * @fileoverview + * + * ECMAScript <a href="http://www.carto.net/papers/svg/resources/helper_functions.html">helper functions</a>, main purpose is to serve in SVG mapping or other SVG based web applications + * + * This ECMA script library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library (http://www.carto.net/papers/svg/resources/lesser_gpl.txt); if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Please report bugs and send improvements to neumann@karto.baug.ethz.ch + * If you use these scripts, please link to the original (http://www.carto.net/papers/svg/resources/helper_functions.html) + * somewhere in the source-code-comment or the "about" of your project and give credits, thanks! + * + * See <a href="js_docs_out/overview-summary-helper_functions.js.html">documentation</a>. + * + * @author Andreas Neumann a.neumann@carto.net + * @copyright LGPL 2.1 <a href="http://www.gnu.org/copyleft/lesser.txt">Gnu LGPL 2.1</a> + * @credits Bruce Rindahl, numerous people on svgdevelopers@yahoogroups.com + */ + +//global variables necessary to create elements in these namespaces, do not delete them!!!! + +/** + * This variable is a shortcut to the full URL of the SVG namespace + * @final + * @type String + */ +var svgNS = "http://www.w3.org/2000/svg"; + +/** + * This variable is a shortcut to the full URL of the XLink namespace + * @final + * @type String + */ +var xlinkNS = "http://www.w3.org/1999/xlink"; + +/** + * This variable is a shortcut to the full URL of the attrib namespace + * @final + * @type String + */ +var cartoNS = "http://www.carto.net/attrib"; + +/** + * This variable is a alias to the full URL of the attrib namespace + * @final + * @type String + */ +var attribNS = "http://www.carto.net/attrib"; + +/** + * This variable is a alias to the full URL of the Batik extension namespace + * @final + * @type String + */ +var batikNS = "http://xml.apache.org/batik/ext"; + +/** + * Returns the polar direction from a given vector + * @param {Number} xdiff the x-part of the vector + * @param {Number} ydiff the y-part of the vector + * @return direction the direction in radians + * @type Number + * @version 1.0 (2007-04-30) + * @see #toPolarDist + * @see #toRectX + * @see #toRectY + */ +function toPolarDir(xdiff,ydiff) { + var direction = (Math.atan2(ydiff,xdiff)); + return(direction); +} + +/** + * Returns the polar distance from a given vector + * @param {Number} xdiff the x-part of the vector + * @param {Number} ydiff the y-part of the vector + * @return distance the distance + * @type Number + * @version 1.0 (2007-04-30) + * @see #toPolarDir + * @see #toRectX + * @see #toRectY + */ +function toPolarDist(xdiff,ydiff) { + var distance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); + return(distance); +} + +/** + * Returns the x-part of a vector from a given direction and distance + * @param {Number} direction the direction (in radians) + * @param {Number} distance the distance + * @return x the x-part of the vector + * @type Number + * @version 1.0 (2007-04-30) + * @see #toPolarDist + * @see #toPolarDir + * @see #toRectY + */ +function toRectX(direction,distance) { + var x = distance * Math.cos(direction); + return(x); +} + +/** + * Returns the y-part of the vector from a given direction and distance + * @param {Number} direction the direction (in radians) + * @param {Number} distance the distance + * @return y the y-part of the vector + * @type Number + * @version 1.0 (2007-04-30) + * @see #toPolarDist + * @see #toPolarDir + * @see #toRectX + */ +function toRectY(direction,distance) { + y = distance * Math.sin(direction); + return(y); +} + +/** + * Converts degrees to radians + * @param {Number} deg the degree value + * @return rad the radians value + * @type Number + * @version 1.0 (2007-04-30) + * @see #RadToDeg + */ +function DegToRad(deg) { + return (deg / 180.0 * Math.PI); +} + +/** + * Converts radians to degrees + * @param {Number} rad the radians value + * @return deg the degree value + * @type Number + * @version 1.0 (2007-04-30) + * @see #DegToRad + */ +function RadToDeg(rad) { + return (rad / Math.PI * 180.0); +} + +/** + * Converts decimal degrees to degrees, minutes, seconds + * @param {Number} dd the decimal degree value + * @return degrees the degree values in the following notation: {deg:degrees,min:minutes,sec:seconds} + * @type literal + * @version 1.0 (2007-04-30) + * @see #dms2dd + */ +function dd2dms(dd) { + var minutes = (Math.abs(dd) - Math.floor(Math.abs(dd))) * 60; + var seconds = (minutes - Math.floor(minutes)) * 60; + var minutes = Math.floor(minutes); + if (dd >= 0) { + var degrees = Math.floor(dd); + } + else { + var degrees = Math.ceil(dd); + } + return {deg:degrees,min:minutes,sec:seconds}; +} + +/** + * Converts degrees, minutes and seconds to decimal degrees + * @param {Number} deg the degree value + * @param {Number} min the minute value + * @param {Number} sec the second value + * @return deg the decimal degree values + * @type Number + * @version 1.0 (2007-04-30) + * @see #dd2dms + */ +function dms2dd(deg,min,sec) { + if (deg < 0) { + return deg - (min / 60) - (sec / 3600); + } + else { + return deg + (min / 60) + (sec / 3600); + } +} + +/** + * log function, missing in the standard Math object + * @param {Number} x the value where the log function should be applied to + * @param {Number} b the base value for the log function + * @return logResult the result of the log function + * @type Number + * @version 1.0 (2007-04-30) + */ +function log(x,b) { + if(b==null) b=Math.E; + return Math.log(x)/Math.log(b); +} + +/** + * interpolates a value (e.g. elevation) bilinearly based on the position within a cell with 4 corner values + * @param {Number} za the value at the upper left corner of the cell + * @param {Number} zb the value at the upper right corner of the cell + * @param {Number} zc the value at the lower right corner of the cell + * @param {Number} zd the value at the lower left corner of the cell + * @param {Number} xpos the x position of the point where a new value should be interpolated + * @param {Number} ypos the y position of the point where a new value should be interpolated + * @param {Number} ax the x position of the lower left corner of the cell + * @param {Number} ay the y position of the lower left corner of the cell + * @param {Number} cellsize the size of the cell + * @return interpol_value the result of the bilinear interpolation function + * @type Number + * @version 1.0 (2007-04-30) + */ +function intBilinear(za,zb,zc,zd,xpos,ypos,ax,ay,cellsize) { //bilinear interpolation function + var e = (xpos - ax) / cellsize; + var f = (ypos - ay) / cellsize; + + //calculation of weights + var wa = (1 - e) * (1 - f); + var wb = e * (1 - f); + var wc = e * f; + var wd = f * (1 - e); + + var interpol_value = wa * zc + wb * zd + wc * za + wd * zb; + return interpol_value; +} + +/** + * tests if a given point is left or right of a given line + * @param {Number} pointx the x position of the given point + * @param {Number} pointy the y position of the given point + * @param {Number} linex1 the x position of line's start point + * @param {Number} liney1 the y position of line's start point + * @param {Number} linex2 the x position of line's end point + * @param {Number} liney2 the y position of line's end point + * @return leftof the result of the leftOfTest, 1 means leftOf, 0 means rightOf + * @type Number (integer, 0|1) + * @version 1.0 (2007-04-30) + */ +function leftOfTest(pointx,pointy,linex1,liney1,linex2,liney2) { + var result = (liney1 - pointy) * (linex2 - linex1) - (linex1 - pointx) * (liney2 - liney1); + if (result < 0) { + var leftof = 1; //case left of + } + else { + var leftof = 0; //case left of + } + return leftof; +} + +/** + * calculates the distance between a given point and a given line + * @param {Number} pointx the x position of the given point + * @param {Number} pointy the y position of the given point + * @param {Number} linex1 the x position of line's start point + * @param {Number} liney1 the y position of line's start point + * @param {Number} linex2 the x position of line's end point + * @param {Number} liney2 the y position of line's end point + * @return distance the result of the leftOfTest, 1 means leftOf, 0 means rightOf + * @type Number + * @version 1.0 (2007-04-30) + */ +function distFromLine(xpoint,ypoint,linex1,liney1,linex2,liney2) { + var dx = linex2 - linex1; + var dy = liney2 - liney1; + var distance = (dy * (xpoint - linex1) - dx * (ypoint - liney1)) / Math.sqrt(Math.pow(dx,2) + Math.pow(dy,2)); + return distance; +} + +/** + * calculates the angle between two vectors (lines) + * @param {Number} ax the x part of vector a + * @param {Number} ay the y part of vector a + * @param {Number} bx the x part of vector b + * @param {Number} by the y part of vector b + * @return angle the angle in radians + * @type Number + * @version 1.0 (2007-04-30) + * @credits <a href="http://www.mathe-online.at/mathint/vect2/i.html#Winkel">Mathe Online (Winkel)</a> + */ +function angleBetwTwoLines(ax,ay,bx,by) { + var angle = Math.acos((ax * bx + ay * by) / (Math.sqrt(Math.pow(ax,2) + Math.pow(ay,2)) * Math.sqrt(Math.pow(bx,2) + Math.pow(by,2)))); + return angle; +} + +/** + * calculates the bisector vector for two given vectors + * @param {Number} ax the x part of vector a + * @param {Number} ay the y part of vector a + * @param {Number} bx the x part of vector b + * @param {Number} by the y part of vector b + * @return c the resulting vector as an Array, c[0] is the x part of the vector, c[1] is the y part + * @type Array + * @version 1.0 (2007-04-30) + * @credits <a href="http://www.mathe-online.at/mathint/vect1/i.html#Winkelsymmetrale">Mathe Online (Winkelsymmetrale)</a> + * see #calcBisectorAngle + * */ +function calcBisectorVector(ax,ay,bx,by) { + var betraga = Math.sqrt(Math.pow(ax,2) + Math.pow(ay,2)); + var betragb = Math.sqrt(Math.pow(bx,2) + Math.pow(by,2)); + var c = new Array(); + c[0] = ax / betraga + bx / betragb; + c[1] = ay / betraga + by / betragb; + return c; +} + +/** + * calculates the bisector angle for two given vectors + * @param {Number} ax the x part of vector a + * @param {Number} ay the y part of vector a + * @param {Number} bx the x part of vector b + * @param {Number} by the y part of vector b + * @return angle the bisector angle in radians + * @type Number + * @version 1.0 (2007-04-30) + * @credits <a href="http://www.mathe-online.at/mathint/vect1/i.html#Winkelsymmetrale">Mathe Online (Winkelsymmetrale)</a> + * see #calcBisectorVector + * */ +function calcBisectorAngle(ax,ay,bx,by) { + var betraga = Math.sqrt(Math.pow(ax,2) + Math.pow(ay,2)); + var betragb = Math.sqrt(Math.pow(bx,2) + Math.pow(by,2)); + var c1 = ax / betraga + bx / betragb; + var c2 = ay / betraga + by / betragb; + var angle = toPolarDir(c1,c2); + return angle; +} + +/** + * calculates the intersection point of two given lines + * @param {Number} line1x1 the x the start point of line 1 + * @param {Number} line1y1 the y the start point of line 1 + * @param {Number} line1x2 the x the end point of line 1 + * @param {Number} line1y2 the y the end point of line 1 + * @return interSectPoint the intersection point, interSectPoint.x contains x-part, interSectPoint.y the y-part of the resulting coordinate + * @type Object + * @version 1.0 (2007-04-30) + * @credits <a href="http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/">P. Bourke</a> + */ +function intersect2lines(line1x1,line1y1,line1x2,line1y2,line2x1,line2y1,line2x2,line2y2) { + var interSectPoint = new Object(); + var denominator = (line2y2 - line2y1)*(line1x2 - line1x1) - (line2x2 - line2x1)*(line1y2 - line1y1); + if (denominator == 0) { + alert("lines are parallel"); + } + else { + var ua = ((line2x2 - line2x1)*(line1y1 - line2y1) - (line2y2 - line2y1)*(line1x1 - line2x1)) / denominator; + var ub = ((line1x2 - line1x1)*(line1y1 - line2y1) - (line1y2 - line1y1)*(line1x1 - line2x1)) / denominator; + } + interSectPoint["x"] = line1x1 + ua * (line1x2 - line1x1); + interSectPoint["y"] = line1y1 + ua * (line1y2 - line1y1); + return interSectPoint; +} + +/** + * reformats a given number to a string by adding separators at every third digit + * @param {String|Number} inputNumber the input number, can be of type number or string + * @param {String} separator the separator, e.g. ' or , + * @return newString the intersection point, interSectPoint.x contains x-part, interSectPoint.y the y-part of the resulting coordinate + * @type String + * @version 1.0 (2007-04-30) + */ +function formatNumberString(inputNumber,separator) { + //check if of type string, if number, convert it to string + if (typeof(inputNumber) == "Number") { + var myTempString = inputNumber.toString(); + } + else { + var myTempString = inputNumber; + } + var newString=""; + //if it contains a comma, it will be split + var splitResults = myTempString.split("."); + var myCounter = splitResults[0].length; + if (myCounter > 3) { + while(myCounter > 0) { + if (myCounter > 3) { + newString = separator + splitResults[0].substr(myCounter - 3,3) + newString; + } + else { + newString = splitResults[0].substr(0,myCounter) + newString; + } + myCounter -= 3; + } + } + else { + newString = splitResults[0]; + } + //concatenate if it contains a comma + if (splitResults[1]) { + newString = newString + "." + splitResults[1]; + } + return newString; +} + +/** + * writes a status text message out to a SVG text element's first child + * @param {String} statusText the text message to be displayed + * @version 1.0 (2007-04-30) + */ + function statusChange(statusText) { + document.getElementById("statusText").firstChild.nodeValue = "Statusbar: " + statusText; +} + +/** + * scales an SVG element, requires that the element has an x and y attribute (e.g. circle, ellipse, use element, etc.) + * @param {dom::Event} evt the evt object that triggered the scaling + * @param {Number} factor the scaling factor + * @version 1.0 (2007-04-30) + */ +function scaleObject(evt,factor) { + //reference to the currently selected object + var element = evt.currentTarget; + var myX = element.getAttributeNS(null,"x"); + var myY = element.getAttributeNS(null,"y"); + var newtransform = "scale(" + factor + ") translate(" + (myX * 1 / factor - myX) + " " + (myY * 1 / factor - myY) +")"; + element.setAttributeNS(null,'transform', newtransform); +} + +/** + * returns the transformation matrix (ctm) for the given node up to the root element + * the basic use case is to provide a wrapper function for the missing SVGLocatable.getTransformToElement method (missing in ASV3) + * @param {svg::SVGTransformable} node the node reference for the SVGElement the ctm is queried + * @return CTM the current transformation matrix from the given node to the root element + * @type svg::SVGMatrix + * @version 1.0 (2007-05-01) + * @credits <a href="http://www.kevlindev.com/tutorials/basics/transformations/toUserSpace/index.htm">Kevin Lindsey (toUserSpace)</a> + * @see #getTransformToElement + */ +function getTransformToRootElement(node) { + try { + //this part is for fully conformant players (like Opera, Batik, Firefox, Safari ...) + var CTM = node.getTransformToElement(document.documentElement); + } + catch (ex) { + //this part is for ASV3 or other non-conformant players + // Initialize our CTM the node's Current Transformation Matrix + var CTM = node.getCTM(); + // Work our way through the ancestor nodes stopping at the SVG Document + while ( ( node = node.parentNode ) != document ) { + // Multiply the new CTM to the one with what we have accumulated so far + CTM = node.getCTM().multiply(CTM); + } + } + return CTM; +} + +/** + * returns the transformation matrix (ctm) for the given dom::Node up to a different dom::Node + * the basic use case is to provide a wrapper function for the missing SVGLocatable.getTransformToElement method (missing in ASV3) + * @param {svg::SVGTransformable} node the node reference for the element the where the ctm should be calculated from + * @param {svg::SVGTransformable} targetNode the target node reference for the element the ctm should be calculated to + * @return CTM the current transformation matrix from the given node to the target element + * @type svg::SVGMatrix + * @version 1.0 (2007-05-01) + * @credits <a href="http://www.kevlindev.com/tutorials/basics/transformations/toUserSpace/index.htm">Kevin Lindsey (toUserSpace)</a> + * @see #getTransformToRootElement + */ +function getTransformToElement(node,targetNode) { + try { + //this part is for fully conformant players + var CTM = node.getTransformToElement(targetNode); + } + catch (ex) { + //this part is for ASV3 or other non-conformant players + // Initialize our CTM the node's Current Transformation Matrix + var CTM = node.getCTM(); + // Work our way through the ancestor nodes stopping at the SVG Document + while ( ( node = node.parentNode ) != targetNode ) { + // Multiply the new CTM to the one with what we have accumulated so far + CTM = node.getCTM().multiply(CTM); + } + } + return CTM; +} + +/** + * converts HSV to RGB values + * @param {Number} hue the hue value (between 0 and 360) + * @param {Number} sat the saturation value (between 0 and 1) + * @param {Number} val the value value (between 0 and 1) + * @return rgbArr the rgb values (associative array or object, the keys are: red,green,blue), all values are scaled between 0 and 255 + * @type Object + * @version 1.0 (2007-05-01) + * @see #rgb2hsv + */ +function hsv2rgb(hue,sat,val) { + var rgbArr = new Object(); + if ( sat == 0) { + rgbArr["red"] = Math.round(val * 255); + rgbArr["green"] = Math.round(val * 255); + rgbArr["blue"] = Math.round(val * 255); + } + else { + var h = hue / 60; + var i = Math.floor(h); + var f = h - i; + if (i % 2 == 0) { + f = 1 - f; + } + var m = val * (1 - sat); + var n = val * (1 - sat * f); + switch(i) { + case 0: + rgbArr["red"] = val; + rgbArr["green"] = n; + rgbArr["blue"] = m; + break; + case 1: + rgbArr["red"] = n; + rgbArr["green"] = val; + rgbArr["blue"] = m; + break; + case 2: + rgbArr["red"] = m; + rgbArr["green"] = val; + rgbArr["blue"] = n; + break; + case 3: + rgbArr["red"] = m; + rgbArr["green"] = n; + rgbArr["blue"] = val; + break; + case 4: + rgbArr["red"] = n; + rgbArr["green"] = m; + rgbArr["blue"] = val; + break; + case 5: + rgbArr["red"] = val; + rgbArr["green"] = m; + rgbArr["blue"] = n; + break; + case 6: + rgbArr["red"] = val; + rgbArr["green"] = n; + rgbArr["blue"] = m; + break; + } + rgbArr["red"] = Math.round(rgbArr["red"] * 255); + rgbArr["green"] = Math.round(rgbArr["green"] * 255); + rgbArr["blue"] = Math.round(rgbArr["blue"] * 255); + } + return rgbArr; +} + +/** + * converts RGB to HSV values + * @param {Number} red the hue value (between 0 and 255) + * @param {Number} green the saturation value (between 0 and 255) + * @param {Number} blue the value value (between 0 and 255) + * @return hsvArr the hsv values (associative array or object, the keys are: hue (0-360),sat (0-1),val (0-1)) + * @type Object + * @version 1.0 (2007-05-01) + * @see #hsv2rgb + */ +function rgb2hsv(red,green,blue) { + var hsvArr = new Object(); + red = red / 255; + green = green / 255; + blue = blue / 255; + myMax = Math.max(red, Math.max(green,blue)); + myMin = Math.min(red, Math.min(green,blue)); + v = myMax; + if (myMax > 0) { + s = (myMax - myMin) / myMax; + } + else { + s = 0; + } + if (s > 0) { + myDiff = myMax - myMin; + rc = (myMax - red) / myDiff; + gc = (myMax - green) / myDiff; + bc = (myMax - blue) / myDiff; + if (red == myMax) { + h = (bc - gc) / 6; + } + if (green == myMax) { + h = (2 + rc - bc) / 6; + } + if (blue == myMax) { + h = (4 + gc - rc) / 6; + } + } + else { + h = 0; + } + if (h < 0) { + h += 1; + } + hsvArr["hue"] = Math.round(h * 360); + hsvArr["sat"] = s; + hsvArr["val"] = v; + return hsvArr; +} + +/** + * populates an array such that it can be addressed by both a key or an index nr, + * note that both Arrays need to be of the same length + * @param {Array} arrayKeys the array containing the keys + * @param {Array} arrayValues the array containing the values + * @return returnArray the resulting array containing both associative values and also a regular indexed array + * @type Array + * @version 1.0 (2007-05-01) + */ +function arrayPopulate(arrayKeys,arrayValues) { + var returnArray = new Array(); + if (arrayKeys.length != arrayValues.length) { + alert("error: arrays do not have the same length!"); + } + else { + for (i=0;i<arrayKeys.length;i++) { + returnArray[arrayKeys[i]] = arrayValues[i]; + } + } + return returnArray; +} + +/** + * Wrapper object for network requests, uses getURL or XMLHttpRequest depending on availability + * The callBackFunction receives a XML or text node representing the rootElement + * of the fragment received or the return text, depending on the returnFormat. + * See also the following <a href="http://www.carto.net/papers/svg/network_requests/">documentation</a>. + * @class this is a wrapper object to provide network request functionality (get|post) + * @param {String} url the URL/IRI of the network resource to be called + * @param {Function|Object} callBackFunction the callBack function or object that is called after the data was received, in case of an object, the method 'receiveData' is called; both the function and the object's 'receiveData' method get 2 return parameters: 'node.firstChild'|text (the root element of the XML or text resource), this.additionalParams (if defined) + * @param {String} returnFormat the return format, either 'xml' or 'json' (or text) + * @param {String} method the method of the network request, either 'get' or 'post' + * @param {String|Undefined} postText the String containing the post text (optional) or Undefined (if not a 'post' request) + * @param {Object|Array|String|Number|Undefined} additionalParams additional parameters that will be passed to the callBackFunction or object (optional) or Undefined + * @return a new getData instance + * @type getData + * @constructor + * @version 1.0 (2007-02-23) + */ +function getData(url,callBackFunction,returnFormat,method,postText,additionalParams) { + this.url = url; + this.callBackFunction = callBackFunction; + this.returnFormat = returnFormat; + this.method = method; + this.additionalParams = additionalParams; + if (method != "get" && method != "post") { + alert("Error in network request: parameter 'method' must be 'get' or 'post'"); + } + this.postText = postText; + this.xmlRequest = null; //@private reference to the XMLHttpRequest object +} + +/** + * triggers the network request defined in the constructor + */ +getData.prototype.getData = function() { + //call getURL() if available + if (window.getURL) { + if (this.method == "get") { + getURL(this.url,this); + } + if (this.method == "post") { + postURL(this.url,this.postText,this); + } + } + //or call XMLHttpRequest() if available + else if (window.XMLHttpRequest) { + var _this = this; + this.xmlRequest = new XMLHttpRequest(); + if (this.method == "get") { + if (this.returnFormat == "xml") { + this.xmlRequest.overrideMimeType("text/xml"); + } + this.xmlRequest.open("GET",this.url,true); + } + if (this.method == "post") { + this.xmlRequest.open("POST",this.url,true); + } + this.xmlRequest.onreadystatechange = function() {_this.handleEvent()}; + if (this.method == "get") { + this.xmlRequest.send(null); + } + if (this.method == "post") { + //test if postText exists and is of type string + var reallyPost = true; + if (!this.postText) { + reallyPost = false; + alert("Error in network post request: missing parameter 'postText'!"); + } + if (typeof(this.postText) != "string") { + reallyPost = false; + alert("Error in network post request: parameter 'postText' has to be of type 'string')"); + } + if (reallyPost) { + this.xmlRequest.send(this.postText); + } + } + } + //write an error message if neither method is available + else { + alert("your browser/svg viewer neither supports window.getURL nor window.XMLHttpRequest!"); + } +} + +/** + * this is the callback method for the getURL() or postURL() case + * @private + */ +getData.prototype.operationComplete = function(data) { + //check if data has a success property + if (data.success) { + //parse content of the XML format to the variable "node" + if (this.returnFormat == "xml") { + //convert the text information to an XML node and get the first child + var node = parseXML(data.content,document); + //distinguish between a callback function and an object + if (typeof(this.callBackFunction) == "function") { + this.callBackFunction(node.firstChild,this.additionalParams); + } + if (typeof(this.callBackFunction) == "object") { + this.callBackFunction.receiveData(node.firstChild,this.additionalParams); + } + } + if (this.returnFormat == "json") { + if (typeof(this.callBackFunction) == "function") { + this.callBackFunction(data.content,this.additionalParams); + } + if (typeof(this.callBackFunction) == "object") { + this.callBackFunction.receiveData(data.content,this.additionalParams); + } + } + } + else { + alert("something went wrong with dynamic loading of geometry!"); + } +} + +/** + * this is the callback method for the XMLHttpRequest case + * @private + */ +getData.prototype.handleEvent = function() { + if (this.xmlRequest.readyState == 4) { + if (this.returnFormat == "xml") { + //we need to import the XML node first + var importedNode = document.importNode(this.xmlRequest.responseXML.documentElement,true); + if (typeof(this.callBackFunction) == "function") { + this.callBackFunction(importedNode,this.additionalParams); + } + if (typeof(this.callBackFunction) == "object") { + this.callBackFunction.receiveData(importedNode,this.additionalParams); + } + } + if (this.returnFormat == "json") { + if (typeof(this.callBackFunction) == "function") { + this.callBackFunction(this.xmlRequest.responseText,this.additionalParams); + } + if (typeof(this.callBackFunction) == "object") { + this.callBackFunction.receiveData(this.xmlRequest.responseText,this.additionalParams); + } + } + } +} + +/** + * Serializes an XML node and returns a string representation. Wrapper function to hide implementation differences. + * This can be used for debugging purposes or to post data to a server or network resource. + * @param {dom::Node} node the DOM node reference + * @return textRepresentation the String representation of the XML node + * @type String + * @version 1.0 (2007-05-01) + * @see getData + */ +function serializeNode(node) { + if (typeof XMLSerializer != 'undefined') { + return new XMLSerializer().serializeToString(node); + } + else if (typeof node.xml != 'undefined') { + return node.xml; + } + else if (typeof printNode != 'undefined') { + return printNode(node); + } + else if (typeof Packages != 'undefined') { + try { + var stringWriter = new java.io.StringWriter(); + Packages.org.apache.batik.dom.util.DOMUtilities.writeNode(node,stringWriter); + return stringWriter.toString(); + } + catch (e) { + alert("Sorry, your SVG viewer does not support the printNode/serialize function."); + return ''; + } + } + else { + alert("Sorry, your SVG viewer does not support the printNode/serialize function."); + return ''; + } +} + +/** + * Starts a SMIL animation element with the given id by triggering the '.beginElement()' method. + * This is a convenience (shortcut) function. + * @param {String} id a valid id of a valid SMIL animation element + * @version 1.0 (2007-05-01) + */ +//starts an animtion with the given id +//this function is useful in combination with window.setTimeout() +function startAnimation(id) { + document.getElementById(id).beginElement(); +} diff -r 878ca31a4995 -r c2d042ba8cd0 static/scripts/timer.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/scripts/timer.js Wed Oct 14 15:47:58 2009 -0400 @@ -0,0 +1,74 @@ +// source/credits: "Algorithm": http://www.codingforums.com/showthread.php?s=&threadid=10531 +// The constructor should be called with +// the parent object (optional, defaults to window). + +function Timer(){ + this.obj = (arguments.length)?arguments[0]:window; + return this; +} + +// The set functions should be called with: +// - The name of the object method (as a string) (required) +// - The millisecond delay (required) +// - Any number of extra arguments, which will all be +// passed to the method when it is evaluated. + +Timer.prototype.setInterval = function(func, msec){ + var i = Timer.getNew(); + var t = Timer.buildCall(this.obj, i, arguments); + Timer.set[i].timer = window.setInterval(t,msec); + return i; +} +Timer.prototype.setTimeout = function(func, msec){ + var i = Timer.getNew(); + Timer.buildCall(this.obj, i, arguments); + Timer.set[i].timer = window.setTimeout("Timer.callOnce("+i+");",msec); + return i; +} + +// The clear functions should be called with +// the return value from the equivalent set function. + +Timer.prototype.clearInterval = function(i){ + if(!Timer.set[i]) return; + window.clearInterval(Timer.set[i].timer); + Timer.set[i] = null; +} +Timer.prototype.clearTimeout = function(i){ + if(!Timer.set[i]) return; + window.clearTimeout(Timer.set[i].timer); + Timer.set[i] = null; +} + +// Private data + +Timer.set = new Array(); +Timer.buildCall = function(obj, i, args){ + var t = ""; + Timer.set[i] = new Array(); + if(obj != window){ + Timer.set[i].obj = obj; + t = "Timer.set["+i+"].obj."; + } + t += args[0]+"("; + if(args.length > 2){ + Timer.set[i][0] = args[2]; + t += "Timer.set["+i+"][0]"; + for(var j=1; (j+2)<args.length; j++){ + Timer.set[i][j] = args[j+2]; + t += ", Timer.set["+i+"]["+j+"]"; + }} + t += ");"; + Timer.set[i].call = t; + return t; +} +Timer.callOnce = function(i){ + if(!Timer.set[i]) return; + eval(Timer.set[i].call); + Timer.set[i] = null; +} +Timer.getNew = function(){ + var i = 0; + while(Timer.set[i]) i++; + return i; +} \ No newline at end of file