package 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. import com.google.gwt.dom.client.Element; import com.google.web.bindery.event.shared.HandlerRegistration; import com.google.gwt.user.client.ui.*; import com.google.gwt.view.client.SelectionChangeEvent; import org.vectomatic.dom.svg.*; import org.vectomatic.dom.svg.utils.*; import votorola.a.web.gwt.*; import votorola.s.gwt.scene.*; import votorola.s.gwt.scene.feed.*; import votorola.s.gwt.stage.poll.*; import votorola.g.hold.*; import votorola.g.lang.*; import votorola.g.web.gwt.*; import static org.vectomatic.dom.svg.OMSVGLength.SVG_LENGTHTYPE_EMS; import static org.vectomatic.dom.svg.OMSVGLength.SVG_LENGTHTYPE_PERCENTAGE; /** A view of a triaxial map done as a scalable vector graphic (SVG). It uses the x and y * axes of the viewport plane, and simulates the z-axis with points plotted as circles of * varying brightness. * * @see Live example of a * TriaxialPollMapV (right) */ public final class TriaxialPollMapV extends HTML { /** Constructs a TriaxialPollMapV. */ public TriaxialPollMapV( TriaxialPollMap _map ) { map = _map; final OMSVGDocument doc = OMSVGParser.createDocument(); svg = doc.createSVGSVGElement(); getElement().appendChild( svg.getElement() ); svg.addClassNameBaseVal( "axial-TriaxialPollMapV" ); svg.setWidth( SVG_LENGTHTYPE_PERCENTAGE, 100 ); svg.setHeight( SVG_LENGTHTYPE_PERCENTAGE, 100 ); for( final TriaxialPollMap.MappedPoll poll: map.polls().values() ) { final OMSVGCircleElement circle = doc.createSVGCircleElement(); svg.appendChild( circle ); circle.getR().getBaseVal().newValueSpecifiedUnits( SVG_LENGTHTYPE_EMS, 0.5f ); final Element circleElement = (Element)circle.getNode(); // circleElement.setPropertyObject( "poll", poll ); /// we can look it up in our own map now that we set the 'id' circleElement.setAttribute( "id", pollNameToID( poll.name() )); } } // - W i d g e t ---------------------------------------------------------------------- protected @Override void onLoad() { spool = new Spool1(); new Scoper(); new Spotlighter(); super.onLoad(); } protected @Override void onUnload() { super.onUnload(); spool.unwind(); spool = null; // single use } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private static String idToPollName( final String id ) { final StringBuilder b = GWTX.stringBuilderClear(); b.append( id ); b.delete( 0, POLL_ID_PREFIX.length() ); votorola.a.count.PollService.xmlColonNameToPollName( b ); return b.toString(); } private final TriaxialPollMap map; private static final String POLL_ID_PREFIX = "p-"; private static String pollNameToID( final String pollName ) { final StringBuilder b = GWTX.stringBuilderClear(); b.append( pollName ); votorola.a.count.PollService.pollNameToXMLColonName( b ); b.insert( 0, POLL_ID_PREFIX ); return b.toString(); } private Spool spool; private final OMSVGSVGElement svg; // ==================================================================================== private final class Scoper implements ScopeChangeHandler { Scoper() { spool.add( new Hold() { final HandlerRegistration hR = map.scoping().addHandler( Scoper.this ); public void release() { hR.removeHandler(); } }); rescope(); // init state } public void onScopeChange( final ScopeChangeEvent e ) { rescope(); } private void rescope() { final DiaxialScoping scoping = map.scoping(); final float xTrans = -scoping.xMin(); final float yTrans = scoping.yMax() - 1; // opposite because SVG origin is at top final float xZoomPercent = 100 / (scoping.xMax() + xTrans); final float yZoomPercent = 100 / (scoping.yMax() - scoping.yMin()); final OMNodeList childList = svg.getElementsByTagName( "circle" ); for( int c = childList.getLength() - 1; c >= 0; --c ) { final OMSVGCircleElement circle = childList.getItem( c ); // final TriaxialPollMap.MappedPoll poll = (TriaxialPollMap.MappedPoll) // ((Element)circle.getNode()).getPropertyObject( "poll" ); final TriaxialPollMap.MappedPoll poll = map.polls().get( idToPollName( circle.getAttribute( "id" ))); final float x = poll.x(); final float y = 1 - poll.y(); // reflected, to put SVG origin at bottom circle.getCx().getBaseVal().newValueSpecifiedUnits( SVG_LENGTHTYPE_PERCENTAGE, (x + xTrans) * xZoomPercent ); circle.getCy().getBaseVal().newValueSpecifiedUnits( SVG_LENGTHTYPE_PERCENTAGE, (y + yTrans) * yZoomPercent ); final String brightness = Integer.toHexString( Math.round( poll.z() * 0x90 )); // CSS range 0 - FF final boolean needsPadding = brightness.length() == 1; // do our own padding, because GWT NumberFormat cannot do hexadecimal final StringBuilder b = GWTX.stringBuilderClear(); b.append( '#' ); for( int n = 0; n < 3; n++ ) { if( needsPadding ) b.append( '0' ); b.append( brightness ); } circle.getStyle().setSVGProperty( "fill", b.toString() ); } } } // ==================================================================================== private final class Spotlighter implements ScopeChangeHandler, SelectionChangeEvent.Handler { Spotlighter() { spool.add( new Hold() { final HandlerRegistration hR = map.scoping().addHandler( Spotlighter.this ); public void release() { hR.removeHandler(); } }); spool.add( new Hold() { final HandlerRegistration hR = Scenes.i().biteSelection().addSelectionChangeHandler( Spotlighter.this ); public void release() { hR.removeHandler(); } }); respotlight(); // init state } private String idLast; public void onSelectionChange( SelectionChangeEvent e ) { respotlight(); } public void onScopeChange( final ScopeChangeEvent e ) { respotlight(); } private void respotlight() { String id = null; // thus far final BiteJS bite = Scenes.i().biteSelection().getSelectedObject(); if( bite != null ) { final PollJS poll = bite.poll(); if( poll != null ) { final String pollName = poll.name(); if( pollName != null ) { final TriaxialPollMap.MappedPoll pollM = map.polls().get( pollName ); if( pollM != null && map.inScope( pollM )) id = pollNameToID( pollName ); } } } if( ObjectX.nullEquals( id, idLast )) return; if( idLast != null ) { final OMSVGCircleElement circle = (OMSVGCircleElement) svg.getOwnerDocument().getElementById( idLast ); circle.removeClassNameBaseVal( "spotlighted" ); } idLast = id; if( id != null ) { final OMSVGCircleElement circle = (OMSVGCircleElement) svg.getOwnerDocument().getElementById( id ); circle.addClassNameBaseVal( "spotlighted" ); } } } }