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}