001package org.biopax.paxtools.io.sbgn;
002
003import java.util.ArrayList;
004import java.util.Iterator;
005import java.util.List;
006
007import org.sbgn.bindings.Port;
008import org.ivis.layout.LGraphObject;
009import org.ivis.layout.LNode;
010import org.ivis.layout.Updatable;
011import org.ivis.layout.sbgn.SbgnProcessNode;
012import org.sbgn.bindings.Glyph;
013import org.sbgn.bindings.Port;
014import org.sbgn.bindings.Bbox;
015import org.ivis.layout.cose.CoSEGraph;
016
017
018
019/**
020 * VNode Class
021 * @author: Istemi Bahceci
022 * */
023
024public class VNode implements Updatable
025{
026    //Glyph attribute of this VNode
027    public Glyph glyph;
028    public int clusterID;
029
030    ArrayList <Glyph> stateGlyphs;
031    ArrayList <Glyph> infoGlyphs;
032
033    /*Glyph class types*/
034    private static final String  MACROMOLECULE = "macromolecule";
035    private static final  String UNIT_OF_INFORMATION = "unit of information";
036    private static final  String STATE_VARIABLE = "state variable";
037    private static final  String SOURCE_AND_SINK = "source and sink";
038    private static final  String ASSOCIATION = "association";
039    private static final  String DISSOCIATION = "dissociation";
040    private static final  String OMITTED_PROCESS = "omitted process";
041    private static final  String UNCERTAIN_PROCESS = "uncertain process";
042    private static final  String SIMPLE_CHEMICAL = "simple chemical";
043    private static final  String PROCESS = "process";
044    private static final  String COMPLEX = "complex";
045    private static final  String AND = "and";
046    private static final  String OR = "or";
047    private static final  String NOT = "not";
048    private static final  String PHENOTYPE = "phenotype";
049    private static final  String PERTURBING_AGENT = "perturbing agent";
050    private static final  String TAG = "tag";
051    private static final  String NUCLEIC_ACID_FEATURE = "nucleic acid feature";
052    private static final  String UNSPECIFIED_ENTITY = "unspecified entity";
053
054    // Constats used for determining "state of information" and "unit of information" glyph widths according to
055    // their labels
056    private static final  int LOWERCASE_LETTER_PIXEL_WIDTH = 6;
057    private static final  int UPPERCASE_LETTER_PIXEL_WIDTH = 9;
058    private static final  int MAX_STATE_AND_INFO_WIDTH = 50;
059    private static final  int MAX_STATE_AND_INFO_HEIGHT = 15;
060    private static final  int OFFSET_BTW_INFO_GLYPHS = 5;
061    private static final  int MAX_INFO_BOX_NUMBER = 4;
062    private static final  int MAX_MACROMOLECULE_HEIGHT_WITH_INFO_BOXES = 25;
063
064    /*Glyph Size Constants for layout*/
065    private static Bound  SOURCE_AND_SINK_BOUND;
066    private static Bound  LOGICAL_OPERATOR_BOUND;
067    private static Bound  PROCESS_NODES_BOUND;
068    private static Bound  MACROMOLECULE_BOUND;
069    private static Bound  NUCLEIC_ACID_FEATURE_BOUND;
070    private static Bound  SIMPLE_CHEMICAL_BOUND;
071    private static Bound  UNSPECIFIED_ENTITY_BOUND;
072    private static Bound  PHENOTYPE_BOUND;
073    private static Bound  PERTURBING_AGENT_BOUND;
074    private static Bound  TAG_BOUND;
075    private static Bound  INFO_BOUND;
076    private static Bound  STATE_BOUND;
077    private static Bound  COMPLEX_BOUND;
078
079
080    /**
081     * Default Constructor, sets the geometry of the bounds which are attributes of this class
082     * @param g glyph
083     * */
084    public VNode(Glyph g)
085    {
086        SOURCE_AND_SINK_BOUND = new Bound(15,15);
087        LOGICAL_OPERATOR_BOUND = new Bound(15,15);
088        PROCESS_NODES_BOUND = new Bound(15,15);
089
090        MACROMOLECULE_BOUND = new Bound(48,20);
091        NUCLEIC_ACID_FEATURE_BOUND = new Bound(50,20);
092
093        SIMPLE_CHEMICAL_BOUND = new Bound(48,20);
094        UNSPECIFIED_ENTITY_BOUND = new Bound(40,40);
095        PHENOTYPE_BOUND = new Bound(50,20);
096        TAG_BOUND = new Bound(50,20);
097        PERTURBING_AGENT_BOUND = new Bound(50,20);
098        COMPLEX_BOUND = new Bound(48,20);
099
100        INFO_BOUND = new Bound(MAX_STATE_AND_INFO_WIDTH,MAX_STATE_AND_INFO_HEIGHT);
101        STATE_BOUND = new Bound(MAX_STATE_AND_INFO_WIDTH,MAX_STATE_AND_INFO_HEIGHT);
102
103        stateGlyphs = new ArrayList<Glyph>();
104        infoGlyphs = new  ArrayList<Glyph>();
105
106        this.glyph = g;
107
108        this.setSizeAccordingToClass();
109    }
110
111    /**
112     * Function that will take place when VNode objects will update in layout process of ChiLay
113     *
114     * @param lGraphObj LGraphObject for whom the update will take place.
115     */
116    public void update(LGraphObject lGraphObj)
117    {
118        if (lGraphObj instanceof CoSEGraph)
119        {
120            return;
121        }
122
123        LNode lNode = (LNode)lGraphObj;
124
125        this.glyph.getBbox().setX((float) lNode.getLeft());
126        this.glyph.getBbox().setY((float) lNode.getTop());
127        this.placeStateAndInfoGlyphs();
128    }
129
130    /**
131     * Sets the bound of this VNode by given width and height
132     * @param w new width
133     * @param h new height
134     */
135    public void setBounds(float w, float h)
136    {
137        this.glyph.getBbox().setW(w);
138        this.glyph.getBbox().setH(h);
139    }
140
141    /**
142     * Chooses a proper bound for this VNode according to its class.
143     */
144    public void setSizeAccordingToClass()
145    {
146        String glyphClass = this.glyph.getClazz();
147                
148                /*
149                 * need to add bbox objects
150                 */
151        Bbox b = new Bbox();
152        this.glyph.setBbox(b);
153
154        if (glyphClass == SOURCE_AND_SINK)
155        {
156            setBounds(SOURCE_AND_SINK_BOUND.getWidth(), SOURCE_AND_SINK_BOUND.getHeight());
157        }
158
159        else if (glyphClass == AND || glyphClass == OR || glyphClass == NOT )
160        {
161            setBounds(LOGICAL_OPERATOR_BOUND.getWidth(), LOGICAL_OPERATOR_BOUND.getHeight());
162        }
163
164        else if (glyphClass == ASSOCIATION || glyphClass == DISSOCIATION || glyphClass == OMITTED_PROCESS ||
165                glyphClass == UNCERTAIN_PROCESS || glyphClass  == PROCESS)
166        {
167            setBounds(PROCESS_NODES_BOUND.getWidth(), PROCESS_NODES_BOUND.getHeight());
168        }
169
170        else if (glyphClass == SIMPLE_CHEMICAL)
171        {
172            setBounds(SIMPLE_CHEMICAL_BOUND.getWidth(), SIMPLE_CHEMICAL_BOUND.getHeight());
173        }
174
175        else if (glyphClass == UNSPECIFIED_ENTITY)
176        {
177            setBounds(UNSPECIFIED_ENTITY_BOUND.getWidth(), UNSPECIFIED_ENTITY_BOUND.getHeight());
178        }
179
180        else if (glyphClass == MACROMOLECULE)
181        {
182            setBounds(MACROMOLECULE_BOUND.getWidth(), MACROMOLECULE_BOUND.getHeight());
183        }
184
185        else if (glyphClass == NUCLEIC_ACID_FEATURE)
186        {
187            setBounds(NUCLEIC_ACID_FEATURE_BOUND.getWidth(), NUCLEIC_ACID_FEATURE_BOUND.getHeight());
188        }
189
190        else if (glyphClass == STATE_VARIABLE)
191        {
192            setBounds(STATE_BOUND.getWidth(), STATE_BOUND.getHeight());
193        }
194
195        else if (glyphClass == UNIT_OF_INFORMATION)
196        {
197            setBounds(INFO_BOUND.getWidth(), INFO_BOUND.getHeight());
198        }
199
200        else if (glyphClass == PHENOTYPE)
201        {
202            setBounds(PHENOTYPE_BOUND.getWidth(), PHENOTYPE_BOUND.getHeight());
203        }
204
205        else if (glyphClass == PERTURBING_AGENT)
206        {
207            setBounds(PERTURBING_AGENT_BOUND.getWidth(), PERTURBING_AGENT_BOUND.getHeight());
208        }
209
210        else if (glyphClass == TAG)
211        {
212            setBounds(TAG_BOUND.getWidth(), TAG_BOUND.getHeight());
213        }
214
215        else if (glyphClass == COMPLEX)
216        {
217            setBounds(COMPLEX_BOUND.getWidth(), COMPLEX_BOUND.getHeight());
218        }
219
220        if( this.glyph.getClone() != null )
221        {
222            Bbox glyphBbox = this.glyph.getBbox();
223            setBounds(3*glyphBbox.getW()/4, 3*glyphBbox.getH()/4);
224        }
225
226        if (glyphClass == MACROMOLECULE || glyphClass == NUCLEIC_ACID_FEATURE || glyphClass == SIMPLE_CHEMICAL || glyphClass == COMPLEX)
227        {
228            updateSizeForStateAndInfo();
229        }
230
231    }
232
233    /**
234     * Calculates required width according to the given list state and info glyphs of this VNode.
235     * This method also previously computes the width and height of state and info glyphs
236     * according to their label.
237     *
238     * @param  stateORinfoList list that keeps state or info glyphs of this VNode
239     * @return new width that is adjusted so that all glyphs in stateORinfoList are included.
240     * */
241    public int calcReqWidthByStateAndInfos(List<Glyph> stateORinfoList)
242    {
243        int wholeSize = 0;
244        int count = 0;
245
246        for (Glyph tmpGlyph: stateORinfoList)
247        {
248            String text;
249
250            if (tmpGlyph.getState() != null)
251            {
252                text = tmpGlyph.getState().getValue();
253
254                if (tmpGlyph.getState().getVariable() != null &&
255                        tmpGlyph.getState().getVariable().length() > 0)
256                {
257                    if(tmpGlyph.getState().getVariable() != null)
258                        text += "@" + tmpGlyph.getState().getVariable();
259                }
260            }
261            else if (tmpGlyph.getLabel() != null)
262            {
263                text = tmpGlyph.getLabel().getText();
264            }
265            else
266            {
267                throw new RuntimeException("Encountered an information glyph with no state " +
268                        "variable (as modification boxes should have) and no label (as molecule type " +
269                        "boxed should have). glyph = " + tmpGlyph);
270            }
271
272            int numOfUpper = 0;
273            int numOfLower = 0;
274
275            for (int i = 0; i < text.length(); i++)
276            {
277                if (Character.isLowerCase(text.charAt(i)))
278                {
279                    numOfLower++;
280
281                } else
282                    numOfUpper++;
283            }
284
285            Bbox b = new Bbox();
286            tmpGlyph.setBbox(b);
287
288            //Set width
289            float requiredSize = numOfLower * LOWERCASE_LETTER_PIXEL_WIDTH + numOfUpper * UPPERCASE_LETTER_PIXEL_WIDTH;
290            if (requiredSize < MAX_STATE_AND_INFO_WIDTH)
291                tmpGlyph.getBbox().setW(requiredSize);
292            else
293                tmpGlyph.getBbox().setW(STATE_BOUND.width);
294
295            //Set height
296            tmpGlyph.getBbox().setH(MAX_STATE_AND_INFO_HEIGHT);
297
298            if (count < MAX_INFO_BOX_NUMBER / 2)
299                wholeSize += tmpGlyph.getBbox().getW();
300
301            count++;
302
303        }
304
305        return wholeSize;
306    }
307
308    /**
309     *  If glyph attribute of this VNode object includes any "state of information" or "unit of information" glyphs, this method updates the
310     *  size of VNode accordingly.
311     * */
312    public void updateSizeForStateAndInfo()
313    {
314
315        // Find all state and info glyphs
316        for (Glyph glyph : this.glyph.getGlyph())
317        {
318            if (glyph.getClazz() == STATE_VARIABLE)
319            {
320                stateGlyphs.add(glyph);
321            }
322            else if (glyph.getClazz() == UNIT_OF_INFORMATION)
323            {
324                infoGlyphs.add(glyph);
325            }
326        }
327
328        //Calculate "state of information" glyphs' sizes
329        int wholeWidthOfStates = calcReqWidthByStateAndInfos(stateGlyphs);
330        int wholeWidthOfInfos  = calcReqWidthByStateAndInfos(infoGlyphs);
331
332        // Calculate  positions
333        int numOfStates = stateGlyphs.size();
334        int numOfInfos = infoGlyphs.size();
335
336        //Adjust heights so that info box offsets are taken into account in layout.
337        if(numOfStates > 0 || numOfInfos > 0)
338            this.glyph.getBbox().setH(this.glyph.getBbox().getH()+MAX_STATE_AND_INFO_HEIGHT/2);
339
340        //Half of the info boxes on the upper side, half on the bottom side.
341        numOfStates = (numOfStates >= MAX_INFO_BOX_NUMBER/2) ? MAX_INFO_BOX_NUMBER/2 : numOfStates;
342        numOfInfos  = (numOfInfos  >= MAX_INFO_BOX_NUMBER/2) ? MAX_INFO_BOX_NUMBER/2 : numOfInfos;
343
344        float requiredWidthForStates = (numOfStates+1) * OFFSET_BTW_INFO_GLYPHS + wholeWidthOfStates;
345        float requiredWidthForInfos =  (numOfInfos+1)  * OFFSET_BTW_INFO_GLYPHS + wholeWidthOfInfos;
346
347
348        if (this.glyph.getBbox().getW() < requiredWidthForStates || this.glyph.getBbox().getW() < requiredWidthForInfos )
349        {
350            this.glyph.getBbox().setW(Math.max(requiredWidthForStates, requiredWidthForInfos));
351        }
352    }
353
354    /**
355     * Places state and info glyphs of this node
356     * */
357    public void placeStateAndInfoGlyphs()
358    {
359        int numOfStates = stateGlyphs.size();
360        int numOfInfos = infoGlyphs.size();
361
362        //Adjust heights so that inf obox offsets are taken into account in layout.
363        if(numOfStates > 0 || numOfInfos > 0)
364            this.glyph.getBbox().setH(this.glyph.getBbox().getH()-MAX_STATE_AND_INFO_HEIGHT/2);
365
366        float parent_y_up = this.glyph.getBbox().getY()-INFO_BOUND.height/2;
367        float parent_y_bot = this.glyph.getBbox().getY()+this.glyph.getBbox().getH()-INFO_BOUND.height/2;;
368        float parent_x_up = this.glyph.getBbox().getX();
369        float parentWidth = this.glyph.getBbox().getW();
370        String parentID = this.glyph.getId();
371
372        int usedWidth = 0;
373        for (int i = 0; i < numOfStates; i++)
374        {
375            Glyph tmpglyph = stateGlyphs.get(i);
376            if(numOfStates == 1)
377            {
378                tmpglyph.getBbox().setX(parent_x_up+parentWidth/2-tmpglyph.getBbox().getW()/2);
379                tmpglyph.getBbox().setY(parent_y_bot);
380                //set dummy id
381                tmpglyph.setId(parentID + ".state." + (i+1) );
382                break;
383            }
384
385            //set dummy id
386            tmpglyph.setId(parentID + ".state." + (i+1) );
387
388            tmpglyph.getBbox().setX(parent_x_up+(i+1)*OFFSET_BTW_INFO_GLYPHS + usedWidth);
389            tmpglyph.getBbox().setY(parent_y_bot);
390
391            usedWidth += tmpglyph.getBbox().getW();
392
393        }
394
395        usedWidth = 0;
396        for (int i = 0; i < numOfInfos; i++)
397        {
398            Glyph tmpglyph = infoGlyphs.get(i);
399            if(numOfInfos == 1)
400            {
401                tmpglyph.getBbox().setX(parent_x_up+parentWidth/2-tmpglyph.getBbox().getW()/2);
402                tmpglyph.getBbox().setY(parent_y_up);
403                //set dummy id
404                tmpglyph.setId(parentID + ".info." + (i+1) );
405                break;
406            }
407
408            //set dummy id
409            tmpglyph.setId(parentID + ".info." + (i+1) );
410
411            tmpglyph.getBbox().setX(parent_x_up+(i+1)*OFFSET_BTW_INFO_GLYPHS + usedWidth);
412            tmpglyph.getBbox().setY(parent_y_up);
413
414            usedWidth += tmpglyph.getBbox().getW();
415        }
416    }
417
418    /**
419     * Inner Class for glyph bounds
420     * */
421    public class Bound
422    {
423        public float width;
424        public float height;
425
426        public Bound(float width, float height)
427        {
428            this.width = width;
429            this.height = height;
430        }
431
432        public float getWidth()
433        {
434            return width;
435        }
436
437        public void setWidth(float width)
438        {
439            this.width = width;
440        }
441
442        public float getHeight()
443        {
444            return height;
445        }
446
447        public void setHeight(float height)
448        {
449            this.height = height;
450        }
451    }
452}