001package votorola.s.gwt.scene.axial; // Copyright 2010, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. 002 003import com.google.gwt.dom.client.Element; 004import com.google.web.bindery.event.shared.HandlerRegistration; 005import com.google.gwt.user.client.ui.*; 006import com.google.gwt.view.client.SelectionChangeEvent; 007import org.vectomatic.dom.svg.*; 008import org.vectomatic.dom.svg.utils.*; 009import votorola.a.web.gwt.*; 010import votorola.s.gwt.scene.*; 011import votorola.s.gwt.scene.feed.*; 012import votorola.s.gwt.stage.poll.*; 013import votorola.g.hold.*; 014import votorola.g.lang.*; 015import votorola.g.web.gwt.*; 016 017import static org.vectomatic.dom.svg.OMSVGLength.SVG_LENGTHTYPE_EMS; 018import static org.vectomatic.dom.svg.OMSVGLength.SVG_LENGTHTYPE_PERCENTAGE; 019 020 021/** A view of a triaxial map done as a scalable vector graphic (SVG). It uses the x and y 022 * axes of the viewport plane, and simulates the z-axis with points plotted as circles of 023 * varying brightness. 024 * 025 * @see <a href='http://reluk.ca/y/vw/scene/#c=DT'>Live example of a 026 * TriaxialPollMapV (right)</a> 027 */ 028public final class TriaxialPollMapV extends HTML 029{ 030 031 032 /** Constructs a TriaxialPollMapV. 033 */ 034 public TriaxialPollMapV( TriaxialPollMap _map ) 035 { 036 map = _map; 037 038 final OMSVGDocument doc = OMSVGParser.createDocument(); 039 svg = doc.createSVGSVGElement(); 040 getElement().appendChild( svg.getElement() ); 041 svg.addClassNameBaseVal( "axial-TriaxialPollMapV" ); 042 svg.setWidth( SVG_LENGTHTYPE_PERCENTAGE, 100 ); 043 svg.setHeight( SVG_LENGTHTYPE_PERCENTAGE, 100 ); 044 045 for( final TriaxialPollMap.MappedPoll poll: map.polls().values() ) 046 { 047 final OMSVGCircleElement circle = doc.createSVGCircleElement(); 048 svg.appendChild( circle ); 049 circle.getR().getBaseVal().newValueSpecifiedUnits( SVG_LENGTHTYPE_EMS, 0.5f ); 050 051 final Element circleElement = (Element)circle.getNode(); 052 // circleElement.setPropertyObject( "poll", poll ); 053 /// we can look it up in our own map now that we set the 'id' 054 circleElement.setAttribute( "id", pollNameToID( poll.name() )); 055 } 056 } 057 058 059 060 // - W i d g e t ---------------------------------------------------------------------- 061 062 063 protected @Override void onLoad() 064 { 065 spool = new Spool1(); 066 new Scoper(); 067 new Spotlighter(); 068 069 super.onLoad(); 070 } 071 072 073 074 protected @Override void onUnload() 075 { 076 super.onUnload(); 077 078 spool.unwind(); 079 spool = null; // single use 080 } 081 082 083 084//// P r i v a t e /////////////////////////////////////////////////////////////////////// 085 086 087 private static String idToPollName( final String id ) 088 { 089 final StringBuilder b = GWTX.stringBuilderClear(); 090 b.append( id ); 091 b.delete( 0, POLL_ID_PREFIX.length() ); 092 votorola.a.count.PollService.xmlColonNameToPollName( b ); 093 return b.toString(); 094 } 095 096 097 098 private final TriaxialPollMap map; 099 100 101 102 private static final String POLL_ID_PREFIX = "p-"; 103 104 105 106 private static String pollNameToID( final String pollName ) 107 { 108 final StringBuilder b = GWTX.stringBuilderClear(); 109 b.append( pollName ); 110 votorola.a.count.PollService.pollNameToXMLColonName( b ); 111 b.insert( 0, POLL_ID_PREFIX ); 112 return b.toString(); 113 } 114 115 116 117 private Spool spool; 118 119 120 121 private final OMSVGSVGElement svg; 122 123 124 125 // ==================================================================================== 126 127 128 private final class Scoper implements ScopeChangeHandler 129 { 130 131 Scoper() 132 { 133 spool.add( new Hold() 134 { 135 final HandlerRegistration hR = map.scoping().addHandler( Scoper.this ); 136 public void release() { hR.removeHandler(); } 137 }); 138 rescope(); // init state 139 } 140 141 142 public void onScopeChange( final ScopeChangeEvent e ) { rescope(); } 143 144 145 private void rescope() 146 { 147 final DiaxialScoping scoping = map.scoping(); 148 final float xTrans = -scoping.xMin(); 149 final float yTrans = scoping.yMax() - 1; // opposite because SVG origin is at top 150 final float xZoomPercent = 100 / (scoping.xMax() + xTrans); 151 final float yZoomPercent = 100 / (scoping.yMax() - scoping.yMin()); 152 153 final OMNodeList<OMSVGCircleElement> childList = svg.getElementsByTagName( "circle" ); 154 for( int c = childList.getLength() - 1; c >= 0; --c ) 155 { 156 final OMSVGCircleElement circle = childList.getItem( c ); 157 // final TriaxialPollMap.MappedPoll poll = (TriaxialPollMap.MappedPoll) 158 // ((Element)circle.getNode()).getPropertyObject( "poll" ); 159 final TriaxialPollMap.MappedPoll poll = 160 map.polls().get( idToPollName( circle.getAttribute( "id" ))); 161 final float x = poll.x(); 162 final float y = 1 - poll.y(); // reflected, to put SVG origin at bottom 163 circle.getCx().getBaseVal().newValueSpecifiedUnits( SVG_LENGTHTYPE_PERCENTAGE, 164 (x + xTrans) * xZoomPercent ); 165 circle.getCy().getBaseVal().newValueSpecifiedUnits( SVG_LENGTHTYPE_PERCENTAGE, 166 (y + yTrans) * yZoomPercent ); 167 168 final String brightness = Integer.toHexString( Math.round( poll.z() * 0x90 )); // CSS range 0 - FF 169 final boolean needsPadding = brightness.length() == 1; // do our own padding, because GWT NumberFormat cannot do hexadecimal 170 final StringBuilder b = GWTX.stringBuilderClear(); 171 b.append( '#' ); 172 for( int n = 0; n < 3; n++ ) 173 { 174 if( needsPadding ) b.append( '0' ); 175 176 b.append( brightness ); 177 } 178 circle.getStyle().setSVGProperty( "fill", b.toString() ); 179 } 180 } 181 } 182 183 184 185 // ==================================================================================== 186 187 188 private final class Spotlighter implements ScopeChangeHandler, SelectionChangeEvent.Handler 189 { 190 191 Spotlighter() 192 { 193 spool.add( new Hold() 194 { 195 final HandlerRegistration hR = map.scoping().addHandler( Spotlighter.this ); 196 public void release() { hR.removeHandler(); } 197 }); 198 spool.add( new Hold() 199 { 200 final HandlerRegistration hR = 201 Scenes.i().biteSelection().addSelectionChangeHandler( Spotlighter.this ); 202 public void release() { hR.removeHandler(); } 203 }); 204 respotlight(); // init state 205 } 206 207 208 private String idLast; 209 210 211 public void onSelectionChange( SelectionChangeEvent e ) { respotlight(); } 212 213 214 public void onScopeChange( final ScopeChangeEvent e ) { respotlight(); } 215 216 217 private void respotlight() 218 { 219 String id = null; // thus far 220 final BiteJS bite = Scenes.i().biteSelection().getSelectedObject(); 221 if( bite != null ) 222 { 223 final PollJS poll = bite.poll(); 224 if( poll != null ) 225 { 226 final String pollName = poll.name(); 227 if( pollName != null ) 228 { 229 final TriaxialPollMap.MappedPoll pollM = map.polls().get( pollName ); 230 if( pollM != null && map.inScope( pollM )) id = pollNameToID( pollName ); 231 } 232 } 233 } 234 235 if( ObjectX.nullEquals( id, idLast )) return; 236 237 if( idLast != null ) 238 { 239 final OMSVGCircleElement circle = (OMSVGCircleElement) 240 svg.getOwnerDocument().getElementById( idLast ); 241 circle.removeClassNameBaseVal( "spotlighted" ); 242 } 243 244 idLast = id; 245 if( id != null ) 246 { 247 final OMSVGCircleElement circle = (OMSVGCircleElement) 248 svg.getOwnerDocument().getElementById( id ); 249 circle.addClassNameBaseVal( "spotlighted" ); 250 } 251 } 252 } 253 254 255 256}